summaryrefslogtreecommitdiff
path: root/src/spu_dec/sputext_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/spu_dec/sputext_decoder.c')
-rw-r--r--src/spu_dec/sputext_decoder.c1211
1 files changed, 1211 insertions, 0 deletions
diff --git a/src/spu_dec/sputext_decoder.c b/src/spu_dec/sputext_decoder.c
new file mode 100644
index 000000000..0eb42d665
--- /dev/null
+++ b/src/spu_dec/sputext_decoder.c
@@ -0,0 +1,1211 @@
+/*
+ * Copyright (C) 2000-2004 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#define LOG_MODULE "libsputext"
+#define LOG_VERBOSE
+/*
+#define LOG
+*/
+
+#include <xine/buffer.h>
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include <xine/osd.h>
+
+#define SUB_MAX_TEXT 5 /* lines */
+#define SUB_BUFSIZE 256 /* chars per line */
+
+/* alignment in SSA codes */
+#define ALIGN_LEFT 1
+#define ALIGN_CENTER 2
+#define ALIGN_RIGHT 3
+#define ALIGN_BOTTOM 0
+#define ALIGN_TOP 4
+#define ALIGN_MIDDLE 8
+#define GET_X_ALIGNMENT(a) ((a) & 3)
+#define GET_Y_ALIGNMENT(a) ((a) - ((a) & 3))
+
+/* subtitles projection */
+/* for subrip file with SSA tags, those values are always correct.*/
+/* But for SSA files, those values are the default ones. we have */
+/* to use PlayResX and PlayResY defined in [Script Info] section. */
+/* not implemented yet... */
+#define SPU_PROJECTION_X 384
+#define SPU_PROJECTION_Y 288
+
+
+
+#define rgb2yuv(R,G,B) ((((((66*R+129*G+25*B+128)>>8)+16)<<8)|(((112*R-94*G-18*B+128)>>8)+128))<<8|(((-38*R-74*G+112*B+128)>>8)+128))
+
+static const uint32_t sub_palette[22]={
+/* RED */
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(50,10,10),
+ rgb2yuv(120,20,20),
+ rgb2yuv(185,50,50),
+ rgb2yuv(255,70,70),
+/* BLUE */
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,30,50),
+ rgb2yuv(0,90,120),
+ rgb2yuv(0,140,185),
+ rgb2yuv(0,170,255)
+};
+
+static const uint8_t sub_trans[22]={
+ 0, 0, 3, 6, 8, 10, 12, 14, 15, 15, 15,
+ 0, 0, 3, 6, 8, 10, 12, 14, 15, 15, 15
+};
+
+typedef enum {
+ SUBTITLE_SIZE_TINY = 0,
+ SUBTITLE_SIZE_SMALL,
+ SUBTITLE_SIZE_NORMAL,
+ SUBTITLE_SIZE_LARGE,
+ SUBTITLE_SIZE_VERY_LARGE,
+ SUBTITLE_SIZE_HUGE,
+
+ SUBTITLE_SIZE_NUM /* number of values in enum */
+} subtitle_size;
+
+#define FONTNAME_SIZE 100
+
+typedef struct sputext_class_s {
+ spu_decoder_class_t class;
+
+ subtitle_size subtitle_size; /* size of subtitles */
+ int vertical_offset;
+ char font[FONTNAME_SIZE]; /* subtitle font */
+#ifdef HAVE_FT2
+ char font_ft[FILENAME_MAX]; /* subtitle font */
+ int use_font_ft; /* use Freetype */
+#endif
+ const char *src_encoding; /* encoding of subtitle file */
+ int use_unscaled; /* use unscaled OSD if possible */
+
+ xine_t *xine;
+
+} sputext_class_t;
+
+
+/* Convert subtiles coordinates in window coordinates. */
+/* (a, b) --> (x + a * dx, y + b * dy) */
+typedef struct video2wnd_s {
+ int x;
+ int y;
+ double dx;
+ double dy;
+} video2wnd_t;
+
+typedef struct sputext_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ sputext_class_t *class;
+ xine_stream_t *stream;
+
+ int ogm;
+ int lines;
+ char text[SUB_MAX_TEXT][SUB_BUFSIZE];
+
+ /* below 3 variables are the same from class. use to detect
+ * when something changes.
+ */
+ subtitle_size subtitle_size; /* size of subtitles */
+ int vertical_offset;
+ char font[FILENAME_MAX]; /* subtitle font */
+ char *buf_encoding; /* encoding of subtitle buffer */
+
+ int width; /* frame width */
+ int height; /* frame height */
+ int font_size;
+ int line_height;
+ int started;
+ int finished;
+
+ osd_renderer_t *renderer;
+ osd_object_t *osd;
+ int current_osd_text;
+ uint32_t spu_palette[OVL_PALETTE_SIZE];
+ uint8_t spu_trans[OVL_PALETTE_SIZE];
+
+ int64_t img_duration;
+ int64_t last_subtitle_end; /* no new subtitle before this vpts */
+ int unscaled; /* use unscaled OSD */
+
+ int last_y; /* location of the previous subtitle */
+ int last_lines; /* number of lines of the previous subtitle */
+ video2wnd_t video2wnd;
+} sputext_decoder_t;
+
+static inline char *get_font (sputext_class_t *class)
+{
+#ifdef HAVE_FT2
+ return class->use_font_ft ? class->font_ft : class->font;
+#else
+ return class->font;
+#endif
+}
+
+static void update_font_size (sputext_decoder_t *this, int force_update) {
+ static const int sizes[SUBTITLE_SIZE_NUM] = { 16, 20, 24, 32, 48, 64 };
+
+ if ((this->subtitle_size != this->class->subtitle_size) ||
+ (this->vertical_offset != this->class->vertical_offset) ||
+ force_update) {
+
+ this->subtitle_size = this->class->subtitle_size;
+ this->vertical_offset = this->class->vertical_offset;
+ this->last_lines = 0;
+
+ this->font_size = sizes[this->class->subtitle_size];
+
+ this->line_height = this->font_size + 10;
+
+ /* Create a full-window OSD */
+ if( this->osd )
+ this->renderer->free_object (this->osd);
+
+ this->osd = this->renderer->new_object (this->renderer,
+ this->width,
+ this->height);
+
+ this->renderer->set_font (this->osd, get_font (this->class), this->font_size);
+
+ this->renderer->set_position (this->osd, 0, 0);
+ }
+}
+
+static void update_output_size (sputext_decoder_t *this) {
+ const int unscaled = this->class->use_unscaled &&
+ (this->stream->video_out->get_capabilities(this->stream->video_out) &
+ VO_CAP_UNSCALED_OVERLAY);
+
+ if( unscaled != this->unscaled ) {
+ this->unscaled = unscaled;
+ this->width = 0; /* force update */
+ }
+
+ /* initialize decoder if needed */
+ if( this->unscaled ) {
+ if( this->width != this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_WIDTH) ||
+ this->height != this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_HEIGHT) ||
+ !this->img_duration || !this->osd ) {
+
+ int width = 0, height = 0;
+
+ this->stream->video_out->status(this->stream->video_out, NULL,
+ &width, &height, &this->img_duration );
+ if( width && height ) {
+
+ this->width = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_WIDTH);
+ this->height = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_HEIGHT);
+
+ if(!this->osd || (this->width && this->height)) {
+
+ /* in unscaled mode, we have to convert subtitle position in window coordinates. */
+ /* we have a scale factor because video may be zommed */
+ /* and a displacement factor because video may have blacks lines. */
+ int output_width, output_height, output_xoffset, output_yoffset;
+
+ output_width = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_WIDTH);
+ output_height = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_HEIGHT);
+ output_xoffset = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_XOFFSET);
+ output_yoffset = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_YOFFSET);
+
+ /* driver don't seen to be capable to give us those values */
+ /* fallback to a default full-window values */
+ if (output_width <= 0 || output_height <= 0) {
+ output_width = this->width;
+ output_height = this->height;
+ output_xoffset = 0;
+ output_yoffset = 0;
+ }
+
+ this->video2wnd.x = output_xoffset;
+ this->video2wnd.y = output_yoffset;
+ this->video2wnd.dx = (double)output_width / SPU_PROJECTION_X;
+ this->video2wnd.dy = (double)output_height / SPU_PROJECTION_Y;
+
+ this->renderer = this->stream->osd_renderer;
+ update_font_size (this, 1);
+ }
+ }
+ }
+ } else {
+ if( !this->width || !this->height || !this->img_duration || !this->osd ) {
+
+ this->width = 0;
+ this->height = 0;
+
+ this->stream->video_out->status(this->stream->video_out, NULL,
+ &this->width, &this->height, &this->img_duration );
+
+ if(!this->osd || ( this->width && this->height)) {
+ this->renderer = this->stream->osd_renderer;
+
+ /* in scaled mode, we have to convert subtitle position in film coordinates. */
+ this->video2wnd.x = 0;
+ this->video2wnd.y = 0;
+ this->video2wnd.dx = (double)this->width / SPU_PROJECTION_X;
+ this->video2wnd.dy = (double)this->height / SPU_PROJECTION_Y;
+
+ update_font_size (this, 1);
+ }
+ }
+ }
+}
+
+static int parse_utf8_size(const void *buf)
+{
+ const uint8_t *c = buf;
+ if ( c[0]<0x80 )
+ return 1;
+
+ if( c[1]==0 )
+ return 1;
+ if ( (c[0]>=0xC2 && c[0]<=0xDF) && (c[1]>=0x80 && c[1]<=0xBF) )
+ return 2;
+
+ if( c[2]==0 )
+ return 2;
+ else if ( c[0]==0xE0 && (c[1]>=0xA0 && c[1]<=0xBF) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else if ( (c[0]>=0xE1 && c[0]<=0xEC) && (c[1]>=0x80 && c[1]<=0xBF) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else if ( c[0]==0xED && (c[1]>=0x80 && c[1]<=0x9F) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else if ( c[0]==0xEF && (c[1]>=0xA4 && c[1]<=0xBF) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else
+ return 1;
+}
+
+static int ogm_render_line_internal(sputext_decoder_t *this, int x, int y, const char *text, int render)
+{
+ const size_t length = strlen (text);
+ size_t i = 0;
+
+ while (i <= length) {
+
+ if (text[i] == '<') {
+ if (!strncmp("<b>", text+i, 3)) {
+ /* enable Bold color */
+ if (render)
+ this->current_osd_text = OSD_TEXT2;
+ i=i+3;
+ continue;
+ } else if (!strncmp("</b>", text+i, 4)) {
+ /* disable BOLD */
+ if (render)
+ this->current_osd_text = OSD_TEXT1;
+ i=i+4;
+ continue;
+ } else if (!strncmp("<i>", text+i, 3)) {
+ /* enable italics color */
+ if (render)
+ this->current_osd_text = OSD_TEXT3;
+ i=i+3;
+ continue;
+ } else if (!strncmp("</i>", text+i, 4)) {
+ /* disable italics */
+ if (render)
+ this->current_osd_text = OSD_TEXT1;
+ i=i+4;
+ continue;
+ } else if (!strncmp("<font>", text+i, 6)) {
+ /*Do somethink to disable typing
+ fixme - no teststreams*/
+ i=i+6;
+ continue;
+ } else if (!strncmp("</font>", text+i, 7)) {
+ /*Do somethink to enable typing
+ fixme - no teststreams*/
+ i=i+7;
+ continue;
+ }
+ }
+ if (text[i] == '{') {
+
+ if (!strncmp("{\\", text+i, 2)) {
+ int value;
+
+ if (sscanf(text+i, "{\\b%d}", &value) == 1) {
+ if (render) {
+ if (value)
+ this->current_osd_text = OSD_TEXT2;
+ else
+ this->current_osd_text = OSD_TEXT1;
+ }
+ } else if (sscanf(text+i, "{\\i%d}", &value) == 1) {
+ if (render) {
+ if (value)
+ this->current_osd_text = OSD_TEXT3;
+ else
+ this->current_osd_text = OSD_TEXT1;
+ }
+ }
+ char *const end = strstr(text+i+2, "}");
+ if (end) {
+ i=end-text+1;
+ continue;
+ }
+ }
+ }
+
+ char letter[5];
+ const char *const encoding = this->buf_encoding ? : this->class->src_encoding;
+ const int isutf8 = !strcmp(encoding, "utf-8");
+ const size_t shift = isutf8 ? parse_utf8_size (&text[i]) : 1;
+ memcpy(letter,&text[i],shift);
+ letter[shift]=0;
+
+ if (render)
+ this->renderer->render_text(this->osd, x, y, letter, this->current_osd_text);
+
+ int w, dummy;
+ this->renderer->get_text_size(this->osd, letter, &w, &dummy);
+ x += w;
+ i += shift;
+ }
+
+ return x;
+}
+
+static inline int ogm_get_width(sputext_decoder_t *this, char* text) {
+ return ogm_render_line_internal (this, 0, 0, text, 0);
+}
+
+static inline void ogm_render_line(sputext_decoder_t *this, int x, int y, char* text) {
+ ogm_render_line_internal (this, x, y, text, 1);
+}
+
+/* read SSA tags at begening of text. Suported tags are : */
+/* \a : alignment in SSA code (see #defines) */
+/* \an : alignment in 'numpad code' */
+/* \pos : absolute position of subtitles. Alignment define origin. */
+static void read_ssa_tag(sputext_decoder_t *this, const char* text,
+ int* alignment, int* sub_x, int* sub_y, int* max_width) {
+
+ int in_tag = 0;
+
+ (*alignment) = 2;
+ (*sub_x) = -1;
+ (*sub_y) = -1;
+
+ while (*text) {
+
+ /* wait for tag begin, allow space and tab */
+ if (in_tag == 0) {
+ if (*text == '{') in_tag = 1;
+ else if ((*text != ' ') && (*text != '\t')) break;
+
+ /* parse SSA command */
+ } else {
+ if (*text == '\\') {
+ if (sscanf(text, "\\pos(%d,%d)", sub_x, sub_y) == 2) {
+ text += 8; /* just for speed up, 8 is the minimal with */
+ }
+
+ if (sscanf(text, "\\a%d", alignment) == 1) {
+ text += 2;
+ }
+
+ if (sscanf(text, "\\an%d", alignment) == 1) {
+ text += 3;
+ if ((*alignment) > 6) (*alignment) = (*alignment) - 2;
+ else if ((*alignment) > 3) (*alignment) = (*alignment) + 5;
+ }
+ }
+
+ if (*text == '}') in_tag = 0;
+ }
+
+ text++;
+ }
+
+
+ /* check alignment validity */
+ if ((*alignment) < 1 || (*alignment) > 11) {
+ (*alignment) = 2;
+ }
+
+ /* convert to window coordinates */
+ if ((*sub_x) >= 0 && (*sub_y) >= 0) {
+ (*sub_x) = this->video2wnd.x + this->video2wnd.dx * (*sub_x);
+ (*sub_y) = this->video2wnd.y + this->video2wnd.dy * (*sub_y);
+ }
+
+ /* check validity, compute max width */
+ if ( (*sub_x) < 0 || (*sub_x) >= this->width ||
+ (*sub_y) < 0 || (*sub_y) >= this->height ) {
+ (*sub_x) = -1;
+ (*sub_y) = -1;
+ (*max_width) = this->width;
+ } else {
+ switch (GET_X_ALIGNMENT(*alignment)) {
+ case ALIGN_LEFT:
+ (*max_width) = this->width - (*sub_x);
+ break;
+ case ALIGN_CENTER:
+ (*max_width) = this->width;
+ break;
+ case ALIGN_RIGHT:
+ (*max_width) = (*sub_x);
+ break;
+ }
+ }
+
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: position : (%d, %d), max width : %d, alignment : %d\n",
+ (*sub_x), (*sub_y), (*max_width), (*alignment));
+}
+
+static int is_cjk_encoding(const char *enc) {
+ /* CJK charset strings defined in iconvdata/gconv-modules of glibc */
+ static const char cjk_encoding_strings[][16] = {
+ "SJIS",
+ "CP932",
+ "EUC-KR",
+ "UHC",
+ "JOHAB",
+ "BIG5",
+ "BIG5HKSCS",
+ "EUC-JP-MS",
+ "EUC-JP",
+ "EUC-CN",
+ "GBBIG5",
+ "GBK",
+ "GBGBK",
+ "EUC-TW",
+ "ISO-2022-JP",
+ "ISO-2022-JP-2",
+ "ISO-2022-JP-3",
+ "ISO-2022-KR",
+ "ISO-2022-CN",
+ "ISO-2022-CN-EXT",
+ "GB18030",
+ "EUC-JISX0213",
+ "SHIFT_JISX0213",
+ };
+
+ int pstr;
+
+ /* return 1 if encoding string is one of the CJK(Chinese,Jananese,Korean)
+ * character set strings. */
+ for (pstr = 0; pstr < sizeof (cjk_encoding_strings) / sizeof (cjk_encoding_strings[0]); pstr++)
+ if (strcasecmp (enc, cjk_encoding_strings[pstr]) == 0)
+ return 1;
+
+ return 0;
+}
+
+static void draw_subtitle(sputext_decoder_t *this, int64_t sub_start, int64_t sub_end ) {
+
+ int y;
+ int sub_x, sub_y, max_width = this->width;
+ int alignment;
+
+ _x_assert(this->renderer != NULL);
+ if ( ! this->renderer )
+ return;
+
+ read_ssa_tag(this, this->text[0], &alignment, &sub_x, &sub_y, &max_width);
+
+ update_font_size(this, 0);
+
+ const char *const font = get_font (this->class);
+ if( strcmp(this->font, font) ) {
+ strncpy(this->font, font, FILENAME_MAX);
+ this->font[FILENAME_MAX - 1] = '\0';
+ this->renderer->set_font (this->osd, font, this->font_size);
+ }
+
+ int font_size = this->font_size;
+
+ const char *const encoding = this->buf_encoding ? : this->class->src_encoding;
+ this->renderer->set_encoding(this->osd, encoding);
+
+ int rebuild_all = 0;
+ int line;
+ for (line = 0; line < this->lines; line++) {
+ int line_width = ogm_get_width(this, this->text[line]);
+
+ /* line too long */
+ if (line_width > max_width) {
+ char *current_cut, *best_cut;
+ int a;
+
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: Line too long: %d > %d, split at max size.\n",
+ line_width, max_width);
+
+ /* can't fit with keeping existing lines */
+ if (this->lines + 1 > SUB_MAX_TEXT) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: Can't fit with keeping existing line, we have to rebuild all the subtitle\n");
+ rebuild_all = 1;
+ break;
+ }
+
+ /* find the longest sequence witch fit */
+ line_width = 0;
+ current_cut = this->text[line];
+ best_cut = NULL;
+ while (line_width < max_width) {
+ while (*current_cut && *current_cut != ' ') current_cut++;
+ if (*current_cut == ' ') {
+ *current_cut = 0;
+ line_width = ogm_get_width(this, this->text[line]);
+ *current_cut = ' ';
+ if (line_width < max_width) best_cut = current_cut;
+ current_cut++;
+ } else {
+ break; /* end of line */
+ }
+ }
+
+ if (best_cut == NULL) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: Can't wrap line: a word is too long, abort.\n");
+ break;
+ }
+
+ /* move other lines */
+ for (a = this->lines - 1; a > line; a--)
+ memcpy(this->text[a + 1], this->text[a], SUB_BUFSIZE);
+
+ /* split current one */
+ strncpy(this->text[line + 1], best_cut + 1, SUB_BUFSIZE);
+ *best_cut = 0;
+
+ this->lines = this->lines + 1;
+ }
+ }
+
+ /* regenerate all the lines to find something that better fits */
+ if (rebuild_all) {
+ char buf[SUB_BUFSIZE * SUB_MAX_TEXT] = { 0, };
+
+ int line;
+ for(line = 0; line < this->lines; line++) {
+ const size_t len = strlen(buf);
+ if (len)
+ buf[len] = ' ';
+
+ strncat(buf, this->text[line], SUB_BUFSIZE-len-1);
+ }
+
+ char *stream = buf;
+ this->lines = 0;
+
+ char *current_cut, *best_cut;
+ do {
+
+ if (this->lines + 1 < SUB_MAX_TEXT) {
+
+ /* find the longest sequence witch fit */
+ int line_width = 0;
+ current_cut = stream;
+ best_cut = NULL;
+ while (line_width < max_width) {
+ while (*current_cut && *current_cut != ' ') current_cut++;
+ if (*current_cut == ' ') {
+ *current_cut = 0;
+ line_width = ogm_get_width(this, stream);
+ *current_cut = ' ';
+ if (line_width < max_width) best_cut = current_cut;
+ current_cut++;
+ } else {
+ line_width = ogm_get_width(this, stream);
+ if (line_width < max_width) best_cut = current_cut;
+ break; /* end of line */
+ }
+ }
+ }
+
+ /* line maybe too long, but we have reached last subtitle line */
+ else {
+ best_cut = current_cut = stream + strlen(stream);
+ }
+
+ /* copy current line */
+ if (best_cut != NULL) *best_cut = 0;
+ strncpy(this->text[this->lines], stream, SUB_BUFSIZE);
+ this->lines = this->lines + 1;
+
+ stream = best_cut + 1;
+
+ } while (best_cut != current_cut);
+
+ }
+
+
+ /* Erase subtitle : use last_y and last_lines saved last turn. */
+ if (this->last_lines) {
+ this->renderer->filled_rect (this->osd, 0, this->last_y,
+ this->width - 1, this->last_y + this->last_lines * this->line_height,
+ 0);
+ }
+
+ switch (GET_Y_ALIGNMENT(alignment)) {
+ case ALIGN_TOP:
+ if (sub_y >= 0) y = sub_y;
+ else y = 5;
+ break;
+
+ case ALIGN_MIDDLE:
+ if (sub_y >= 0) y = sub_y - (this->lines * this->line_height) / 2;
+ else y = (this->height - this->lines * this->line_height) / 2;
+ break;
+
+ case ALIGN_BOTTOM:
+ default:
+ if (sub_y >= 0) y = sub_y - this->lines * this->line_height;
+ else y = this->height - this->lines * this->line_height - this->class->vertical_offset;
+ break;
+ }
+ if (y < 0 || y >= this->height)
+ y = this->height - this->line_height * this->lines;
+
+ this->last_lines = this->lines;
+ this->last_y = y;
+
+
+ for (line = 0; line < this->lines; line++) {
+ int w, x;
+
+ while(1) {
+ w = ogm_get_width( this, this->text[line]);
+
+ switch (GET_X_ALIGNMENT(alignment)) {
+ case ALIGN_LEFT:
+ if (sub_x >= 0) x = sub_x;
+ else x = 5;
+ break;
+
+ case ALIGN_RIGHT:
+ if (sub_x >= 0) x = sub_x - w;
+ else x = max_width - w - 5;
+ break;
+
+ case ALIGN_CENTER:
+ default:
+ if (sub_x >= 0) x = sub_x - w / 2;
+ else x = (max_width - w) / 2;
+ break;
+ }
+
+
+ if( w > max_width && font_size > 16 ) {
+ font_size -= 4;
+ this->renderer->set_font (this->osd, get_font (this->class), font_size);
+ } else {
+ break;
+ }
+ }
+
+ if( is_cjk_encoding(encoding) ) {
+ this->renderer->render_text (this->osd, x, y + line * this->line_height,
+ this->text[line], OSD_TEXT1);
+ } else {
+ ogm_render_line(this, x, y + line*this->line_height, this->text[line]);
+ }
+ }
+
+ if( font_size != this->font_size )
+ this->renderer->set_font (this->osd, get_font (this->class), this->font_size);
+
+ if( this->last_subtitle_end && sub_start < this->last_subtitle_end ) {
+ sub_start = this->last_subtitle_end;
+ }
+ this->last_subtitle_end = sub_end;
+
+ this->renderer->set_text_palette (this->osd, -1, OSD_TEXT1);
+ this->renderer->get_palette(this->osd, this->spu_palette, this->spu_trans);
+ /* append some colors for colored typeface tag */
+ memcpy(this->spu_palette+OSD_TEXT2, sub_palette, sizeof(sub_palette));
+ memcpy(this->spu_trans+OSD_TEXT2, sub_trans, sizeof(sub_trans));
+ this->renderer->set_palette(this->osd, this->spu_palette, this->spu_trans);
+
+ if (this->unscaled)
+ this->renderer->show_unscaled (this->osd, sub_start);
+ else
+ this->renderer->show (this->osd, sub_start);
+
+ this->renderer->hide (this->osd, sub_end);
+
+ lprintf ("scheduling subtitle >%s< at %"PRId64" until %"PRId64", current time is %"PRId64"\n",
+ this->text[0], sub_start, sub_end,
+ this->stream->xine->clock->get_current_time (this->stream->xine->clock));
+}
+
+
+static void spudec_decode_data (spu_decoder_t *this_gen, buf_element_t *buf) {
+
+ sputext_decoder_t *this = (sputext_decoder_t *) this_gen;
+ int uses_time;
+ int32_t start, end, diff;
+ int64_t start_vpts, end_vpts;
+ int64_t spu_offset;
+ int i;
+ uint32_t *val;
+ char *str;
+ extra_info_t extra_info;
+ int master_status, slave_status;
+ int vo_discard;
+
+ /* filter unwanted streams */
+ if (buf->decoder_flags & BUF_FLAG_HEADER) {
+ return;
+ }
+ if (buf->decoder_flags & BUF_FLAG_PREVIEW)
+ return;
+
+ if ((this->stream->spu_channel & 0x1f) != (buf->type & 0x1f))
+ return;
+
+ if ( (buf->decoder_flags & BUF_FLAG_SPECIAL) &&
+ (buf->decoder_info[1] == BUF_SPECIAL_CHARSET_ENCODING) )
+ this->buf_encoding = buf->decoder_info_ptr[2];
+ else
+ this->buf_encoding = NULL;
+
+ this->current_osd_text = OSD_TEXT1;
+
+ if( (buf->type & 0xFFFF0000) == BUF_SPU_OGM ) {
+
+ this->ogm = 1;
+ uses_time = 1;
+ val = (uint32_t * )buf->content;
+ start = *val++;
+ end = *val++;
+ str = (char *)val;
+
+ if (!*str) return;
+ /* Empty ogm packets (as created by ogmmux) clears out old messages. We already respect the end time. */
+
+ this->lines = 0;
+
+ i = 0;
+ while (*str && (this->lines < SUB_MAX_TEXT) && (i < SUB_BUFSIZE)) {
+ if (*str == '\r' || *str == '\n') {
+ if (i) {
+ this->text[ this->lines ][i] = 0;
+ this->lines++;
+ i = 0;
+ }
+ } else {
+ this->text[ this->lines ][i] = *str;
+ if (i < SUB_BUFSIZE-1)
+ i++;
+ }
+ str++;
+ }
+ if (i == SUB_BUFSIZE)
+ i--;
+
+ if (i) {
+ this->text[ this->lines ][i] = 0;
+ this->lines++;
+ }
+
+ } else {
+
+ this->ogm = 0;
+ val = (uint32_t * )buf->content;
+
+ this->lines = *val++;
+ uses_time = *val++;
+ start = *val++;
+ end = *val++;
+ str = (char *)val;
+ for (i = 0; i < this->lines; i++, str += strlen(str) + 1) {
+ strncpy( this->text[i], str, SUB_BUFSIZE - 1);
+ this->text[i][SUB_BUFSIZE - 1] = '\0';
+ }
+
+ }
+
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: decoder data [%s]\n", this->text[0]);
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: mode %d timing %d->%d\n", uses_time, start, end);
+
+ if( end <= start ) {
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: discarding subtitle with invalid timing\n");
+ return;
+ }
+
+ spu_offset = this->stream->master->metronom->get_option (this->stream->master->metronom,
+ METRONOM_SPU_OFFSET);
+ if( uses_time ) {
+ start += (spu_offset / 90);
+ end += (spu_offset / 90);
+ } else {
+ if( this->osd && this->img_duration ) {
+ start += spu_offset / this->img_duration;
+ end += spu_offset / this->img_duration;
+ }
+ }
+
+ while( !this->finished ) {
+
+ master_status = xine_get_status (this->stream->master);
+ slave_status = xine_get_status (this->stream);
+ vo_discard = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_DISCARD_FRAMES);
+
+ _x_get_current_info (this->stream->master, &extra_info, sizeof(extra_info) );
+
+ lprintf("master: %d slave: %d input_normpos: %d vo_discard: %d\n",
+ master_status, slave_status, extra_info.input_normpos, vo_discard);
+
+ if( !this->started && (master_status == XINE_STATUS_PLAY &&
+ slave_status == XINE_STATUS_PLAY &&
+ extra_info.input_normpos) ) {
+ lprintf("started\n");
+
+ this->width = this->height = 0;
+
+ update_output_size( this );
+ if( this->width && this->height ) {
+ this->started = 1;
+ }
+ }
+
+ if( this->started ) {
+
+ if( master_status != XINE_STATUS_PLAY ||
+ slave_status != XINE_STATUS_PLAY ||
+ vo_discard ) {
+ lprintf("finished\n");
+
+ this->width = this->height = 0;
+ this->finished = 1;
+ return;
+ }
+
+ if( this->osd ) {
+
+ /* try to use frame number mode */
+ if( !uses_time && extra_info.frame_number ) {
+
+ diff = end - extra_info.frame_number;
+
+ /* discard old subtitles */
+ if( diff < 0 ) {
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: discarding old subtitle\n");
+ return;
+ }
+
+ diff = start - extra_info.frame_number;
+
+ start_vpts = extra_info.vpts + diff * this->img_duration;
+ end_vpts = start_vpts + (end-start) * this->img_duration;
+
+ } else {
+
+ if( !uses_time ) {
+ start = start * this->img_duration / 90;
+ end = end * this->img_duration / 90;
+ uses_time = 1;
+ }
+
+ diff = end - extra_info.input_time;
+
+ /* discard old subtitles */
+ if( diff < 0 ) {
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: discarding old subtitle\n");
+ return;
+ }
+
+ diff = start - extra_info.input_time;
+
+ start_vpts = extra_info.vpts + diff * 90;
+ end_vpts = start_vpts + (end-start) * 90;
+ }
+
+ _x_spu_decoder_sleep(this->stream, start_vpts);
+ update_output_size( this );
+ draw_subtitle(this, start_vpts, end_vpts);
+
+ return;
+ }
+ }
+
+ if (_x_spu_decoder_sleep(this->stream, 0))
+ xine_usec_sleep (50000);
+ else
+ return;
+ }
+}
+
+
+static void spudec_reset (spu_decoder_t *this_gen) {
+ sputext_decoder_t *this = (sputext_decoder_t *) this_gen;
+
+ lprintf("i guess we just seeked\n");
+ this->width = this->height = 0;
+ this->started = this->finished = 0;
+ this->last_subtitle_end = 0;
+}
+
+static void spudec_discontinuity (spu_decoder_t *this_gen) {
+ /* sputext_decoder_t *this = (sputext_decoder_t *) this_gen; */
+
+}
+
+static void spudec_dispose (spu_decoder_t *this_gen) {
+ sputext_decoder_t *this = (sputext_decoder_t *) this_gen;
+
+ if (this->osd) {
+ this->renderer->free_object (this->osd);
+ this->osd = NULL;
+ }
+ free(this);
+}
+
+static void update_vertical_offset(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->vertical_offset = entry->num_value;
+}
+
+static void update_osd_font(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ strncpy(class->font, entry->str_value, FONTNAME_SIZE);
+ class->font[FONTNAME_SIZE - 1] = '\0';
+
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_font = %s\n", class->font );
+}
+
+#ifdef HAVE_FT2
+static void update_osd_font_ft(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ strncpy(class->font_ft, entry->str_value, FILENAME_MAX);
+ class->font_ft[FILENAME_MAX - 1] = '\0';
+
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_font_ft = %s\n", class->font_ft);
+}
+
+static void update_osd_use_font_ft(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->use_font_ft = entry->num_value;
+
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_use_font_ft = %d\n", class->use_font_ft);
+}
+#endif
+
+static void update_subtitle_size(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->subtitle_size = entry->num_value;
+}
+
+static void update_use_unscaled(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->use_unscaled = entry->num_value;
+}
+
+static spu_decoder_t *sputext_class_open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream) {
+
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+ sputext_decoder_t *this ;
+
+ this = (sputext_decoder_t *) calloc(1, sizeof(sputext_decoder_t));
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.get_interact_info = NULL;
+ this->spu_decoder.set_button = NULL;
+ this->spu_decoder.dispose = spudec_dispose;
+
+ this->class = class;
+ this->stream = stream;
+
+ return (spu_decoder_t *) this;
+}
+
+static void sputext_class_dispose (spu_decoder_class_t *class_gen) {
+ sputext_class_t *this = (sputext_class_t *)class_gen;
+
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.src_encoding");
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.subtitle_size");
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.vertical_offset");
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.use_unscaled_osd");
+ free (this);
+}
+
+static void update_src_encoding(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->src_encoding = entry->str_value;
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_src_encoding = %s\n", class->src_encoding );
+}
+
+static void *init_spu_decoder_plugin (xine_t *xine, void *data) {
+
+ static const char *const subtitle_size_strings[] = {
+ "tiny", "small", "normal", "large", "very large", "huge", NULL
+ };
+ sputext_class_t *this ;
+
+ lprintf("init class\n");
+
+ this = (sputext_class_t *) calloc(1, sizeof(sputext_class_t));
+
+ this->class.open_plugin = sputext_class_open_plugin;
+ this->class.identifier = "sputext";
+ this->class.description = N_("external subtitle decoder plugin");
+ this->class.dispose = sputext_class_dispose;
+
+ this->xine = xine;
+
+ this->subtitle_size = xine->config->register_enum(xine->config,
+ "subtitles.separate.subtitle_size",
+ 1,
+ subtitle_size_strings,
+ _("subtitle size"),
+ _("You can adjust the subtitle size here. The setting will "
+ "be evaluated relative to the window size."),
+ 0, update_subtitle_size, this);
+ this->vertical_offset = xine->config->register_num(xine->config,
+ "subtitles.separate.vertical_offset",
+ 0,
+ _("subtitle vertical offset"),
+ _("You can adjust the vertical position of the subtitle. "
+ "The setting will be evaluated relative to the window size."),
+ 0, update_vertical_offset, this);
+ strncpy(this->font, xine->config->register_string(xine->config,
+ "subtitles.separate.font",
+ "sans",
+ _("font for subtitles"),
+ _("A font from the xine font directory to be used for the "
+ "subtitle text."),
+ 10, update_osd_font, this), FONTNAME_SIZE);
+ this->font[FONTNAME_SIZE - 1] = '\0';
+#ifdef HAVE_FT2
+ strncpy(this->font_ft, xine->config->register_filename(xine->config,
+ "subtitles.separate.font_freetype",
+ "", XINE_CONFIG_STRING_IS_FILENAME,
+ _("font for subtitles"),
+ _("An outline font file (e.g. a .ttf) to be used for the subtitle text."),
+ 10, update_osd_font_ft, this), FILENAME_MAX);
+ this->font_ft[FILENAME_MAX - 1] = '\0';
+ this->use_font_ft = xine->config->register_bool(xine->config,
+ "subtitles.separate.font_use_freetype",
+ 0,
+ _("whether to use a freetype font"),
+ NULL,
+ 10, update_osd_use_font_ft, this);
+#endif
+ this->src_encoding = xine->config->register_string(xine->config,
+ "subtitles.separate.src_encoding",
+ xine_guess_spu_encoding(),
+ _("encoding of the subtitles"),
+ _("The encoding of the subtitle text in the stream. This setting "
+ "is used to render non-ASCII characters correctly. If non-ASCII "
+ "characters are not displayed as you expect, ask the "
+ "creator of the subtitles what encoding was used."),
+ 10, update_src_encoding, this);
+ this->use_unscaled = xine->config->register_bool(xine->config,
+ "subtitles.separate.use_unscaled_osd",
+ 1,
+ _("use unscaled OSD if possible"),
+ _("The unscaled OSD will be rendered independently of the video "
+ "frame and will always be sharp, even if the video is magnified. "
+ "This will look better, but does not work with all graphics "
+ "hardware. The alternative is the scaled OSD, which will become "
+ "blurry, if you enlarge a low resolution video to fullscreen, but "
+ "it works with all graphics cards."),
+ 10, update_use_unscaled, this);
+
+ return &this->class;
+}
+
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_TEXT, BUF_SPU_OGM, 0 };
+
+static const decoder_info_t spudec_info = {
+ supported_types, /* supported types */
+ 1 /* priority */
+};
+
+extern void *init_sputext_demux_class (xine_t *xine, void *data);
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_SPU_DECODER | PLUGIN_MUST_PRELOAD, 17, "sputext", XINE_VERSION_CODE, &spudec_info, &init_spu_decoder_plugin },
+ { PLUGIN_DEMUX, 27, "sputext", XINE_VERSION_CODE, NULL, &init_sputext_demux_class },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};