aboutsummaryrefslogtreecommitdiffhomepage
path: root/sub
diff options
context:
space:
mode:
authorGravatar Uoti Urpala <uau@glyph.nonexistent.invalid>2011-01-26 19:40:52 +0200
committerGravatar Uoti Urpala <uau@glyph.nonexistent.invalid>2011-01-26 20:39:05 +0200
commitc9026cb3210205b07e2e068467a18ee40f9259a3 (patch)
treeea9657ad306899c6fbedae4abd088e9ac44fa61f /sub
parentebd2058d033416274f2e4b40f5ad907d86f8aad5 (diff)
sub/OSD: move some related files to sub/
Diffstat (limited to 'sub')
-rw-r--r--sub/ass_mp.c355
-rw-r--r--sub/ass_mp.h82
-rw-r--r--sub/av_sub.c119
-rw-r--r--sub/av_sub.h30
-rw-r--r--sub/find_sub.c175
-rw-r--r--sub/font_load.c356
-rw-r--r--sub/font_load.h115
-rw-r--r--sub/font_load_ft.c1176
-rw-r--r--sub/osd_font.h545
-rw-r--r--sub/sd_ass.c2
-rw-r--r--sub/spudec.c1390
-rw-r--r--sub/spudec.h45
-rw-r--r--sub/sub.c1348
-rw-r--r--sub/sub.h167
-rw-r--r--sub/sub_cc.c347
-rw-r--r--sub/sub_cc.h29
-rw-r--r--sub/subreader.c2504
-rw-r--r--sub/subreader.h114
-rw-r--r--sub/unrar_exec.c235
-rw-r--r--sub/unrar_exec.h54
-rw-r--r--sub/vobsub.c1443
-rw-r--r--sub/vobsub.h47
22 files changed, 10677 insertions, 1 deletions
diff --git a/sub/ass_mp.c b/sub/ass_mp.c
new file mode 100644
index 0000000000..98602ace03
--- /dev/null
+++ b/sub/ass_mp.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 libass; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+
+#include <ass/ass.h>
+#include <ass/ass_types.h>
+
+#include <libavutil/common.h>
+
+#include "config.h"
+#include "mp_msg.h"
+#include "path.h"
+#include "ass_mp.h"
+#include "subreader.h"
+#include "stream/stream.h"
+
+#ifdef CONFIG_FONTCONFIG
+#include <fontconfig/fontconfig.h>
+#endif
+
+// libass-related command line options
+ASS_Library *ass_library;
+float ass_font_scale = 1.;
+float ass_line_spacing = 0.;
+int ass_top_margin = 0;
+int ass_bottom_margin = 0;
+int use_embedded_fonts = 1;
+char **ass_force_style_list = NULL;
+int ass_use_margins = 0;
+char *ass_color = NULL;
+char *ass_border_color = NULL;
+char *ass_styles_file = NULL;
+int ass_hinting = ASS_HINTING_LIGHT + 4; // light hinting for unscaled osd
+
+#ifdef CONFIG_FONTCONFIG
+extern int font_fontconfig;
+#else
+static int font_fontconfig = -1;
+#endif
+extern char *font_name;
+extern char *sub_font_name;
+extern float text_font_scale_factor;
+extern int subtitle_autoscale;
+
+#ifdef CONFIG_ICONV
+extern char *sub_cp;
+#else
+static char *sub_cp = 0;
+#endif
+
+ASS_Track *mp_ass_default_track(ASS_Library *library)
+{
+ ASS_Track *track = ass_new_track(library);
+
+ track->track_type = TRACK_TYPE_ASS;
+ track->Timer = 100.;
+ track->PlayResY = 288;
+ track->WrapStyle = 0;
+
+ if (ass_styles_file)
+ ass_read_styles(track, ass_styles_file, sub_cp);
+
+ if (track->n_styles == 0) {
+ track->Kerning = true;
+ int sid = ass_alloc_style(track);
+ ASS_Style *style = track->styles + sid;
+ style->Name = strdup("Default");
+ style->FontName = (font_fontconfig >= 0
+ && sub_font_name) ? strdup(sub_font_name)
+ : (font_fontconfig >= 0
+ && font_name) ? strdup(font_name) : strdup("Sans");
+ style->treat_fontname_as_pattern = 1;
+
+ double fs = track->PlayResY * text_font_scale_factor / 100.;
+ /* The font size is always proportional to video height only;
+ * real -subfont-autoscale behavior is not implemented.
+ * Apply a correction that corresponds to about 4:3 aspect ratio
+ * video to get a size somewhat closer to what non-libass rendering
+ * would produce with the same text_font_scale_factor
+ * and subtitle_autoscale.
+ */
+ if (subtitle_autoscale == 2)
+ fs *= 1.3;
+ else if (subtitle_autoscale == 3)
+ fs *= 1.7;
+
+ uint32_t c1 = 0xFFFFFF00;
+ uint32_t c2 = 0x00000000;
+ if (ass_color)
+ c1 = strtoll(ass_color, NULL, 16);
+ if (ass_border_color)
+ c2 = strtoll(ass_border_color, NULL, 16);
+
+ style->FontSize = fs;
+ style->PrimaryColour = c1;
+ style->SecondaryColour = c1;
+ style->OutlineColour = c2;
+ style->BackColour = 0x00000000;
+ style->BorderStyle = 1;
+ style->Alignment = 2;
+ style->Outline = fs / 16;
+ style->MarginL = 10;
+ style->MarginR = 10;
+ style->MarginV = 5;
+ style->ScaleX = 1.;
+ style->ScaleY = 1.;
+ }
+
+ ass_process_force_style(track);
+ return track;
+}
+
+static int check_duplicate_plaintext_event(ASS_Track *track)
+{
+ int i;
+ ASS_Event *evt = track->events + track->n_events - 1;
+
+ for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
+ if (track->events[i].Start == evt->Start &&
+ track->events[i].Duration == evt->Duration &&
+ strcmp(track->events[i].Text, evt->Text) == 0)
+ return 1;
+ return 0;
+}
+
+/**
+ * \brief Convert subtitle to ASS_Events for the given track
+ * \param track track
+ * \param sub subtitle to convert
+ * \return event id
+ * note: assumes that subtitle is _not_ fps-based; caller must manually correct
+ * Start and Duration in other case.
+ **/
+static int ass_process_subtitle(ASS_Track *track, subtitle *sub)
+{
+ int eid;
+ ASS_Event *event;
+ int len = 0, j;
+ char *p;
+ char *end;
+
+ eid = ass_alloc_event(track);
+ event = track->events + eid;
+
+ event->Start = sub->start * 10;
+ event->Duration = (sub->end - sub->start) * 10;
+ event->Style = 0;
+
+ for (j = 0; j < sub->lines; ++j)
+ len += sub->text[j] ? strlen(sub->text[j]) : 0;
+
+ len += 2 * sub->lines; // '\N', including the one after the last line
+ len += 6; // {\anX}
+ len += 1; // '\0'
+
+ event->Text = malloc(len);
+ end = event->Text + len;
+ p = event->Text;
+
+ if (sub->alignment)
+ p += snprintf(p, end - p, "{\\an%d}", sub->alignment);
+
+ for (j = 0; j < sub->lines; ++j)
+ p += snprintf(p, end - p, "%s\\N", sub->text[j]);
+
+ if (sub->lines > 0)
+ p -= 2; // remove last "\N"
+ *p = 0;
+
+ if (check_duplicate_plaintext_event(track)) {
+ ass_free_event(track, eid);
+ track->n_events--;
+ return -1;
+ }
+
+ mp_msg(MSGT_ASS, MSGL_V,
+ "plaintext event at %" PRId64 ", +%" PRId64 ": %s \n",
+ (int64_t) event->Start, (int64_t) event->Duration, event->Text);
+
+ return eid;
+}
+
+
+/**
+ * \brief Convert subdata to ASS_Track
+ * \param subdata subtitles struct from subreader
+ * \param fps video framerate
+ * \return newly allocated ASS_Track, filled with subtitles from subdata
+ */
+ASS_Track *mp_ass_read_subdata(ASS_Library *library, sub_data *subdata,
+ double fps)
+{
+ ASS_Track *track;
+ int i;
+
+ track = mp_ass_default_track(library);
+ track->name = subdata->filename ? strdup(subdata->filename) : 0;
+
+ for (i = 0; i < subdata->sub_num; ++i) {
+ int eid = ass_process_subtitle(track, subdata->subtitles + i);
+ if (eid < 0)
+ continue;
+ if (!subdata->sub_uses_time) {
+ track->events[eid].Start *= 100. / fps;
+ track->events[eid].Duration *= 100. / fps;
+ }
+ }
+ return track;
+}
+
+ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname,
+ char *charset)
+{
+ int i;
+ char *buf = NULL;
+ ASS_Track *track;
+ size_t sz = 0;
+ size_t buf_alloc = 0;
+ stream_t *fd;
+
+ fd = open_stream(fname, NULL, NULL);
+ if (!fd)
+ // Stream code should have printed an error already
+ return NULL;
+ if (fd->end_pos > STREAM_BUFFER_SIZE)
+ /* read entire file if size is known */
+ buf_alloc = fd->end_pos;
+ else
+ buf_alloc = 1000;
+ for (;;) {
+ if (sz > 100000000) {
+ mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file "
+ "larger than 100 MB: %s\n", fname);
+ sz = 0;
+ break;
+ }
+ buf_alloc = FFMAX(buf_alloc, sz + (sz >> 1));
+ buf_alloc = FFMIN(buf_alloc, 100000001);
+ buf = realloc(buf, buf_alloc + 1);
+ i = stream_read(fd, buf + sz, buf_alloc - sz);
+ if (i <= 0)
+ break;
+ sz += i;
+ }
+ free_stream(fd);
+ if (!sz) {
+ free(buf);
+ return NULL;
+ }
+ buf[sz] = 0;
+ buf = realloc(buf, sz + 1);
+ track = ass_read_memory(library, buf, sz, charset);
+ if (track) {
+ free(track->name);
+ track->name = strdup(fname);
+ }
+ free(buf);
+ return track;
+}
+
+void mp_ass_configure(ASS_Renderer *priv, int w, int h, bool unscaled)
+{
+ int hinting;
+ ass_set_frame_size(priv, w, h);
+ ass_set_margins(priv, ass_top_margin, ass_bottom_margin, 0, 0);
+ ass_set_use_margins(priv, ass_use_margins);
+ ass_set_font_scale(priv, ass_font_scale);
+ if (!unscaled && (ass_hinting & 4))
+ hinting = 0;
+ else
+ hinting = ass_hinting & 3;
+ ass_set_hinting(priv, hinting);
+ ass_set_line_spacing(priv, ass_line_spacing);
+}
+
+void mp_ass_configure_fonts(ASS_Renderer *priv)
+{
+ char *dir, *path, *family;
+ dir = get_path("fonts");
+ if (font_fontconfig < 0 && sub_font_name)
+ path = strdup(sub_font_name);
+ else if (font_fontconfig < 0 && font_name)
+ path = strdup(font_name);
+ else
+ path = get_path("subfont.ttf");
+ if (font_fontconfig >= 0 && sub_font_name)
+ family = strdup(sub_font_name);
+ else if (font_fontconfig >= 0 && font_name)
+ family = strdup(font_name);
+ else
+ family = 0;
+
+ ass_set_fonts(priv, path, family, font_fontconfig + 1, NULL, 1);
+
+ free(dir);
+ free(path);
+ free(family);
+}
+
+static void message_callback(int level, const char *format, va_list va, void *ctx)
+{
+ mp_msg(MSGT_ASS, level, "[ass] ");
+ mp_msg_va(MSGT_ASS, level, format, va);
+ // libass messages lack trailing \n
+ mp_msg(MSGT_ASS, level, "\n");
+}
+
+ASS_Library *mp_ass_init(void)
+{
+ ASS_Library *priv;
+ char *path = get_path("fonts");
+ priv = ass_library_init();
+ ass_set_message_cb(priv, message_callback, NULL);
+ ass_set_fonts_dir(priv, path);
+ ass_set_extract_fonts(priv, use_embedded_fonts);
+ ass_set_style_overrides(priv, ass_force_style_list);
+ free(path);
+ return priv;
+}
+
+int ass_force_reload = 0; // flag set if global ass-related settings were changed
+
+ASS_Image *mp_ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
+ long long now, int *detect_change)
+{
+ if (ass_force_reload) {
+ ass_set_margins(priv, ass_top_margin, ass_bottom_margin, 0, 0);
+ ass_set_use_margins(priv, ass_use_margins);
+ ass_set_font_scale(priv, ass_font_scale);
+ ass_force_reload = 0;
+ }
+ return ass_render_frame(priv, track, now, detect_change);
+}
diff --git a/sub/ass_mp.h b/sub/ass_mp.h
new file mode 100644
index 0000000000..965b063403
--- /dev/null
+++ b/sub/ass_mp.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 libass; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_ASS_MP_H
+#define MPLAYER_ASS_MP_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "config.h"
+#include "subreader.h"
+
+#ifdef CONFIG_ASS
+#include <ass/ass.h>
+#include <ass/ass_types.h>
+
+extern ASS_Library *ass_library;
+extern float ass_font_scale;
+extern float ass_line_spacing;
+extern int ass_top_margin;
+extern int ass_bottom_margin;
+extern int use_embedded_fonts;
+extern char **ass_force_style_list;
+extern int ass_use_margins;
+extern char *ass_color;
+extern char *ass_border_color;
+extern char *ass_styles_file;
+extern int ass_hinting;
+
+ASS_Track *mp_ass_default_track(ASS_Library *library);
+ASS_Track *mp_ass_read_subdata(ASS_Library *library, sub_data *subdata,
+ double fps);
+ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname,
+ char *charset);
+
+void mp_ass_configure(ASS_Renderer *priv, int w, int h, bool unscaled);
+void mp_ass_configure_fonts(ASS_Renderer *priv);
+ASS_Library *mp_ass_init(void);
+
+extern int ass_force_reload;
+ASS_Image *mp_ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
+ long long now, int *detect_change);
+
+#else /* CONFIG_ASS */
+
+/* Needed for EOSD code using this type to compile */
+
+typedef struct ass_image {
+ int w, h;
+ int stride;
+ unsigned char *bitmap;
+ uint32_t color;
+ int dst_x, dst_y;
+ struct ass_image *next;
+} ASS_Image;
+
+#endif
+
+typedef struct {
+ ASS_Image *imgs;
+ int changed;
+} mp_eosd_images_t;
+
+
+#endif /* MPLAYER_ASS_MP_H */
diff --git a/sub/av_sub.c b/sub/av_sub.c
new file mode 100644
index 0000000000..a68fbce083
--- /dev/null
+++ b/sub/av_sub.c
@@ -0,0 +1,119 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <libavcodec/avcodec.h>
+
+#include "libmpdemux/stheader.h"
+#include "sub.h"
+#include "spudec.h"
+#include "av_sub.h"
+
+void reset_avsub(struct sh_sub *sh)
+{
+ if (sh->context) {
+ avcodec_close(sh->context);
+ av_freep(&sh->context);
+ }
+}
+
+/**
+ * Decode a subtitle packet via libavcodec.
+ * \return < 0 on error, > 0 if further processing is needed
+ */
+int decode_avsub(struct sh_sub *sh, uint8_t *data, int size,
+ double pts, double duration)
+{
+ AVCodecContext *ctx = sh->context;
+ enum CodecID cid = CODEC_ID_NONE;
+ int res;
+ int got_sub;
+ AVSubtitle sub;
+ AVPacket pkt;
+
+ switch (sh->type) {
+ case 'b':
+ cid = CODEC_ID_DVB_SUBTITLE; break;
+ case 'p':
+ cid = CODEC_ID_HDMV_PGS_SUBTITLE; break;
+ case 'x':
+ cid = CODEC_ID_XSUB; break;
+ }
+
+ av_init_packet(&pkt);
+ pkt.data = data;
+ pkt.size = size;
+ pkt.pts = pts * 1000;
+ if (duration >= 0)
+ pkt.convergence_duration = duration * 1000;
+ if (!ctx) {
+ AVCodec *sub_codec;
+ avcodec_init();
+ avcodec_register_all();
+ ctx = avcodec_alloc_context();
+ sub_codec = avcodec_find_decoder(cid);
+ if (!ctx || !sub_codec || avcodec_open(ctx, sub_codec) < 0) {
+ mp_msg(MSGT_SUBREADER, MSGL_FATAL,
+ "Could not open subtitle decoder\n");
+ av_freep(&ctx);
+ return -1;
+ }
+ sh->context = ctx;
+ }
+ res = avcodec_decode_subtitle2(ctx, &sub, &got_sub, &pkt);
+ if (res < 0)
+ return res;
+ if (pts != MP_NOPTS_VALUE) {
+ if (sub.end_display_time > sub.start_display_time)
+ duration = (sub.end_display_time - sub.start_display_time) / 1000.0;
+ pts += sub.start_display_time / 1000.0;
+ }
+ double endpts = MP_NOPTS_VALUE;
+ if (pts != MP_NOPTS_VALUE && duration >= 0)
+ endpts = pts + duration;
+ if (got_sub && vo_spudec && sub.num_rects == 0)
+ spudec_set_paletted(vo_spudec, NULL, 0, NULL, 0, 0, 0, 0, pts, endpts);
+ if (got_sub && sub.num_rects > 0) {
+ switch (sub.rects[0]->type) {
+ case SUBTITLE_BITMAP:
+ if (!vo_spudec)
+ vo_spudec = spudec_new_scaled(NULL, ctx->width, ctx->height, NULL, 0);
+ spudec_set_paletted(vo_spudec,
+ sub.rects[0]->pict.data[0],
+ sub.rects[0]->pict.linesize[0],
+ sub.rects[0]->pict.data[1],
+ sub.rects[0]->x,
+ sub.rects[0]->y,
+ sub.rects[0]->w,
+ sub.rects[0]->h,
+ pts,
+ endpts);
+ vo_osd_changed(OSDTYPE_SPU);
+ break;
+ default:
+ mp_msg(MSGT_SUBREADER, MSGL_ERR, "sd_avsub: unsupported subtitle "
+ "type from libavcodec\n");
+ res = -1;
+ break;
+ }
+ }
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 82, 0)
+ if (got_sub)
+ avsubtitle_free(&sub);
+#endif
+ return res;
+}
diff --git a/sub/av_sub.h b/sub/av_sub.h
new file mode 100644
index 0000000000..af3edc4d34
--- /dev/null
+++ b/sub/av_sub.h
@@ -0,0 +1,30 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_AV_SUB_H
+#define MPLAYER_AV_SUB_H
+
+#include <stdint.h>
+
+struct sh_sub;
+
+void reset_avsub(struct sh_sub *sh);
+int decode_avsub(struct sh_sub *sh, uint8_t *data, int size,
+ double pts, double endpts);
+
+#endif /* MPLAYER_AV_SUB_H */
diff --git a/sub/find_sub.c b/sub/find_sub.c
new file mode 100644
index 0000000000..97c232b1db
--- /dev/null
+++ b/sub/find_sub.c
@@ -0,0 +1,175 @@
+/*
+ * .SUB
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+
+#include "libvo/video_out.h"
+#include "sub.h"
+#include "subreader.h"
+
+#include "mp_msg.h"
+#include "mpcommon.h"
+#include "mplayer.h"
+
+static int current_sub=0;
+
+//static subtitle* subtitles=NULL;
+static int nosub_range_start=-1;
+static int nosub_range_end=-1;
+static const sub_data *last_sub_data = NULL;
+
+void step_sub(sub_data *subd, float pts, int movement) {
+ subtitle *subs;
+ int key;
+
+ if (subd == NULL) return;
+ subs = subd->subtitles;
+ key = (pts+sub_delay) * (subd->sub_uses_time ? 100 : sub_fps);
+
+ /* Tell the OSD subsystem that the OSD contents will change soon */
+ vo_osd_changed(OSDTYPE_SUBTITLE);
+
+ /* If we are moving forward, don't count the next (current) subtitle
+ * if we haven't displayed it yet. Same when moving other direction.
+ */
+ if (movement > 0 && key < subs[current_sub].start)
+ movement--;
+ if (movement < 0 && key >= subs[current_sub].end)
+ movement++;
+
+ /* Never move beyond first or last subtitle. */
+ if (current_sub+movement < 0)
+ movement = 0-current_sub;
+ if (current_sub+movement >= subd->sub_num)
+ movement = subd->sub_num - current_sub - 1;
+
+ current_sub += movement;
+ sub_delay = subs[current_sub].start / (subd->sub_uses_time ? 100 : sub_fps) - pts;
+}
+
+void find_sub(struct MPContext *mpctx, sub_data* subd,int key){
+ subtitle *subs;
+ subtitle *new_sub = NULL;
+ int i,j;
+
+ if ( !subd || subd->sub_num == 0) return;
+ subs = subd->subtitles;
+
+ if (last_sub_data != subd) {
+ // Sub data changed, reset nosub range.
+ last_sub_data = subd;
+ nosub_range_start = -1;
+ nosub_range_end = -1;
+ }
+
+ if(vo_sub){
+ if(key>=vo_sub->start && key<=vo_sub->end) return; // OK!
+ } else {
+ if(key>nosub_range_start && key<nosub_range_end) return; // OK!
+ }
+ // sub changed!
+
+ /* Tell the OSD subsystem that the OSD contents will change soon */
+ vo_osd_changed(OSDTYPE_SUBTITLE);
+
+ if(key<=0){
+ // no sub here
+ goto update;
+ }
+
+// printf("\r---- sub changed ----\n");
+
+ // check next sub.
+ if(current_sub>=0 && current_sub+1 < subd->sub_num){
+ if(key>subs[current_sub].end && key<subs[current_sub+1].start){
+ // no sub
+ nosub_range_start=subs[current_sub].end;
+ nosub_range_end=subs[current_sub+1].start;
+ goto update;
+ }
+ // next sub?
+ ++current_sub;
+ new_sub=&subs[current_sub];
+ if(key>=new_sub->start && key<=new_sub->end) goto update; // OK!
+ }
+
+// printf("\r---- sub log search... ----\n");
+
+ // use logarithmic search:
+ i=0;
+ j = subd->sub_num - 1;
+// printf("Searching %d in %d..%d\n",key,subs[i].start,subs[j].end);
+ while(j>=i){
+ current_sub=(i+j+1)/2;
+ new_sub=&subs[current_sub];
+ if(key<new_sub->start) j=current_sub-1;
+ else if(key>new_sub->end) i=current_sub+1;
+ else goto update; // found!
+ }
+// if(key>=new_sub->start && key<=new_sub->end) return; // OK!
+
+ // check where are we...
+ if(key<new_sub->start){
+ if(current_sub<=0){
+ // before the first sub
+ nosub_range_start=key-1; // tricky
+ nosub_range_end=new_sub->start;
+// printf("FIRST... key=%d end=%d \n",key,new_sub->start);
+ new_sub=NULL;
+ goto update;
+ }
+ --current_sub;
+ if(key>subs[current_sub].end && key<subs[current_sub+1].start){
+ // no sub
+ nosub_range_start=subs[current_sub].end;
+ nosub_range_end=subs[current_sub+1].start;
+// printf("No sub... 1 \n");
+ new_sub=NULL;
+ goto update;
+ }
+ printf("HEH???? ");
+ } else {
+ if(key<=new_sub->end) printf("JAJJ! "); else
+ if(current_sub+1 >= subd->sub_num){
+ // at the end?
+ nosub_range_start=new_sub->end;
+ nosub_range_end=0x7FFFFFFF; // MAXINT
+// printf("END!?\n");
+ new_sub=NULL;
+ goto update;
+ } else
+ if(key>subs[current_sub].end && key<subs[current_sub+1].start){
+ // no sub
+ nosub_range_start=subs[current_sub].end;
+ nosub_range_end=subs[current_sub+1].start;
+// printf("No sub... 2 \n");
+ new_sub=NULL;
+ goto update;
+ }
+ }
+
+ mp_msg(MSGT_FIXME,MSGL_FIXME,"SUB ERROR: %d ? %d --- %d [%d] \n",key,(int)new_sub->start,(int)new_sub->end,current_sub);
+
+ new_sub=NULL; // no sub here
+update:
+ set_osd_subtitle(mpctx, new_sub);
+}
diff --git a/sub/font_load.c b/sub/font_load.c
new file mode 100644
index 0000000000..e9980b8e41
--- /dev/null
+++ b/sub/font_load.c
@@ -0,0 +1,356 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "font_load.h"
+#include "mp_msg.h"
+
+raw_file* load_raw(char *name,int verbose){
+ int bpp;
+ raw_file* raw=malloc(sizeof(raw_file));
+ unsigned char head[32];
+ FILE *f=fopen(name,"rb");
+ if(!f) goto err_out; // can't open
+ if(fread(head,32,1,f)<1) goto err_out; // too small
+ if(memcmp(head,"mhwanh",6)) goto err_out; // not raw file
+ raw->w=head[8]*256+head[9];
+ raw->h=head[10]*256+head[11];
+ raw->c=head[12]*256+head[13];
+ if(raw->w == 0) // 2 bytes were not enough for the width... read 4 bytes from the end of the header
+ raw->w = ((head[28]*0x100 + head[29])*0x100 + head[30])*0x100 + head[31];
+ if(raw->c>256) goto err_out; // too many colors!?
+ mp_msg(MSGT_OSD, MSGL_DBG2, "RAW: %s %d x %d, %d colors\n",name,raw->w,raw->h,raw->c);
+ if(raw->c){
+ raw->pal=malloc(raw->c*3);
+ fread(raw->pal,3,raw->c,f);
+ bpp=1;
+ } else {
+ raw->pal=NULL;
+ bpp=3;
+ }
+ raw->bmp=malloc(raw->h*raw->w*bpp);
+ fread(raw->bmp,raw->h*raw->w*bpp,1,f);
+ fclose(f);
+ return raw;
+
+err_out:
+ if (f)
+ fclose(f);
+ free(raw);
+ return NULL;
+}
+
+extern int sub_unicode;
+
+font_desc_t* read_font_desc(const char* fname,float factor,int verbose){
+unsigned char sor[1024];
+unsigned char sor2[1024];
+font_desc_t *desc;
+FILE *f = NULL;
+char *dn;
+//struct stat fstate;
+char section[64];
+int i,j;
+int chardb=0;
+int fontdb=-1;
+int version=0;
+int first=1;
+
+desc=malloc(sizeof(font_desc_t));if(!desc) goto fail_out;
+memset(desc,0,sizeof(font_desc_t));
+
+f=fopen(fname,"rt");if(!f){ mp_msg(MSGT_OSD, MSGL_V, "font: can't open file: %s\n",fname); goto fail_out;}
+
+i = strlen (fname) - 9;
+if ((dn = malloc(i+1))){
+ strncpy (dn, fname, i);
+ dn[i]='\0';
+}
+
+desc->fpath = dn; // search in the same dir as fonts.desc
+
+// desc->fpath=get_path("font/");
+// if (stat(desc->fpath, &fstate)!=0) desc->fpath=DATADIR"/font";
+
+
+
+
+// set up some defaults, and erase table
+desc->charspace=2;
+desc->spacewidth=12;
+desc->height=0;
+for(i=0;i<65536;i++) desc->start[i]=desc->width[i]=desc->font[i]=-1;
+
+section[0]=0;
+
+while(fgets(sor,1020,f)){
+ unsigned char* p[8];
+ int pdb=0;
+ unsigned char *s=sor;
+ unsigned char *d=sor2;
+ int ec=' ';
+ int id=0;
+ sor[1020]=0;
+
+ /* skip files that look like: TTF (0x00, 0x01), PFM (0x00, 0x01), PFB
+ * (0x80, 0x01), PCF (0x01, 0x66), fon ("MZ"), gzipped (0x1f, 0x8b) */
+
+ if (first) {
+ if (!sor[0] || sor[1] == 1 || (sor[0] == 'M' && sor[1] == 'Z') || (sor[0] == 0x1f && sor[1] == 0x8b) || (sor[0] == 1 && sor[1] == 0x66)) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "%s doesn't look like a bitmap font description, ignoring.\n", fname);
+ goto fail_out;
+ }
+ first = 0;
+ }
+
+ p[0]=d;++pdb;
+ while(1){
+ int c=*s++;
+ if(c==0 || c==13 || c==10) break;
+ if(!id){
+ if(c==39 || c==34){ id=c;continue;} // idezojel
+ if(c==';' || c=='#') break;
+ if(c==9) c=' ';
+ if(c==' '){
+ if(ec==' ') continue;
+ *d=0; ++d;
+ p[pdb]=d;++pdb;
+ if(pdb>=8) break;
+ continue;
+ }
+ } else {
+ if(id==c){ id=0;continue;} // idezojel
+
+ }
+ *d=c;d++;
+ ec=c;
+ }
+ if(d==sor2) continue; // skip empty lines
+ *d=0;
+
+// printf("params=%d sor=%s\n",pdb,sor);
+// for(i=0;i<pdb;i++) printf(" param %d = '%s'\n",i,p[i]);
+
+ if(pdb==1 && p[0][0]=='['){
+ int len=strlen(p[0]);
+ if(len && len<63 && p[0][len-1]==']'){
+ strcpy(section,p[0]);
+ mp_msg(MSGT_OSD, MSGL_DBG2, "font: Reading section: %s\n",section);
+ if(strcmp(section,"[files]")==0){
+ ++fontdb;
+ if(fontdb>=16){ mp_msg(MSGT_OSD, MSGL_ERR, "font: Too many bitmaps defined.\n");goto fail_out;}
+ }
+ continue;
+ }
+ }
+
+ if(strcmp(section,"[fpath]")==0){
+ if(pdb==1){
+ free (desc->fpath); // release previously allocated memory
+ desc->fpath=strdup(p[0]);
+ continue;
+ }
+ } else
+
+#ifdef __AMIGAOS4__
+#define FONT_PATH_SEP ""
+#else
+//! path seperator for font paths, may not be more than one character
+#define FONT_PATH_SEP "/"
+#endif
+
+ if(strcmp(section,"[files]")==0){
+ char *default_dir=MPLAYER_DATADIR FONT_PATH_SEP "font";
+ if(pdb==2 && strcmp(p[0],"alpha")==0){
+ char *cp;
+ if (!(cp=malloc(strlen(desc->fpath)+strlen(p[1])+2))) goto fail_out;
+
+ snprintf(cp,strlen(desc->fpath)+strlen(p[1])+2,"%s" FONT_PATH_SEP "%s",
+ desc->fpath,p[1]);
+ if(!((desc->pic_a[fontdb]=load_raw(cp,verbose)))){
+ free(cp);
+ if (!(cp=malloc(strlen(default_dir)+strlen(p[1])+2)))
+ goto fail_out;
+ snprintf(cp,strlen(default_dir)+strlen(p[1])+2,"%s" FONT_PATH_SEP "%s",
+ default_dir,p[1]);
+ if (!((desc->pic_a[fontdb]=load_raw(cp,verbose)))){
+ mp_msg(MSGT_OSD, MSGL_ERR, "Can't load font bitmap: %s\n",p[1]);
+ free(cp);
+ goto fail_out;
+ }
+ }
+ free(cp);
+ continue;
+ }
+ if(pdb==2 && strcmp(p[0],"bitmap")==0){
+ char *cp;
+ if (!(cp=malloc(strlen(desc->fpath)+strlen(p[1])+2))) goto fail_out;
+
+ snprintf(cp,strlen(desc->fpath)+strlen(p[1])+2,"%s" FONT_PATH_SEP "%s",
+ desc->fpath,p[1]);
+ if(!((desc->pic_b[fontdb]=load_raw(cp,verbose)))){
+ free(cp);
+ if (!(cp=malloc(strlen(default_dir)+strlen(p[1])+2)))
+ goto fail_out;
+ snprintf(cp,strlen(default_dir)+strlen(p[1])+2,"%s" FONT_PATH_SEP "%s",
+ default_dir,p[1]);
+ if (!((desc->pic_b[fontdb]=load_raw(cp,verbose)))){
+ mp_msg(MSGT_OSD, MSGL_ERR, "Can't load font bitmap: %s\n",p[1]);
+ free(cp);
+ goto fail_out;
+ }
+ }
+ free(cp);
+ continue;
+ }
+ } else
+
+ if(strcmp(section,"[info]")==0){
+ if(pdb==2 && strcmp(p[0],"name")==0){
+ desc->name=strdup(p[1]);
+ continue;
+ }
+ if(pdb==2 && strcmp(p[0],"descversion")==0){
+ version=atoi(p[1]);
+ continue;
+ }
+ if(pdb==2 && strcmp(p[0],"spacewidth")==0){
+ desc->spacewidth=atoi(p[1]);
+ continue;
+ }
+ if(pdb==2 && strcmp(p[0],"charspace")==0){
+ desc->charspace=atoi(p[1]);
+ continue;
+ }
+ if(pdb==2 && strcmp(p[0],"height")==0){
+ desc->height=atoi(p[1]);
+ continue;
+ }
+ } else
+
+ if(strcmp(section,"[characters]")==0){
+ if(pdb==3){
+ int chr=p[0][0];
+ int start=atoi(p[1]);
+ int end=atoi(p[2]);
+ if(sub_unicode && (chr>=0x80)) chr=(chr<<8)+p[0][1];
+ else if(strlen(p[0])!=1) chr=strtol(p[0],NULL,0);
+ if(end<start) {
+ mp_msg(MSGT_OSD, MSGL_WARN, "error in font desc: end<start for char '%c'\n",chr);
+ } else {
+ desc->start[chr]=start;
+ desc->width[chr]=end-start+1;
+ desc->font[chr]=fontdb;
+// printf("char %d '%c' start=%d width=%d\n",chr,chr,desc->start[chr],desc->width[chr]);
+ ++chardb;
+ }
+ continue;
+ }
+ }
+ mp_msg(MSGT_OSD, MSGL_ERR, "Syntax error in font desc: %s",sor);
+ goto fail_out;
+
+}
+fclose(f);
+f = NULL;
+
+ if (first == 1) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "%s is empty or a directory, ignoring.\n", fname);
+ goto fail_out;
+ }
+
+//printf("font: pos of U = %d\n",desc->start[218]);
+
+for(i=0;i<=fontdb;i++){
+ if(!desc->pic_a[i] || !desc->pic_b[i]){
+ mp_msg(MSGT_OSD, MSGL_ERR, "font: Missing bitmap(s) for sub-font #%d\n",i);
+ goto fail_out;
+ }
+ //if(factor!=1.0f)
+ {
+ // re-sample alpha
+ int f=factor*256.0f;
+ int size=desc->pic_a[i]->w*desc->pic_a[i]->h;
+ int j;
+ mp_msg(MSGT_OSD, MSGL_DBG2, "font: resampling alpha by factor %5.3f (%d) ",factor,f);fflush(stdout);
+ for(j=0;j<size;j++){
+ int x=desc->pic_a[i]->bmp[j]; // alpha
+ int y=desc->pic_b[i]->bmp[j]; // bitmap
+
+#ifdef FAST_OSD
+ x=(x<(255-f))?0:1;
+#else
+
+ x=255-((x*f)>>8); // scale
+ //if(x<0) x=0; else if(x>255) x=255;
+ //x^=255; // invert
+
+ if(x+y>255) x=255-y; // to avoid overflows
+
+ //x=0;
+ //x=((x*f*(255-y))>>16);
+ //x=((x*f*(255-y))>>16)+y;
+ //x=(x*f)>>8;if(x<y) x=y;
+
+ if(x<1) x=1; else
+ if(x>=252) x=0;
+#endif
+
+ desc->pic_a[i]->bmp[j]=x;
+// desc->pic_b[i]->bmp[j]=0; // hack
+ }
+ mp_msg(MSGT_OSD, MSGL_DBG2, "DONE!\n");
+ }
+ if(!desc->height) desc->height=desc->pic_a[i]->h;
+}
+
+j='_';if(desc->font[j]<0) j='?';
+for(i=0;i<65536;i++)
+ if(desc->font[i]<0){
+ desc->start[i]=desc->start[j];
+ desc->width[i]=desc->width[j];
+ desc->font[i]=desc->font[j];
+ }
+desc->font[' ']=-1;
+desc->width[' ']=desc->spacewidth;
+
+mp_msg(MSGT_OSD, MSGL_V, "Bitmap font %s loaded successfully! (%d chars)\n",fname,chardb);
+
+return desc;
+
+fail_out:
+ if (f)
+ fclose(f);
+ free(desc->fpath);
+ free(desc->name);
+ free(desc);
+ return NULL;
+}
+
+#ifndef CONFIG_FREETYPE
+void render_one_glyph(font_desc_t *desc, int c) {}
+int kerning(font_desc_t *desc, int prevc, int c) { return 0; }
+#endif
diff --git a/sub/font_load.h b/sub/font_load.h
new file mode 100644
index 0000000000..7efe067aaf
--- /dev/null
+++ b/sub/font_load.h
@@ -0,0 +1,115 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_FONT_LOAD_H
+#define MPLAYER_FONT_LOAD_H
+
+#include "config.h"
+
+#ifdef CONFIG_FREETYPE
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#endif
+
+typedef struct {
+ unsigned char *bmp;
+ unsigned char *pal;
+ int w,h,c;
+#ifdef CONFIG_FREETYPE
+ int charwidth,charheight,pen,baseline,padding;
+ int current_count, current_alloc;
+#endif
+} raw_file;
+
+typedef struct font_desc {
+#ifdef CONFIG_FREETYPE
+ int dynamic;
+#endif
+ char *name;
+ char *fpath;
+ int spacewidth;
+ int charspace;
+ int height;
+// char *fname_a;
+// char *fname_b;
+ raw_file* pic_a[16];
+ raw_file* pic_b[16];
+ short font[65536];
+ int start[65536]; // short is not enough for unicode fonts
+ short width[65536];
+ int freetype;
+
+#ifdef CONFIG_FREETYPE
+ int face_cnt;
+
+ FT_Face faces[16];
+ FT_UInt glyph_index[65536];
+
+ int max_width, max_height;
+
+ struct
+ {
+ int g_r;
+ int o_r;
+ int g_w;
+ int o_w;
+ int o_size;
+ unsigned volume;
+
+ unsigned *g;
+ unsigned *gt2;
+ unsigned *om;
+ unsigned char *omt;
+ unsigned short *tmp;
+ } tables;
+#endif
+
+} font_desc_t;
+
+extern font_desc_t* vo_font;
+
+extern char *subtitle_font_encoding;
+extern float text_font_scale_factor;
+extern float osd_font_scale_factor;
+extern float subtitle_font_radius;
+extern float subtitle_font_thickness;
+extern int subtitle_autoscale;
+
+extern int vo_image_width;
+extern int vo_image_height;
+
+extern int force_load_font;
+
+int init_freetype(void);
+int done_freetype(void);
+
+font_desc_t* read_font_desc_ft(const char* fname,int face_index,int movie_width, int movie_height, float font_scale_factor);
+void free_font_desc(font_desc_t *desc);
+
+void render_one_glyph(font_desc_t *desc, int c);
+int kerning(font_desc_t *desc, int prevc, int c);
+
+void load_font_ft(int width, int height, font_desc_t **desc, const char *name, float font_scale_factor);
+
+void blur(unsigned char *buffer, unsigned short *tmp2, int width, int height,
+ int stride, int *m2, int r, int mwidth);
+
+raw_file* load_raw(char *name,int verbose);
+font_desc_t* read_font_desc(const char* fname,float factor,int verbose);
+
+#endif /* MPLAYER_FONT_LOAD_H */
diff --git a/sub/font_load_ft.c b/sub/font_load_ft.c
new file mode 100644
index 0000000000..41a0f886cb
--- /dev/null
+++ b/sub/font_load_ft.c
@@ -0,0 +1,1176 @@
+/*
+ * Renders antialiased fonts for mplayer using freetype library.
+ * Should work with TrueType, Type1 and any other font supported by libfreetype.
+ *
+ * Artur Zaprzala <zybi@fanthom.irc.pl>
+ *
+ * ported inside MPlayer by Jindrich Makovicka <makovick@gmail.com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#ifdef CONFIG_ICONV
+#include <iconv.h>
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+#ifdef CONFIG_FONTCONFIG
+#include <fontconfig/fontconfig.h>
+#endif
+
+#include "libavutil/common.h"
+#include "mpbswap.h"
+#include "font_load.h"
+#include "mp_msg.h"
+#include "mplayer.h"
+#include "path.h"
+#include "osd_font.h"
+
+#if (FREETYPE_MAJOR > 2) || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 1)
+#define HAVE_FREETYPE21
+#endif
+
+char *subtitle_font_encoding = NULL;
+float text_font_scale_factor = 3.5;
+float osd_font_scale_factor = 4.0;
+float subtitle_font_radius = 2.0;
+float subtitle_font_thickness = 2.0;
+// 0 = no autoscale
+// 1 = video height
+// 2 = video width
+// 3 = diagonal
+int subtitle_autoscale = 3;
+
+int vo_image_width = 0;
+int vo_image_height = 0;
+int force_load_font;
+
+int using_freetype = 0;
+#ifdef CONFIG_FONTCONFIG
+int font_fontconfig = 1;
+#else
+int font_fontconfig = -1;
+#endif
+
+//// constants
+static unsigned int const colors = 256;
+static unsigned int const maxcolor = 255;
+static unsigned const base = 256;
+static unsigned const first_char = 33;
+#define MAX_CHARSET_SIZE 60000
+
+static FT_Library library;
+
+#define OSD_CHARSET_SIZE 15
+
+static const FT_ULong osd_charset[OSD_CHARSET_SIZE] =
+{
+ 0xe001, 0xe002, 0xe003, 0xe004, 0xe005, 0xe006, 0xe007, 0xe008,
+ 0xe009, 0xe00a, 0xe00b, 0xe010, 0xe011, 0xe012, 0xe013
+};
+
+static const FT_ULong osd_charcodes[OSD_CHARSET_SIZE] =
+{
+ 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,
+ 0x09,0x0a,0x0b,0x10,0x11,0x12,0x13
+};
+
+#define f266ToInt(x) (((x)+32)>>6) // round fractional fixed point number to integer
+ // coordinates are in 26.6 pixels (i.e. 1/64th of pixels)
+#define f266CeilToInt(x) (((x)+63)>>6) // ceiling
+#define f266FloorToInt(x) ((x)>>6) // floor
+#define f1616ToInt(x) (((x)+0x8000)>>16) // 16.16
+#define floatTof266(x) ((int)((x)*(1<<6)+0.5))
+
+#define ALIGN(x) (((x)+7)&~7) // 8 byte align
+
+#define WARNING(msg, args...) mp_msg(MSGT_OSD, MSGL_WARN, msg "\n", ## args)
+
+#define DEBUG 0
+
+//static double ttime;
+
+
+static void paste_bitmap(unsigned char *bbuffer, FT_Bitmap *bitmap, int x, int y, int width, int height, int bwidth) {
+ int drow = x+y*width;
+ int srow = 0;
+ int sp, dp, w, h;
+ if (bitmap->pixel_mode==ft_pixel_mode_mono)
+ for (h = bitmap->rows; h>0 && height > 0; --h, height--, drow+=width, srow+=bitmap->pitch)
+ for (w = bwidth, sp=dp=0; w>0; --w, ++dp, ++sp)
+ bbuffer[drow+dp] = (bitmap->buffer[srow+sp/8] & (0x80>>(sp%8))) ? 255:0;
+ else
+ for (h = bitmap->rows; h>0 && height > 0; --h, height--, drow+=width, srow+=bitmap->pitch)
+ for (w = bwidth, sp=dp=0; w>0; --w, ++dp, ++sp)
+ bbuffer[drow+dp] = bitmap->buffer[srow+sp];
+}
+
+
+static int check_font(font_desc_t *desc, float ppem, int padding, int pic_idx,
+ int charset_size, FT_ULong *charset, FT_ULong *charcodes,
+ int unicode) {
+ FT_Error error;
+ FT_Face face = desc->faces[pic_idx];
+ int const load_flags = FT_LOAD_DEFAULT;
+ int ymin = INT_MAX, ymax = INT_MIN;
+ int space_advance = 20;
+ int width, height;
+ unsigned char *bbuffer;
+ int i, uni_charmap = 1;
+
+ error = FT_Select_Charmap(face, ft_encoding_unicode);
+// fprintf(stderr, "select unicode charmap: %d\n", error);
+
+ if (face->charmap==NULL || face->charmap->encoding!=ft_encoding_unicode) {
+ WARNING("Unicode charmap not available for this font. Very bad!");
+ uni_charmap = 0;
+ error = FT_Set_Charmap(face, face->charmaps[0]);
+ if (error) WARNING("No charmaps! Strange.");
+ }
+
+ /* set size */
+ if (FT_IS_SCALABLE(face)) {
+ error = FT_Set_Char_Size(face, 0, floatTof266(ppem), 0, 0);
+ if (error) WARNING("FT_Set_Char_Size failed.");
+ } else {
+ int j = 0;
+ int jppem = face->available_sizes[0].height;
+ /* find closest size */
+ for (i = 0; i<face->num_fixed_sizes; ++i) {
+ if (fabs(face->available_sizes[i].height - ppem) < abs(face->available_sizes[i].height - jppem)) {
+ j = i;
+ jppem = face->available_sizes[i].height;
+ }
+ }
+ WARNING("Selected font is not scalable. Using ppem=%i.", face->available_sizes[j].height);
+ error = FT_Set_Pixel_Sizes(face, face->available_sizes[j].width, face->available_sizes[j].height);
+ if (error) WARNING("FT_Set_Pixel_Sizes failed.");
+ }
+
+ if (FT_IS_FIXED_WIDTH(face))
+ WARNING("Selected font is fixed-width.");
+
+ /* compute space advance */
+ error = FT_Load_Char(face, ' ', load_flags);
+ if (error) WARNING("spacewidth set to default.");
+ else space_advance = f266ToInt(face->glyph->advance.x);
+
+ if (!desc->spacewidth) desc->spacewidth = 2*padding + space_advance;
+ if (!desc->charspace) desc->charspace = -2*padding;
+ if (!desc->height) desc->height = f266ToInt(face->size->metrics.height);
+
+
+ for (i= 0; i<charset_size; ++i) {
+ FT_ULong character, code;
+ FT_UInt glyph_index;
+
+ character = charset[i];
+ code = charcodes[i];
+ desc->font[unicode?character:code] = pic_idx;
+ // get glyph index
+ if (character==0)
+ glyph_index = 0;
+ else {
+ glyph_index = FT_Get_Char_Index(face, uni_charmap ? character:code);
+ if (glyph_index==0) {
+ WARNING("Glyph for char 0x%02lx|U+%04lX|%c not found.", code, character,
+ code<' '||code>255 ? '.':(char)code);
+ desc->font[unicode?character:code] = -1;
+ continue;
+ }
+ }
+ desc->glyph_index[unicode?character:code] = glyph_index;
+ }
+// fprintf(stderr, "font height: %f\n", (double)(face->bbox.yMax-face->bbox.yMin)/(double)face->units_per_EM*ppem);
+// fprintf(stderr, "font width: %f\n", (double)(face->bbox.xMax-face->bbox.xMin)/(double)face->units_per_EM*ppem);
+
+ ymax = (double)(face->bbox.yMax)/(double)face->units_per_EM*ppem+1;
+ ymin = (double)(face->bbox.yMin)/(double)face->units_per_EM*ppem-1;
+
+ width = ppem*(face->bbox.xMax-face->bbox.xMin)/face->units_per_EM+3+2*padding;
+ if (desc->max_width < width) desc->max_width = width;
+ width = ALIGN(width);
+ desc->pic_b[pic_idx]->charwidth = width;
+
+ if (width <= 0) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "Wrong bounding box, width <= 0 !\n");
+ return -1;
+ }
+
+ if (ymax<=ymin) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "Something went wrong. Use the source!\n");
+ return -1;
+ }
+
+ height = ymax - ymin + 2*padding;
+ if (height <= 0) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "Wrong bounding box, height <= 0 !\n");
+ return -1;
+ }
+
+ if (desc->max_height < height) desc->max_height = height;
+ desc->pic_b[pic_idx]->charheight = height;
+
+// fprintf(stderr, "font height2: %d\n", height);
+ desc->pic_b[pic_idx]->baseline = ymax + padding;
+ desc->pic_b[pic_idx]->padding = padding;
+ desc->pic_b[pic_idx]->current_alloc = 0;
+ desc->pic_b[pic_idx]->current_count = 0;
+
+ bbuffer = NULL;
+
+ desc->pic_b[pic_idx]->w = width;
+ desc->pic_b[pic_idx]->h = height;
+ desc->pic_b[pic_idx]->c = colors;
+ desc->pic_b[pic_idx]->bmp = bbuffer;
+ desc->pic_b[pic_idx]->pen = 0;
+ return 0;
+}
+
+// general outline
+static void outline(
+ unsigned char *s,
+ unsigned char *t,
+ int width,
+ int height,
+ int stride,
+ unsigned char *m,
+ int r,
+ int mwidth,
+ int msize) {
+
+ int x, y;
+
+ for (y = 0; y<height; y++) {
+ for (x = 0; x<width; x++) {
+ const int src= s[x];
+ if(src==0) continue;
+ {
+ const int x1=(x<r) ? r-x : 0;
+ const int y1=(y<r) ? r-y : 0;
+ const int x2=(x+r>=width ) ? r+width -x : 2*r+1;
+ const int y2=(y+r>=height) ? r+height-y : 2*r+1;
+ register unsigned char *dstp= t + (y1+y-r)* stride + x-r;
+ //register int *mp = m + y1 *mwidth;
+ register unsigned char *mp= m + msize*src + y1*mwidth;
+ int my;
+
+ for(my= y1; my<y2; my++){
+ register int mx;
+ for(mx= x1; mx<x2; mx++){
+ if(dstp[mx] < mp[mx]) dstp[mx]= mp[mx];
+ }
+ dstp+=stride;
+ mp+=mwidth;
+ }
+ }
+ }
+ s+= stride;
+ }
+}
+
+
+// 1 pixel outline
+static void outline1(
+ unsigned char *s,
+ unsigned char *t,
+ int width,
+ int height,
+ int stride) {
+
+ int x, y;
+ int skip = stride-width;
+
+ for (x = 0; x<width; ++x, ++s, ++t) *t = *s;
+ s += skip;
+ t += skip;
+ for (y = 1; y<height-1; ++y) {
+ *t++ = *s++;
+ for (x = 1; x<width-1; ++x, ++s, ++t) {
+ unsigned v = (
+ s[-1-stride]+
+ s[-1+stride]+
+ s[+1-stride]+
+ s[+1+stride]
+ )/2 + (
+ s[-1]+
+ s[+1]+
+ s[-stride]+
+ s[+stride]+
+ s[0]
+ );
+ *t = v>maxcolor ? maxcolor : v;
+ }
+ *t++ = *s++;
+ s += skip;
+ t += skip;
+ }
+ for (x = 0; x<width; ++x, ++s, ++t) *t = *s;
+}
+
+// "0 pixel outline"
+static void outline0(
+ unsigned char *s,
+ unsigned char *t,
+ int width,
+ int height,
+ int stride) {
+ int y;
+ for (y = 0; y<height; ++y) {
+ memcpy(t, s, width);
+ s += stride;
+ t += stride;
+ }
+}
+
+// gaussian blur
+void blur(
+ unsigned char *buffer,
+ unsigned short *tmp2,
+ int width,
+ int height,
+ int stride,
+ int *m2,
+ int r,
+ int mwidth) {
+
+ int x, y;
+
+ unsigned char *s = buffer;
+ unsigned short *t = tmp2+1;
+ for(y=0; y<height; y++){
+ memset(t-1, 0, (width+1)*sizeof(short));
+
+ for(x=0; x<r; x++){
+ const int src= s[x];
+ if(src){
+ register unsigned short *dstp= t + x-r;
+ int mx;
+ unsigned *m3= m2 + src*mwidth;
+ for(mx=r-x; mx<mwidth; mx++){
+ dstp[mx]+= m3[mx];
+ }
+ }
+ }
+
+ for(; x<width-r; x++){
+ const int src= s[x];
+ if(src){
+ register unsigned short *dstp= t + x-r;
+ int mx;
+ unsigned *m3= m2 + src*mwidth;
+ for(mx=0; mx<mwidth; mx++){
+ dstp[mx]+= m3[mx];
+ }
+ }
+ }
+
+ for(; x<width; x++){
+ const int src= s[x];
+ if(src){
+ register unsigned short *dstp= t + x-r;
+ int mx;
+ const int x2= r+width -x;
+ unsigned *m3= m2 + src*mwidth;
+ for(mx=0; mx<x2; mx++){
+ dstp[mx]+= m3[mx];
+ }
+ }
+ }
+
+ s+= stride;
+ t+= width + 1;
+ }
+
+ t = tmp2;
+ for(x=0; x<width; x++){
+ for(y=0; y<r; y++){
+ unsigned short *srcp= t + y*(width+1) + 1;
+ int src= *srcp;
+ if(src){
+ register unsigned short *dstp= srcp - 1 + width+1;
+ const int src2= (src + 128)>>8;
+ unsigned *m3= m2 + src2*mwidth;
+
+ int mx;
+ *srcp= 128;
+ for(mx=r-1; mx<mwidth; mx++){
+ *dstp += m3[mx];
+ dstp+= width+1;
+ }
+ }
+ }
+ for(; y<height-r; y++){
+ unsigned short *srcp= t + y*(width+1) + 1;
+ int src= *srcp;
+ if(src){
+ register unsigned short *dstp= srcp - 1 - r*(width+1);
+ const int src2= (src + 128)>>8;
+ unsigned *m3= m2 + src2*mwidth;
+
+ int mx;
+ *srcp= 128;
+ for(mx=0; mx<mwidth; mx++){
+ *dstp += m3[mx];
+ dstp+= width+1;
+ }
+ }
+ }
+ for(; y<height; y++){
+ unsigned short *srcp= t + y*(width+1) + 1;
+ int src= *srcp;
+ if(src){
+ const int y2=r+height-y;
+ register unsigned short *dstp= srcp - 1 - r*(width+1);
+ const int src2= (src + 128)>>8;
+ unsigned *m3= m2 + src2*mwidth;
+
+ int mx;
+ *srcp= 128;
+ for(mx=0; mx<y2; mx++){
+ *dstp += m3[mx];
+ dstp+= width+1;
+ }
+ }
+ }
+ t++;
+ }
+
+ t = tmp2;
+ s = buffer;
+ for(y=0; y<height; y++){
+ for(x=0; x<width; x++){
+ s[x]= t[x]>>8;
+ }
+ s+= stride;
+ t+= width + 1;
+ }
+}
+
+static void resample_alpha(unsigned char *abuf, unsigned char *bbuf, int width, int height, int stride, float factor)
+{
+ int f=factor*256.0f;
+ int i,j;
+ for (i = 0; i < height; i++) {
+ unsigned char *a = abuf+i*stride;
+ unsigned char *b = bbuf+i*stride;
+ for(j=0;j<width;j++,a++,b++){
+ int x=*a; // alpha
+ int y=*b; // bitmap
+ x=255-((x*f)>>8); // scale
+ if (x+y>255) x=255-y; // to avoid overflows
+ if (x<1) x=1; else if (x>=252) x=0;
+ *a=x;
+ }
+ }
+}
+
+#define ALLOC_INCR 32
+void render_one_glyph(font_desc_t *desc, int c)
+{
+ FT_GlyphSlot slot;
+ FT_UInt glyph_index;
+ FT_BitmapGlyph glyph;
+ int width, height, stride, maxw, off;
+ unsigned char *abuffer, *bbuffer;
+
+ int const load_flags = FT_LOAD_DEFAULT;
+ int pen_xa;
+ int font = desc->font[c];
+ int error;
+
+// fprintf(stderr, "render_one_glyph %d\n", c);
+
+ if (!desc->dynamic) return;
+ if (desc->width[c] != -1) return;
+ if (desc->font[c] == -1) return;
+
+ glyph_index = desc->glyph_index[c];
+
+ // load glyph
+ error = FT_Load_Glyph(desc->faces[font], glyph_index, load_flags);
+ if (error) {
+ WARNING("FT_Load_Glyph 0x%02x (char 0x%04x) failed.", glyph_index, c);
+ desc->font[c] = -1;
+ return;
+ }
+ slot = desc->faces[font]->glyph;
+
+ // render glyph
+ if (slot->format != ft_glyph_format_bitmap) {
+ error = FT_Render_Glyph(slot, ft_render_mode_normal);
+ if (error) {
+ WARNING("FT_Render_Glyph 0x%04x (char 0x%04x) failed.", glyph_index, c);
+ desc->font[c] = -1;
+ return;
+ }
+ }
+
+ // extract glyph image
+ error = FT_Get_Glyph(slot, (FT_Glyph*)&glyph);
+ if (error) {
+ WARNING("FT_Get_Glyph 0x%04x (char 0x%04x) failed.", glyph_index, c);
+ desc->font[c] = -1;
+ return;
+ }
+
+// fprintf(stderr, "glyph generated\n");
+
+ maxw = desc->pic_b[font]->charwidth;
+
+ if (glyph->bitmap.width > maxw) {
+ fprintf(stderr, "glyph too wide!\n");
+ }
+
+ // allocate new memory, if needed
+// fprintf(stderr, "\n%d %d %d\n", desc->pic_b[font]->charwidth, desc->pic_b[font]->charheight, desc->pic_b[font]->current_alloc);
+ if (desc->pic_b[font]->current_count >= desc->pic_b[font]->current_alloc) {
+ int newsize = desc->pic_b[font]->charwidth*desc->pic_b[font]->charheight*(desc->pic_b[font]->current_alloc+ALLOC_INCR);
+ int increment = desc->pic_b[font]->charwidth*desc->pic_b[font]->charheight*ALLOC_INCR;
+ desc->pic_b[font]->current_alloc += ALLOC_INCR;
+
+// fprintf(stderr, "\nns = %d inc = %d\n", newsize, increment);
+
+ desc->pic_b[font]->bmp = realloc(desc->pic_b[font]->bmp, newsize);
+ desc->pic_a[font]->bmp = realloc(desc->pic_a[font]->bmp, newsize);
+
+ off = desc->pic_b[font]->current_count*desc->pic_b[font]->charwidth*desc->pic_b[font]->charheight;
+ memset(desc->pic_b[font]->bmp+off, 0, increment);
+ memset(desc->pic_a[font]->bmp+off, 0, increment);
+ }
+
+ abuffer = desc->pic_a[font]->bmp;
+ bbuffer = desc->pic_b[font]->bmp;
+
+ off = desc->pic_b[font]->current_count*desc->pic_b[font]->charwidth*desc->pic_b[font]->charheight;
+
+ paste_bitmap(bbuffer+off,
+ &glyph->bitmap,
+ desc->pic_b[font]->padding + glyph->left,
+ desc->pic_b[font]->baseline - glyph->top,
+ desc->pic_b[font]->charwidth, desc->pic_b[font]->charheight,
+ glyph->bitmap.width <= maxw ? glyph->bitmap.width : maxw);
+
+// fprintf(stderr, "glyph pasted\n");
+ FT_Done_Glyph((FT_Glyph)glyph);
+
+ /* advance pen */
+ pen_xa = f266ToInt(slot->advance.x) + 2*desc->pic_b[font]->padding;
+ if (pen_xa > maxw) pen_xa = maxw;
+
+ desc->start[c] = off;
+ width = desc->width[c] = pen_xa;
+ height = desc->pic_b[font]->charheight;
+ stride = desc->pic_b[font]->w;
+
+ if (desc->tables.o_r == 0) {
+ outline0(bbuffer+off, abuffer+off, width, height, stride);
+ } else if (desc->tables.o_r == 1) {
+ outline1(bbuffer+off, abuffer+off, width, height, stride);
+ } else {
+ outline(bbuffer+off, abuffer+off, width, height, stride,
+ desc->tables.omt, desc->tables.o_r, desc->tables.o_w,
+ desc->tables.o_size);
+ }
+// fprintf(stderr, "fg: outline t = %f\n", GetTimer()-t);
+
+ if (desc->tables.g_r) {
+ blur(abuffer+off, desc->tables.tmp, width, height, stride,
+ desc->tables.gt2, desc->tables.g_r,
+ desc->tables.g_w);
+// fprintf(stderr, "fg: blur t = %f\n", GetTimer()-t);
+ }
+
+ resample_alpha(abuffer+off, bbuffer+off, width, height, stride, font_factor);
+
+ desc->pic_b[font]->current_count++;
+}
+
+
+static int prepare_font(font_desc_t *desc, FT_Face face, float ppem, int pic_idx,
+ int charset_size, FT_ULong *charset, FT_ULong *charcodes, int unicode,
+ double thickness, double radius)
+{
+ int i, err;
+ int padding = ceil(radius) + ceil(thickness);
+
+ desc->faces[pic_idx] = face;
+
+ desc->pic_a[pic_idx] = malloc(sizeof(raw_file));
+ if (!desc->pic_a[pic_idx]) return -1;
+ desc->pic_b[pic_idx] = malloc(sizeof(raw_file));
+ if (!desc->pic_b[pic_idx]) return -1;
+
+ desc->pic_a[pic_idx]->bmp = NULL;
+ desc->pic_a[pic_idx]->pal = NULL;
+ desc->pic_b[pic_idx]->bmp = NULL;
+ desc->pic_b[pic_idx]->pal = NULL;
+
+ desc->pic_a[pic_idx]->pal = malloc(sizeof(unsigned char)*256*3);
+ if (!desc->pic_a[pic_idx]->pal) return -1;
+ for (i = 0; i<768; ++i) desc->pic_a[pic_idx]->pal[i] = i/3;
+
+ desc->pic_b[pic_idx]->pal = malloc(sizeof(unsigned char)*256*3);
+ if (!desc->pic_b[pic_idx]->pal) return -1;
+ for (i = 0; i<768; ++i) desc->pic_b[pic_idx]->pal[i] = i/3;
+
+// ttime = GetTimer();
+ err = check_font(desc, ppem, padding, pic_idx, charset_size, charset, charcodes, unicode);
+// ttime=GetTimer()-ttime;
+// printf("render: %7f us\n",ttime);
+ if (err) return -1;
+// fprintf(stderr, "fg: render t = %f\n", GetTimer()-t);
+
+ desc->pic_a[pic_idx]->w = desc->pic_b[pic_idx]->w;
+ desc->pic_a[pic_idx]->h = desc->pic_b[pic_idx]->h;
+ desc->pic_a[pic_idx]->c = colors;
+
+ desc->pic_a[pic_idx]->bmp = NULL;
+
+// fprintf(stderr, "fg: w = %d, h = %d\n", desc->pic_a[pic_idx]->w, desc->pic_a[pic_idx]->h);
+ return 0;
+
+}
+
+static int generate_tables(font_desc_t *desc, double thickness, double radius)
+{
+ int width = desc->max_height;
+ int height = desc->max_width;
+
+ double A = log(1.0/base)/(radius*radius*2);
+ int mx, my, i;
+ double volume_diff, volume_factor = 0;
+ unsigned char *omtp;
+
+ desc->tables.g_r = ceil(radius);
+ desc->tables.o_r = ceil(thickness);
+ desc->tables.g_w = 2*desc->tables.g_r+1;
+ desc->tables.o_w = 2*desc->tables.o_r+1;
+ desc->tables.o_size = desc->tables.o_w * desc->tables.o_w;
+
+// fprintf(stderr, "o_r = %d\n", desc->tables.o_r);
+
+ if (desc->tables.g_r) {
+ desc->tables.g = malloc(desc->tables.g_w * sizeof(unsigned));
+ desc->tables.gt2 = malloc(256 * desc->tables.g_w * sizeof(unsigned));
+ if (desc->tables.g==NULL || desc->tables.gt2==NULL) {
+ return -1;
+ }
+ }
+ desc->tables.om = malloc(desc->tables.o_w*desc->tables.o_w * sizeof(unsigned));
+ desc->tables.omt = malloc(desc->tables.o_size*256);
+
+ omtp = desc->tables.omt;
+ desc->tables.tmp = malloc((width+1)*height*sizeof(short));
+
+ if (desc->tables.om==NULL || desc->tables.omt==NULL || desc->tables.tmp==NULL) {
+ return -1;
+ };
+
+ if (desc->tables.g_r) {
+ // gaussian curve with volume = 256
+ for (volume_diff=10000000; volume_diff>0.0000001; volume_diff*=0.5){
+ volume_factor+= volume_diff;
+ desc->tables.volume=0;
+ for (i = 0; i<desc->tables.g_w; ++i) {
+ desc->tables.g[i] = (unsigned)(exp(A * (i-desc->tables.g_r)*(i-desc->tables.g_r)) * volume_factor + .5);
+ desc->tables.volume+= desc->tables.g[i];
+ }
+ if(desc->tables.volume>256) volume_factor-= volume_diff;
+ }
+ desc->tables.volume=0;
+ for (i = 0; i<desc->tables.g_w; ++i) {
+ desc->tables.g[i] = (unsigned)(exp(A * (i-desc->tables.g_r)*(i-desc->tables.g_r)) * volume_factor + .5);
+ desc->tables.volume+= desc->tables.g[i];
+ }
+
+ // gauss table:
+ for(mx=0;mx<desc->tables.g_w;mx++){
+ for(i=0;i<256;i++){
+ desc->tables.gt2[mx+i*desc->tables.g_w] = i*desc->tables.g[mx];
+ }
+ }
+ }
+
+ /* outline matrix */
+ for (my = 0; my<desc->tables.o_w; ++my) {
+ for (mx = 0; mx<desc->tables.o_w; ++mx) {
+ // antialiased circle would be perfect here, but this one is good enough
+ double d = thickness + 1 - sqrt((mx-desc->tables.o_r)*(mx-desc->tables.o_r)+(my-desc->tables.o_r)*(my-desc->tables.o_r));
+ desc->tables.om[mx+my*desc->tables.o_w] = d>=1 ? base : d<=0 ? 0 : (d*base + .5);
+ }
+ }
+
+ // outline table:
+ for(i=0;i<256;i++){
+ for(mx=0;mx<desc->tables.o_size;mx++) *(omtp++) = (i*desc->tables.om[mx] + (base/2))/base;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_ICONV
+/* decode from 'encoding' to unicode */
+static FT_ULong decode_char(iconv_t *cd, char c) {
+ FT_ULong o;
+ char *inbuf = &c;
+ char *outbuf = (char*)&o;
+ size_t inbytesleft = 1;
+ size_t outbytesleft = sizeof(FT_ULong);
+
+ iconv(*cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
+
+ /* convert unicode BigEndian -> MachineEndian */
+ o = be2me_32(o);
+
+ // if (count==-1) o = 0; // not OK, at least my iconv() returns E2BIG for all
+ if (outbytesleft!=0) o = 0;
+
+ /* we don't want control characters */
+ if (o>=0x7f && o<0xa0) o = 0;
+ return o;
+}
+
+static int prepare_charset(char *charmap, char *encoding, FT_ULong *charset, FT_ULong *charcodes) {
+ FT_ULong i;
+ int count = 0;
+ int charset_size;
+ iconv_t cd;
+
+ // check if ucs-4 is available
+ cd = iconv_open(charmap, charmap);
+ if (cd==(iconv_t)-1) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "iconv doesn't know %s encoding. Use the source!\n", charmap);
+ return -1;
+ }
+
+ iconv_close(cd);
+
+ cd = iconv_open(charmap, encoding);
+ if (cd==(iconv_t)-1) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "Unsupported encoding `%s', use iconv --list to list character sets known on your system.\n", encoding);
+ return -1;
+ }
+
+ charset_size = 256 - first_char;
+ for (i = 0; i<charset_size; ++i) {
+ charcodes[count] = i+first_char;
+ charset[count] = decode_char(&cd, i+first_char);
+ if (charset[count]!=0) ++count;
+ }
+ charcodes[count] = charset[count] = 0; ++count;
+ charset_size = count;
+
+ iconv_close(cd);
+ if (charset_size==0) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "No characters to render!\n");
+ return -1;
+ }
+
+ return charset_size;
+}
+
+static int prepare_charset_unicode(FT_Face face, FT_ULong *charset, FT_ULong *charcodes) {
+#ifdef HAVE_FREETYPE21
+ FT_ULong charcode;
+#else
+ int j;
+#endif
+ FT_UInt gindex;
+ int i;
+
+ if (face->charmap==NULL || face->charmap->encoding!=ft_encoding_unicode) {
+ WARNING("Unicode charmap not available for this font. Very bad!");
+ return -1;
+ }
+#ifdef HAVE_FREETYPE21
+ i = 0;
+ charcode = FT_Get_First_Char( face, &gindex );
+ while (gindex != 0) {
+ if (charcode < 65536 && charcode >= 33) { // sanity check
+ charset[i] = charcode;
+ charcodes[i] = 0;
+ i++;
+ }
+ charcode = FT_Get_Next_Char( face, charcode, &gindex );
+ }
+#else
+ // for FT < 2.1 we have to use brute force enumeration
+ i = 0;
+ for (j = 33; j < 65536; j++) {
+ gindex = FT_Get_Char_Index(face, j);
+ if (gindex > 0) {
+ charset[i] = j;
+ charcodes[i] = 0;
+ i++;
+ }
+ }
+#endif
+ mp_msg(MSGT_OSD, MSGL_V, "Unicode font: %d glyphs.\n", i);
+
+ return i;
+}
+#endif
+
+static font_desc_t* init_font_desc(void)
+{
+ font_desc_t *desc;
+
+ desc = calloc(1, sizeof(*desc));
+ if(!desc) return NULL;
+
+ desc->dynamic = 1;
+
+ /* setup sane defaults */
+ desc->freetype = 1;
+
+ memset(desc->start, 0xff, sizeof(desc->start));
+ memset(desc->width, 0xff, sizeof(desc->width));
+ memset(desc->font, 0xff, sizeof(desc->font));
+
+ return desc;
+}
+
+void free_font_desc(font_desc_t *desc)
+{
+ int i;
+
+ if (!desc) return;
+
+// if (!desc->dynamic) return; // some vo_aa crap, better leaking than crashing
+
+ free(desc->name);
+ free(desc->fpath);
+
+ for(i = 0; i < 16; i++) {
+ if (desc->pic_a[i]) {
+ free(desc->pic_a[i]->bmp);
+ free(desc->pic_a[i]->pal);
+ free(desc->pic_a[i]);
+ }
+ if (desc->pic_b[i]) {
+ free(desc->pic_b[i]->bmp);
+ free(desc->pic_b[i]->pal);
+ free(desc->pic_b[i]);
+ }
+ }
+
+ free(desc->tables.g);
+ free(desc->tables.gt2);
+ free(desc->tables.om);
+ free(desc->tables.omt);
+ free(desc->tables.tmp);
+
+ for(i = 0; i < desc->face_cnt; i++) {
+ FT_Done_Face(desc->faces[i]);
+ }
+
+ free(desc);
+}
+
+static int load_sub_face(const char *name, int face_index, FT_Face *face)
+{
+ int err = -1;
+
+ if (name) err = FT_New_Face(library, name, face_index, face);
+
+ if (err) {
+ char *font_file = get_path("subfont.ttf");
+ err = FT_New_Face(library, font_file, 0, face);
+ free(font_file);
+ if (err) {
+ err = FT_New_Face(library, MPLAYER_DATADIR "/subfont.ttf", 0, face);
+ if (err) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "New_Face failed. Maybe the font path is wrong.\nPlease supply the text font file (~/.mplayer/subfont.ttf).\n");
+ return -1;
+ }
+ }
+ }
+ return err;
+}
+
+static int load_osd_face(FT_Face *face)
+{
+ if ( FT_New_Memory_Face(library, osd_font_pfb, sizeof(osd_font_pfb), 0, face) ) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "New_Memory_Face failed..\n");
+ return -1;
+ }
+ return 0;
+}
+
+int kerning(font_desc_t *desc, int prevc, int c)
+{
+ FT_Vector kern;
+
+ if (!desc->dynamic) return 0;
+ if (prevc < 0 || c < 0) return 0;
+ if (desc->font[prevc] != desc->font[c]) return 0;
+ if (desc->font[prevc] == -1 || desc->font[c] == -1) return 0;
+ FT_Get_Kerning(desc->faces[desc->font[c]],
+ desc->glyph_index[prevc], desc->glyph_index[c],
+ ft_kerning_default, &kern);
+
+// fprintf(stderr, "kern: %c %c %d\n", prevc, c, f266ToInt(kern.x));
+
+ return f266ToInt(kern.x);
+}
+
+font_desc_t* read_font_desc_ft(const char *fname, int face_index, int movie_width, int movie_height, float font_scale_factor)
+{
+ font_desc_t *desc = NULL;
+
+ FT_Face face;
+
+ FT_ULong *my_charset = malloc(MAX_CHARSET_SIZE * sizeof(FT_ULong)); /* characters we want to render; Unicode */
+ FT_ULong *my_charcodes = malloc(MAX_CHARSET_SIZE * sizeof(FT_ULong)); /* character codes in 'encoding' */
+
+ char *charmap = "ucs-4";
+ int err;
+ int charset_size;
+ int i, j;
+ int unicode;
+
+ float movie_size;
+
+ float subtitle_font_ppem;
+ float osd_font_ppem;
+
+ if (my_charset == NULL || my_charcodes == NULL) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "subtitle font: malloc failed.\n");
+ goto err_out;
+ }
+
+ switch (subtitle_autoscale) {
+ case 1:
+ movie_size = movie_height;
+ break;
+ case 2:
+ movie_size = movie_width;
+ break;
+ case 3:
+ movie_size = sqrt(movie_height*movie_height+movie_width*movie_width);
+ break;
+ default:
+ movie_size = 100;
+ break;
+ }
+
+ subtitle_font_ppem = movie_size*font_scale_factor/100.0;
+ osd_font_ppem = movie_size*(font_scale_factor+1)/100.0;
+
+ if (subtitle_font_ppem < 5) subtitle_font_ppem = 5;
+ if (osd_font_ppem < 5) osd_font_ppem = 5;
+
+ if (subtitle_font_ppem > 128) subtitle_font_ppem = 128;
+ if (osd_font_ppem > 128) osd_font_ppem = 128;
+
+ if ((subtitle_font_encoding == NULL)
+ || (strcasecmp(subtitle_font_encoding, "unicode") == 0)) {
+ unicode = 1;
+ } else {
+ unicode = 0;
+ }
+
+ desc = init_font_desc();
+ if(!desc) goto err_out;
+
+// t=GetTimer();
+
+ /* generate the subtitle font */
+ err = load_sub_face(fname, face_index, &face);
+ if (err) {
+ mp_tmsg(MSGT_OSD, MSGL_WARN, "subtitle font: load_sub_face failed.\n");
+ goto gen_osd;
+ }
+ desc->face_cnt++;
+
+#ifdef CONFIG_ICONV
+ if (unicode) {
+ charset_size = prepare_charset_unicode(face, my_charset, my_charcodes);
+ } else {
+ if (subtitle_font_encoding) {
+ charset_size = prepare_charset(charmap, subtitle_font_encoding, my_charset, my_charcodes);
+ } else {
+ charset_size = prepare_charset(charmap, "iso-8859-1", my_charset, my_charcodes);
+ }
+ }
+
+ if (charset_size < 0) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "subtitle font: prepare_charset failed.\n");
+ goto err_out;
+ }
+#else
+ goto err_out;
+#endif
+
+// fprintf(stderr, "fg: prepare t = %f\n", GetTimer()-t);
+
+ err = prepare_font(desc, face, subtitle_font_ppem, desc->face_cnt-1,
+ charset_size, my_charset, my_charcodes, unicode,
+ subtitle_font_thickness, subtitle_font_radius);
+
+ if (err) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "Cannot prepare subtitle font.\n");
+ goto err_out;
+ }
+
+gen_osd:
+
+ /* generate the OSD font */
+ err = load_osd_face(&face);
+ if (err) {
+ goto err_out;
+ }
+ desc->face_cnt++;
+
+ err = prepare_font(desc, face, osd_font_ppem, desc->face_cnt-1,
+ OSD_CHARSET_SIZE, osd_charset, osd_charcodes, 0,
+ subtitle_font_thickness, subtitle_font_radius);
+
+ if (err) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "Cannot prepare OSD font.\n");
+ goto err_out;
+ }
+
+ err = generate_tables(desc, subtitle_font_thickness, subtitle_font_radius);
+
+ if (err) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "Cannot generate tables.\n");
+ goto err_out;
+ }
+
+ // final cleanup
+ desc->font[' ']=-1;
+ desc->width[' ']=desc->spacewidth;
+
+ j = '_';
+ if (desc->font[j] < 0) j = '?';
+ if (desc->font[j] < 0) j = ' ';
+ render_one_glyph(desc, j);
+ for(i = 0; i < 65536; i++) {
+ if (desc->font[i] < 0 && i != ' ') {
+ desc->start[i] = desc->start[j];
+ desc->width[i] = desc->width[j];
+ desc->font[i] = desc->font[j];
+ }
+ }
+ free(my_charset);
+ free(my_charcodes);
+ return desc;
+
+err_out:
+ if (desc)
+ free_font_desc(desc);
+ free(my_charset);
+ free(my_charcodes);
+ return NULL;
+}
+
+int init_freetype(void)
+{
+ int err;
+
+ /* initialize freetype */
+ err = FT_Init_FreeType(&library);
+ if (err) {
+ mp_msg(MSGT_OSD, MSGL_ERR, "Init_FreeType failed.\n");
+ return -1;
+ }
+ mp_msg(MSGT_OSD, MSGL_V, "init_freetype\n");
+ using_freetype = 1;
+ return 0;
+}
+
+int done_freetype(void)
+{
+ int err;
+
+ if (!using_freetype)
+ return 0;
+
+ err = FT_Done_FreeType(library);
+ if (err) {
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "FT_Done_FreeType failed.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void load_font_ft(int width, int height, font_desc_t** fontp, const char *font_name, float font_scale_factor)
+{
+#ifdef CONFIG_FONTCONFIG
+ FcPattern *fc_pattern;
+ FcPattern *fc_pattern2;
+ FcChar8 *s;
+ int face_index;
+ FcBool scalable;
+ FcResult result;
+#endif
+ font_desc_t *vo_font = *fontp;
+ vo_image_width = width;
+ vo_image_height = height;
+
+ // protection against vo_aa font hacks
+ if (vo_font && !vo_font->dynamic) return;
+
+ if (vo_font) free_font_desc(vo_font);
+
+#ifdef CONFIG_FONTCONFIG
+ if (font_fontconfig > 0)
+ {
+ FcInit();
+ fc_pattern = FcNameParse(font_name ? font_name : "sans-serif");
+ FcConfigSubstitute(0, fc_pattern, FcMatchPattern);
+ FcDefaultSubstitute(fc_pattern);
+ fc_pattern2 = fc_pattern;
+ fc_pattern = FcFontMatch(0, fc_pattern, &result);
+ if (fc_pattern) {
+ FcPatternDestroy(fc_pattern2);
+ FcPatternGetBool(fc_pattern, FC_SCALABLE, 0, &scalable);
+ if (scalable != FcTrue) {
+ FcPatternDestroy(fc_pattern);
+ fc_pattern = FcNameParse("sans-serif");
+ FcConfigSubstitute(0, fc_pattern, FcMatchPattern);
+ FcDefaultSubstitute(fc_pattern);
+ fc_pattern2 = fc_pattern;
+ fc_pattern = FcFontMatch(0, fc_pattern, 0);
+ FcPatternDestroy(fc_pattern2);
+ }
+ // s doesn't need to be freed according to fontconfig docs
+ FcPatternGetString(fc_pattern, FC_FILE, 0, &s);
+ FcPatternGetInteger(fc_pattern, FC_INDEX, 0, &face_index);
+ *fontp=read_font_desc_ft(s, face_index, width, height, font_scale_factor);
+ FcPatternDestroy(fc_pattern);
+ return;
+ }
+ mp_tmsg(MSGT_OSD, MSGL_ERR, "Fontconfig failed to select a font. "
+ "Trying without fontconfig...\n");
+ }
+#endif
+ *fontp=read_font_desc_ft(font_name, 0, width, height, font_scale_factor);
+}
diff --git a/sub/osd_font.h b/sub/osd_font.h
new file mode 100644
index 0000000000..6be45bc1fa
--- /dev/null
+++ b/sub/osd_font.h
@@ -0,0 +1,545 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_OSD_FONT_H
+#define MPLAYER_OSD_FONT_H
+
+const unsigned char osd_font_pfb[] = {
+0x80,0x01,0x02,0x17,0x00,0x00,0x25,0x21,0x50,0x53,0x2d,0x41,0x64,0x6f,0x62,0x65,
+0x46,0x6f,0x6e,0x74,0x2d,0x31,0x2e,0x30,0x3a,0x20,0x4f,0x53,0x44,0x20,0x31,0x2e,
+0x30,0x30,0x0a,0x25,0x25,0x43,0x72,0x65,0x61,0x74,0x69,0x6f,0x6e,0x44,0x61,0x74,
+0x65,0x3a,0x20,0x53,0x75,0x6e,0x20,0x4a,0x75,0x6c,0x20,0x32,0x32,0x20,0x31,0x32,
+0x3a,0x33,0x38,0x3a,0x32,0x38,0x20,0x32,0x30,0x30,0x31,0x0a,0x25,0x0a,0x25,0x25,
+0x45,0x6e,0x64,0x43,0x6f,0x6d,0x6d,0x65,0x6e,0x74,0x73,0x0a,0x31,0x32,0x20,0x64,
+0x69,0x63,0x74,0x20,0x62,0x65,0x67,0x69,0x6e,0x0a,0x2f,0x46,0x6f,0x6e,0x74,0x49,
+0x6e,0x66,0x6f,0x20,0x39,0x20,0x64,0x69,0x63,0x74,0x20,0x64,0x75,0x70,0x20,0x62,
+0x65,0x67,0x69,0x6e,0x0a,0x2f,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x28,0x56,
+0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x2e,0x30,0x30,0x29,0x20,0x72,0x65,0x61,
+0x64,0x6f,0x6e,0x6c,0x79,0x20,0x64,0x65,0x66,0x0a,0x2f,0x4e,0x6f,0x74,0x69,0x63,
+0x65,0x20,0x28,0x54,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x67,0x65,0x6e,0x65,0x72,
+0x61,0x74,0x65,0x64,0x20,0x66,0x69,0x6c,0x65,0x2e,0x29,0x20,0x72,0x65,0x61,0x64,
+0x6f,0x6e,0x6c,0x79,0x20,0x64,0x65,0x66,0x0a,0x2f,0x46,0x75,0x6c,0x6c,0x4e,0x61,
+0x6d,0x65,0x20,0x28,0x4f,0x53,0x44,0x29,0x20,0x72,0x65,0x61,0x64,0x6f,0x6e,0x6c,
+0x79,0x20,0x64,0x65,0x66,0x0a,0x2f,0x46,0x61,0x6d,0x69,0x6c,0x79,0x4e,0x61,0x6d,
+0x65,0x20,0x28,0x4f,0x53,0x44,0x29,0x20,0x72,0x65,0x61,0x64,0x6f,0x6e,0x6c,0x79,
+0x20,0x64,0x65,0x66,0x0a,0x2f,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x28,0x52,0x65,
+0x67,0x75,0x6c,0x61,0x72,0x29,0x20,0x72,0x65,0x61,0x64,0x6f,0x6e,0x6c,0x79,0x20,
+0x64,0x65,0x66,0x0a,0x2f,0x49,0x74,0x61,0x6c,0x69,0x63,0x41,0x6e,0x67,0x6c,0x65,
+0x20,0x30,0x2e,0x30,0x30,0x30,0x30,0x30,0x30,0x20,0x64,0x65,0x66,0x0a,0x2f,0x69,
+0x73,0x46,0x69,0x78,0x65,0x64,0x50,0x69,0x74,0x63,0x68,0x20,0x66,0x61,0x6c,0x73,
+0x65,0x20,0x64,0x65,0x66,0x0a,0x2f,0x55,0x6e,0x64,0x65,0x72,0x6c,0x69,0x6e,0x65,
+0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2d,0x31,0x33,0x33,0x20,0x64,0x65,
+0x66,0x0a,0x2f,0x55,0x6e,0x64,0x65,0x72,0x6c,0x69,0x6e,0x65,0x54,0x68,0x69,0x63,
+0x6b,0x6e,0x65,0x73,0x73,0x20,0x34,0x39,0x20,0x64,0x65,0x66,0x0a,0x65,0x6e,0x64,
+0x20,0x72,0x65,0x61,0x64,0x6f,0x6e,0x6c,0x79,0x20,0x64,0x65,0x66,0x0a,0x2f,0x46,
+0x6f,0x6e,0x74,0x4e,0x61,0x6d,0x65,0x20,0x2f,0x4f,0x53,0x44,0x20,0x64,0x65,0x66,
+0x0a,0x2f,0x50,0x61,0x69,0x6e,0x74,0x54,0x79,0x70,0x65,0x20,0x30,0x20,0x64,0x65,
+0x66,0x0a,0x2f,0x53,0x74,0x72,0x6f,0x6b,0x65,0x57,0x69,0x64,0x74,0x68,0x20,0x30,
+0x20,0x64,0x65,0x66,0x0a,0x2f,0x46,0x6f,0x6e,0x74,0x4d,0x61,0x74,0x72,0x69,0x78,
+0x20,0x5b,0x30,0x2e,0x30,0x30,0x31,0x20,0x30,0x20,0x30,0x20,0x30,0x2e,0x30,0x30,
+0x31,0x20,0x30,0x20,0x30,0x5d,0x20,0x64,0x65,0x66,0x0a,0x2f,0x46,0x6f,0x6e,0x74,
+0x42,0x42,0x6f,0x78,0x20,0x7b,0x30,0x20,0x2d,0x31,0x30,0x20,0x31,0x30,0x30,0x30,
+0x20,0x38,0x31,0x30,0x7d,0x20,0x72,0x65,0x61,0x64,0x6f,0x6e,0x6c,0x79,0x20,0x64,
+0x65,0x66,0x0a,0x2f,0x45,0x6e,0x63,0x6f,0x64,0x69,0x6e,0x67,0x20,0x32,0x35,0x36,
+0x20,0x61,0x72,0x72,0x61,0x79,0x0a,0x64,0x75,0x70,0x20,0x30,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x20,
+0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x31,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x32,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x33,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x33,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x34,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,
+0x34,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x20,0x2f,0x75,0x6e,0x69,
+0x45,0x30,0x30,0x35,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x36,0x20,0x2f,
+0x75,0x6e,0x69,0x45,0x30,0x30,0x36,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x37,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x37,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x38,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x38,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x39,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x30,0x20,0x2f,0x75,0x6e,0x69,
+0x45,0x30,0x30,0x41,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x20,
+0x2f,0x75,0x6e,0x69,0x45,0x30,0x30,0x42,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x36,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x31,0x30,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x37,0x20,0x2f,0x75,0x6e,0x69,0x45,0x30,0x31,0x31,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x38,0x20,0x2f,0x75,0x6e,0x69,
+0x45,0x30,0x31,0x32,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x20,
+0x2f,0x75,0x6e,0x69,0x45,0x30,0x31,0x33,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x33,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x36,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x37,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x33,0x30,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x33,0x31,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x33,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x33,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x33,0x34,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x33,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x33,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x33,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x33,0x38,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x33,0x39,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x34,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x34,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x34,0x32,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x34,0x33,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x34,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x34,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x34,0x36,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x34,0x37,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x34,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x34,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x30,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x31,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x35,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x35,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x34,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x35,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x35,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x38,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x35,0x39,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x36,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x36,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x36,0x32,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x36,0x33,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x36,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x36,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x36,0x36,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x36,0x37,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x36,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x36,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x37,0x30,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x37,0x31,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x37,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x37,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x37,0x34,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x37,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x37,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x37,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x37,0x38,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x37,0x39,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x38,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x38,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x38,0x32,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x38,0x33,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x38,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x38,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x38,0x36,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x38,0x37,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x38,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x38,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x30,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x31,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x39,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x39,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x34,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x39,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x39,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x38,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x39,0x39,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x30,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x30,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x30,0x32,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x30,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x30,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x30,0x35,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,
+0x30,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x31,0x30,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x30,0x38,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x30,
+0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x31,0x31,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x31,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x32,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x31,0x31,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x31,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x31,0x38,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x31,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x32,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x32,0x31,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,
+0x32,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x31,0x32,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x32,0x34,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x32,
+0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x31,0x32,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x32,0x37,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x32,0x38,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x31,0x32,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x31,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x33,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x34,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x33,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x33,0x37,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,
+0x33,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x31,0x33,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x30,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,
+0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x31,0x34,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x33,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x34,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x31,0x34,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x37,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x34,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x34,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x35,0x30,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x35,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x35,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x35,0x33,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,
+0x35,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x31,0x35,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x35,0x36,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x35,
+0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x31,0x35,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x35,0x39,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x30,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x31,0x36,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x33,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x36,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x36,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x36,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x36,0x39,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,
+0x37,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x31,0x37,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x37,0x32,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x37,
+0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x31,0x37,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x37,0x35,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x37,0x36,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x31,0x37,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x37,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x37,0x39,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x38,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x38,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x38,0x32,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x38,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x31,0x38,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x38,0x35,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,
+0x38,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x31,0x38,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x38,0x38,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x38,
+0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x31,0x39,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x31,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x32,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x31,0x39,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x35,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x31,0x39,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x31,0x39,0x38,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x31,0x39,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x30,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x30,0x31,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,
+0x30,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x32,0x30,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x30,0x34,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x30,
+0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x32,0x30,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x30,0x37,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x30,0x38,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x32,0x30,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x31,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x31,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x34,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x32,0x31,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x31,0x37,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,
+0x31,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x32,0x31,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x30,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,
+0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x32,0x32,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x33,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x34,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x32,0x32,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x36,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x37,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x32,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x32,0x39,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x33,0x30,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x32,0x33,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x33,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x33,0x33,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,
+0x33,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x32,0x33,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x33,0x36,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x33,
+0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x32,0x33,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x33,0x39,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x30,
+0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,
+0x70,0x20,0x32,0x34,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,
+0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x32,0x20,0x2f,0x2e,0x6e,0x6f,0x74,
+0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x33,0x20,
+0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,
+0x20,0x32,0x34,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,
+0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x35,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,
+0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x36,0x20,0x2f,
+0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,
+0x32,0x34,0x37,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,
+0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x38,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,
+0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x34,0x39,0x20,0x2f,0x2e,
+0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,
+0x35,0x30,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,
+0x64,0x75,0x70,0x20,0x32,0x35,0x31,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,
+0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x35,0x32,0x20,0x2f,0x2e,0x6e,
+0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x35,
+0x33,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x64,
+0x75,0x70,0x20,0x32,0x35,0x34,0x20,0x2f,0x2e,0x6e,0x6f,0x74,0x64,0x65,0x66,0x20,
+0x70,0x75,0x74,0x0a,0x64,0x75,0x70,0x20,0x32,0x35,0x35,0x20,0x2f,0x2e,0x6e,0x6f,
+0x74,0x64,0x65,0x66,0x20,0x70,0x75,0x74,0x0a,0x72,0x65,0x61,0x64,0x6f,0x6e,0x6c,
+0x79,0x20,0x64,0x65,0x66,0x0a,0x63,0x75,0x72,0x72,0x65,0x6e,0x74,0x64,0x69,0x63,
+0x74,0x20,0x65,0x6e,0x64,0x0a,0x63,0x75,0x72,0x72,0x65,0x6e,0x74,0x66,0x69,0x6c,
+0x65,0x20,0x65,0x65,0x78,0x65,0x63,0x0a,0x80,0x02,0x64,0x07,0x00,0x00,0xd9,0xd6,
+0x6f,0x63,0x3b,0x84,0x6a,0x98,0x9b,0x99,0x74,0xb0,0x17,0x9f,0xc6,0xcc,0x44,0x5b,
+0xc2,0xc0,0x31,0x03,0xc6,0x85,0x70,0xa7,0xb3,0x54,0xa4,0xa2,0x80,0xae,0x6f,0xbf,
+0x7f,0x98,0x88,0xe0,0x62,0xac,0xe9,0x21,0x16,0xed,0xa3,0x16,0x4a,0xb4,0x31,0xb6,
+0xaa,0x5d,0x94,0xf1,0xc3,0x04,0x34,0xfb,0xeb,0xfd,0x8d,0xb1,0xf9,0xaf,0xd6,0x14,
+0x0e,0x48,0xa2,0xd1,0xdf,0x43,0xa3,0x52,0xac,0xb2,0xa0,0x05,0xe8,0xd4,0x3c,0xdf,
+0x44,0xb0,0xfb,0x04,0xb4,0x6b,0xf5,0x7c,0xb2,0x5a,0xcf,0x59,0x2b,0xa6,0x84,0xf3,
+0xb8,0x18,0x21,0xbe,0xfe,0xf4,0xfd,0x83,0x30,0x31,0x19,0x63,0x70,0x76,0xcc,0x17,
+0x8d,0xf5,0xbb,0x1b,0x08,0xb2,0x39,0x99,0x74,0x40,0xfc,0xe8,0xa5,0x75,0xb0,0xe8,
+0x06,0x4f,0x42,0xa4,0xed,0x7b,0xd4,0xde,0x19,0xf8,0x85,0x97,0x5a,0x82,0xe9,0x7a,
+0xda,0xf9,0xe9,0x9e,0x70,0x7f,0xd5,0xd6,0x50,0x4e,0x24,0xfe,0xad,0xb3,0xd1,0x83,
+0xb0,0x63,0x04,0xb0,0x43,0x5d,0xcf,0xe8,0xe0,0xf7,0x01,0xb5,0xae,0x80,0xa2,0xab,
+0xf3,0xf9,0x02,0x5e,0xe4,0x71,0x0d,0x72,0x9d,0x9a,0xfd,0x81,0x96,0xc5,0xde,0x19,
+0xf8,0xbc,0x12,0xc0,0x52,0xc0,0xa8,0x55,0x8c,0x7f,0xa0,0x5f,0x84,0x46,0x75,0x46,
+0xfc,0x13,0x5e,0x05,0x11,0xce,0xfc,0xd4,0x96,0xfd,0xf0,0xa9,0xc4,0xbb,0xcf,0x07,
+0x97,0x37,0x93,0xc2,0xab,0x48,0x78,0xa5,0x80,0xc2,0xf6,0xfa,0x62,0x90,0x77,0x74,
+0x29,0x53,0x8e,0xcc,0x7c,0xb2,0x30,0xd2,0x4c,0x74,0xe5,0xd9,0x8c,0x5a,0x06,0x83,
+0x90,0x47,0xd8,0xbb,0x48,0x86,0x93,0xbf,0xdb,0x87,0x79,0xca,0xaf,0xd0,0xb0,0x8b,
+0x3d,0x0e,0x27,0xc0,0xda,0xda,0x4e,0xf9,0xc7,0x77,0x11,0xff,0x10,0xc9,0x6a,0x74,
+0xfb,0xe5,0x0d,0xd2,0x43,0x70,0x44,0xbb,0x41,0xf3,0x15,0x92,0x57,0x90,0xd9,0x47,
+0x6f,0x46,0x8e,0x4a,0x56,0x72,0x4f,0x39,0xee,0xfc,0x19,0x45,0xb2,0x89,0x9a,0xf0,
+0xa7,0x0f,0x14,0x78,0xaf,0xf3,0x87,0x09,0x86,0xd9,0xfa,0x4b,0x3e,0x72,0x96,0xc1,
+0x3b,0x39,0xfa,0x7e,0x7c,0x2e,0xa2,0xd0,0x78,0xa9,0xce,0x4f,0x3d,0x0c,0x71,0x3e,
+0xca,0x52,0xa0,0x3a,0xe9,0x33,0xe8,0x9b,0x41,0xc6,0xec,0x13,0x2d,0x22,0x8a,0x66,
+0x21,0xc1,0x8f,0x98,0xce,0xa5,0xf9,0xe5,0x44,0x76,0xb4,0xc8,0x46,0x8d,0xcd,0x65,
+0xc9,0x61,0xdf,0xa7,0xa9,0xd7,0x58,0x0a,0xc1,0x5b,0xc8,0xd9,0x87,0xda,0xc6,0x1e,
+0x8b,0x64,0xca,0x08,0x75,0x5a,0x86,0xba,0xb2,0x57,0x96,0x23,0xfb,0xf9,0xb2,0x42,
+0xd5,0x13,0x8f,0xaa,0x7e,0x97,0xa2,0xd6,0x0e,0x17,0xcb,0xdf,0x6f,0xdb,0xb0,0xb2,
+0x68,0x8a,0xf6,0x9b,0x6c,0x9f,0x77,0xd7,0x33,0xa9,0x36,0x1f,0x50,0xe1,0xa0,0x1f,
+0xd9,0xb8,0x3b,0xa9,0x1c,0x2b,0x1e,0xa4,0x31,0x24,0x5b,0x48,0x95,0xc1,0x26,0x3f,
+0x62,0x24,0x71,0x2e,0xe6,0xae,0xdd,0xd0,0xa1,0x51,0x83,0x2c,0x3b,0x2d,0x14,0xea,
+0x94,0xda,0x77,0x87,0xe2,0xd2,0x97,0x4d,0x5a,0x28,0x6b,0x64,0x19,0x37,0xc9,0x3f,
+0x90,0x66,0xb8,0x91,0x2f,0x17,0xd6,0xb6,0xa9,0x55,0xc1,0x7b,0x04,0xb0,0x96,0xa0,
+0x34,0x9a,0x14,0xea,0x12,0xf0,0x33,0xc3,0xc2,0x79,0x2a,0x42,0x33,0x8d,0x75,0x17,
+0xf4,0x05,0x79,0x36,0x89,0x22,0x8d,0xdb,0xd6,0x28,0xae,0xaa,0x22,0x0c,0x86,0x60,
+0xe0,0x66,0x5c,0x7a,0x43,0x0b,0x7f,0xd4,0x74,0x59,0x48,0x2b,0xd8,0xb7,0xd1,0x05,
+0xee,0xbe,0x91,0xe4,0x67,0x8d,0x4d,0xd2,0x25,0xe0,0x13,0xd7,0x85,0x53,0x8b,0x9c,
+0xd0,0x5f,0x09,0x09,0xfd,0xff,0xe1,0x59,0x37,0xe4,0x6d,0x9e,0x65,0x34,0x2e,0xfd,
+0x34,0x67,0x2a,0x1d,0x6e,0xc6,0x51,0x4d,0x69,0x55,0x9c,0xf7,0xfc,0xe7,0x4e,0x75,
+0xd5,0x2c,0x01,0xd5,0x70,0x29,0x8b,0xd0,0x6a,0xa5,0x69,0x1c,0x98,0x98,0x81,0xda,
+0xbf,0xc0,0x8a,0xf6,0x30,0x6c,0xf9,0xb0,0x0d,0xf4,0xf9,0x6a,0x35,0x55,0x02,0x2d,
+0x97,0xe6,0xd0,0x15,0xa8,0xbe,0xf5,0x80,0xe0,0x73,0xd5,0xb8,0xae,0xdc,0x5b,0x6a,
+0xa2,0x4d,0x67,0x7d,0x61,0xcd,0xa9,0xf0,0xe0,0x0a,0x63,0x3e,0x03,0x01,0x7d,0xcf,
+0x56,0x91,0x48,0x74,0x65,0x4c,0xdc,0xcd,0xe3,0xfe,0xdf,0xc0,0x82,0x5e,0x25,0xb4,
+0xea,0x04,0xe0,0x6f,0x16,0x5b,0x94,0x5d,0xf7,0x62,0xce,0xc3,0xa8,0xb9,0x9b,0x0f,
+0xd9,0x5c,0x92,0x56,0x59,0x58,0x41,0x50,0x85,0xbb,0x20,0x13,0xcc,0xff,0xa9,0xd0,
+0xa1,0x88,0x91,0x02,0x4e,0x44,0xe6,0x2e,0x36,0xc1,0x8b,0x53,0xb5,0xc7,0x47,0xad,
+0xb1,0x10,0xe7,0xb7,0x1c,0x27,0xbf,0xf0,0x16,0x79,0x39,0xaa,0x4f,0xa4,0x05,0x79,
+0x00,0x7f,0x3f,0xc6,0xfc,0x0f,0xf9,0x6b,0x21,0xbf,0x57,0x9f,0x64,0x10,0xfe,0xcd,
+0xd9,0x80,0x1c,0xab,0xb0,0x21,0x6a,0x84,0x4e,0x8a,0x07,0x93,0xc6,0x76,0x54,0x41,
+0x8e,0x2d,0x2d,0x72,0xc9,0x52,0xa2,0x16,0xdf,0x0d,0x84,0x09,0x43,0x88,0x3e,0x25,
+0x21,0x5a,0xf3,0x28,0xec,0x4d,0x08,0x41,0xce,0x55,0x7e,0xf9,0x34,0x54,0xcc,0xa2,
+0x2d,0xe0,0x95,0x4a,0x78,0xd5,0x8c,0xd4,0xd4,0x1b,0x3c,0x17,0xe0,0xb7,0x2f,0xf5,
+0x8a,0xc7,0x2b,0x71,0x2a,0x42,0x44,0x04,0x58,0xcf,0x03,0x1a,0x43,0x2e,0xf5,0xf0,
+0x68,0xbf,0x90,0x54,0x83,0xc8,0x57,0xed,0xbb,0xbc,0xc0,0x07,0x13,0x7d,0x71,0x17,
+0x37,0x2c,0x3e,0x3c,0xce,0x31,0xdb,0x68,0xac,0xd4,0x8c,0xca,0xd7,0x6e,0x52,0x51,
+0x0a,0x2d,0xa0,0x09,0xbf,0xfa,0x80,0xce,0xd9,0xe8,0x2c,0xfa,0x34,0xce,0x8c,0x85,
+0x95,0xd5,0x7a,0xd3,0x63,0x02,0x49,0x44,0x4e,0xee,0x34,0xa9,0x23,0x56,0x88,0x69,
+0xf1,0xf8,0x6d,0x28,0xc9,0x2d,0xd2,0x63,0x72,0x64,0xe2,0xd7,0xb6,0x6a,0x74,0xb8,
+0xf3,0xda,0xca,0x56,0xa9,0xbd,0xff,0x04,0x03,0xca,0xc2,0x08,0x27,0x54,0x17,0x5c,
+0xa1,0x71,0xb4,0x32,0x70,0x85,0x52,0x0a,0xc0,0x65,0x37,0x97,0x9f,0xb5,0xef,0xe6,
+0x26,0xc5,0xad,0x7b,0x2a,0x2a,0xd9,0x39,0xb2,0x27,0x56,0x43,0xf6,0x4a,0xce,0x84,
+0x3e,0x90,0x97,0xe3,0x62,0xcc,0xe5,0xa6,0x2c,0x4a,0xf3,0x10,0x91,0xf3,0x37,0x98,
+0x61,0x69,0xdf,0x5b,0x11,0x49,0xe0,0x43,0x56,0xed,0xe9,0x03,0x98,0x17,0x6e,0x41,
+0xc9,0x55,0xa8,0x77,0x5e,0x02,0xd5,0x08,0x9f,0x04,0x5e,0x18,0x79,0x8d,0xf9,0xed,
+0xc7,0x46,0x93,0x4f,0xd7,0xb9,0x46,0x19,0xd4,0x48,0xc7,0xf9,0x73,0x61,0xac,0x5b,
+0x3d,0x18,0x52,0x8f,0x73,0xc6,0x77,0xad,0x57,0x3a,0x8a,0xd7,0x3a,0xee,0xae,0xf9,
+0x80,0x47,0xbc,0xdb,0x44,0x49,0x01,0x5d,0x9d,0x3e,0xeb,0x1a,0xb3,0x53,0x37,0xca,
+0x4b,0x42,0xda,0x09,0xd9,0x4c,0xcd,0x85,0xc0,0x94,0x37,0x22,0x5e,0x74,0x73,0xda,
+0x60,0x56,0x4e,0x07,0x0c,0x4d,0x46,0x29,0x66,0x08,0x74,0x80,0xc2,0x38,0xfb,0x76,
+0xd5,0x94,0x03,0xa0,0x41,0x3b,0x66,0xc3,0xfa,0x2c,0x1a,0xdd,0x9e,0x5f,0x17,0x77,
+0xa1,0x0c,0x8c,0x47,0xc4,0x0d,0x51,0x29,0x47,0xb9,0xb9,0xed,0xbb,0xee,0xde,0xb3,
+0xf9,0x33,0xec,0x59,0xda,0x02,0xf9,0xa3,0x6c,0x8c,0x52,0x59,0x8d,0xbc,0x86,0x89,
+0xa0,0x0d,0xd0,0x68,0x6d,0xa0,0xb1,0x54,0x0c,0xbc,0x2e,0xe9,0xa0,0x8d,0x2e,0xad,
+0x37,0xdb,0x95,0x9b,0xb0,0x77,0xb6,0x4f,0x02,0xba,0x0b,0xe2,0xbd,0x7c,0x4b,0xa7,
+0x4d,0xab,0x45,0x96,0x64,0xef,0x2d,0x6b,0x66,0x3e,0x10,0x67,0x79,0x63,0x0a,0x96,
+0x7e,0x76,0x98,0xcf,0x4e,0x13,0xa0,0x3c,0x8e,0xbf,0xf3,0xd7,0x60,0x9e,0xb9,0xd0,
+0xe0,0xbd,0x14,0xfb,0x89,0xaf,0x2d,0xcf,0xd7,0x90,0x22,0xc1,0x77,0x54,0x4a,0x6c,
+0x42,0x5c,0xab,0x68,0x75,0xbd,0xeb,0x05,0x3e,0x5e,0x3b,0xfb,0x1a,0x87,0xc6,0x20,
+0x15,0x3f,0x62,0x03,0xf7,0x7d,0xf6,0x9c,0x1d,0x5f,0x80,0x93,0x13,0xb6,0x76,0xbe,
+0x90,0xbc,0x1d,0x87,0x66,0xa4,0xfe,0xfd,0x11,0x91,0x1f,0x8f,0x1e,0x35,0x9b,0x88,
+0x59,0xf6,0x85,0x8f,0x07,0xe9,0x15,0x45,0x59,0x07,0xf4,0xb5,0x66,0x1a,0xcd,0x19,
+0x12,0xe0,0xe4,0x88,0x5d,0xc9,0x8c,0xa6,0x9b,0x23,0x29,0x19,0x07,0x89,0xe5,0x4a,
+0x83,0xc7,0xcf,0xf5,0x10,0xc5,0x80,0x56,0xde,0x3e,0xa7,0x68,0x8b,0x71,0x15,0x63,
+0x12,0x24,0x12,0xbb,0xc4,0xb0,0xfe,0x65,0x4f,0xe9,0xe9,0x13,0x80,0xbf,0x3a,0x33,
+0xbd,0xba,0x9f,0x70,0x42,0x33,0xc4,0x5d,0x3b,0xeb,0x66,0x99,0x0e,0xff,0x66,0x39,
+0x1e,0xea,0x00,0x13,0x13,0x49,0xa7,0xef,0x7c,0x55,0xb0,0x36,0x9f,0xe3,0x2d,0x7d,
+0x1a,0xb9,0x0e,0x2f,0xff,0x9b,0x03,0xa1,0xaa,0xb6,0x6d,0x7b,0x8d,0xe3,0x0e,0x4b,
+0x2b,0x7c,0x96,0xdb,0xb8,0xf0,0xd3,0xa5,0xbf,0xbb,0x3e,0xda,0x95,0xa2,0xc7,0xd2,
+0x8e,0x8e,0xdc,0xfe,0x77,0x93,0x4b,0x6e,0x1f,0x13,0x21,0x6a,0x60,0x9f,0x8c,0xe1,
+0x05,0x50,0x53,0x58,0x3f,0xdc,0xab,0x20,0x96,0xdb,0xb0,0xb5,0x08,0xdb,0xb9,0xc4,
+0x62,0xce,0x47,0x1b,0x0c,0xcd,0xb6,0x78,0xfb,0x68,0x14,0x42,0xf3,0x75,0x3f,0x4f,
+0x44,0xec,0x75,0x12,0x7b,0x10,0x0e,0x2b,0x39,0x64,0x2c,0xea,0xaf,0xf2,0x11,0x08,
+0xfc,0xca,0x61,0xc1,0xcc,0x8b,0xc7,0xb4,0xc3,0x73,0x7f,0x30,0xc9,0xb8,0x9c,0xa5,
+0xce,0xb6,0xa7,0xc4,0x1c,0xf0,0xe6,0x17,0x5a,0x18,0x7e,0x7f,0xbb,0xc0,0x8f,0x08,
+0x3d,0xf7,0xa4,0x9a,0x99,0xb4,0x7b,0x4d,0x81,0xfc,0xe1,0x36,0x1b,0xb5,0x9a,0xa7,
+0xdc,0xdc,0x6e,0x1c,0xe3,0x4a,0x4e,0x57,0x80,0x86,0xb4,0xfa,0x01,0x94,0x3d,0x92,
+0x49,0xa4,0x0f,0x95,0x00,0x57,0xcf,0xe6,0x74,0xd7,0xaf,0x8a,0x93,0x23,0x83,0x67,
+0x1f,0xcd,0xb9,0xe1,0x3b,0x9f,0x86,0x04,0x3c,0x6b,0x88,0x5f,0x7f,0x87,0x54,0x98,
+0xf7,0x2f,0x0c,0x5c,0x23,0x95,0x11,0xe1,0x01,0x28,0x56,0x5c,0x5e,0x82,0xe2,0x22,
+0x12,0x6b,0x1e,0x90,0x27,0x16,0x80,0x50,0x65,0x28,0xc1,0x67,0xf5,0x36,0xf2,0xfb,
+0xff,0x30,0xf1,0x06,0x28,0x72,0x2e,0xb8,0x64,0xca,0xa6,0xd0,0x14,0xc7,0x50,0xca,
+0xb2,0xa3,0x82,0xda,0xe8,0xb2,0x77,0x60,0xac,0xc9,0x94,0x93,0x8a,0xcf,0xb5,0xfe,
+0x28,0x90,0x96,0x1d,0x5c,0x80,0x1e,0xcb,0xf3,0xdb,0x3c,0x3a,0x03,0xeb,0xc7,0x29,
+0x2c,0xa2,0x55,0x5e,0x66,0x6b,0xd4,0xa7,0x60,0xa7,0xe9,0x56,0xdc,0x1e,0xf2,0x02,
+0x5f,0x49,0xb7,0x29,0x31,0x4f,0x94,0x2b,0xd3,0xd7,0x1a,0x51,0x16,0xe1,0x0b,0x5b,
+0xcf,0xa1,0x98,0x70,0x24,0x50,0x11,0x3b,0x97,0x84,0x9f,0x5c,0xdc,0x81,0xfe,0x64,
+0x64,0xee,0x2a,0xa5,0x91,0xd3,0x14,0xc6,0x6e,0xe6,0x3b,0xf8,0x77,0x2b,0x35,0xa3,
+0x2d,0x6c,0x3b,0xc2,0xd5,0x8e,0x28,0x1a,0x67,0xf0,0x56,0x55,0x11,0xf0,0x78,0x91,
+0x81,0xa9,0xf4,0xd2,0xbb,0x06,0xe4,0x21,0x7f,0x9d,0x33,0x49,0x30,0x22,0x06,0x96,
+0x85,0x31,0xe6,0xd7,0x9b,0xd0,0x27,0x53,0x6c,0xe2,0x0c,0xcf,0x73,0x0d,0x18,0x26,
+0x56,0x21,0xcd,0xf8,0xad,0x1d,0xfb,0x82,0x6b,0x01,0x21,0x45,0x17,0x96,0x11,0x88,
+0x27,0xc1,0x2e,0x11,0xa9,0x23,0x95,0xf2,0xbe,0xaa,0xba,0xc7,0xa0,0xf5,0x42,0x21,
+0xed,0xa0,0x7d,0x02,0xf8,0xbe,0x58,0x23,0x8f,0xdb,0x0d,0x7b,0xdc,0xd8,0x8f,0xe2,
+0xe4,0xd9,0x99,0x26,0x08,0x7b,0x97,0x6b,0x4f,0xfe,0x3f,0x6a,0x49,0xb0,0x8e,0x7b,
+0xe5,0xf9,0x0d,0x8c,0x4e,0x39,0x8c,0x95,0x81,0x80,0xd9,0x3a,0xc2,0x9f,0x65,0x3f,
+0xd9,0x2f,0xa3,0xd7,0x4c,0xa3,0xf6,0x93,0xd7,0x84,0xb0,0xaf,0x2d,0xe2,0xcd,0x04,
+0x56,0x9e,0x63,0x08,0x1b,0x23,0x61,0xad,0xdd,0x17,0xa3,0x94,0x51,0xaa,0x7a,0xd3,
+0xac,0x90,0xc2,0x58,0x61,0x86,0xec,0x0e,0x97,0xfc,0x45,0xb4,0x3b,0xa2,0xdc,0xb2,
+0xdd,0xe2,0x80,0x01,0x08,0x02,0x00,0x00,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0a,
+0x80,0x03,};
+
+#endif /* MPLAYER_OSD_FONT_H */
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index be2740bfa6..ba5710611e 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -25,7 +25,7 @@
#include "mpcommon.h"
#include "libmpdemux/stheader.h"
-#include "libvo/sub.h"
+#include "sub.h"
#include "ass_mp.h"
#include "sd.h"
#include "subassconvert.h"
diff --git a/sub/spudec.c b/sub/spudec.c
new file mode 100644
index 0000000000..f48d47fd2a
--- /dev/null
+++ b/sub/spudec.c
@@ -0,0 +1,1390 @@
+/*
+ * Skeleton of function spudec_process_controll() is from xine sources.
+ * Further works:
+ * LGB,... (yeah, try to improve it and insert your name here! ;-)
+ *
+ * Kim Minh Kaplan
+ * implement fragments reassembly, RLE decoding.
+ * read brightness from the IFO.
+ *
+ * For information on SPU format see <URL:http://sam.zoy.org/doc/dvd/subtitles/>
+ * and <URL:http://members.aol.com/mpucoder/DVD/spu.html>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mp_msg.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include "libvo/video_out.h"
+#include "spudec.h"
+#include "vobsub.h"
+#include "libavutil/avutil.h"
+#include "ffmpeg_files/intreadwrite.h"
+#include "libswscale/swscale.h"
+#include "mpcommon.h"
+
+/* Valid values for spu_aamode:
+ 0: none (fastest, most ugly)
+ 1: approximate
+ 2: full (slowest)
+ 3: bilinear (similiar to vobsub, fast and not too bad)
+ 4: uses swscaler gaussian (this is the only one that looks good)
+ */
+
+int spu_aamode = 3;
+int spu_alignment = -1;
+float spu_gaussvar = 1.0;
+extern int sub_pos;
+
+typedef struct packet_t packet_t;
+struct packet_t {
+ int is_decoded;
+ unsigned char *packet;
+ int data_len;
+ unsigned int palette[4];
+ unsigned int alpha[4];
+ unsigned int control_start; /* index of start of control data */
+ unsigned int current_nibble[2]; /* next data nibble (4 bits) to be
+ processed (for RLE decoding) for
+ even and odd lines */
+ int deinterlace_oddness; /* 0 or 1, index into current_nibble */
+ unsigned int start_col;
+ unsigned int start_row;
+ unsigned int width, height, stride;
+ unsigned int start_pts, end_pts;
+ packet_t *next;
+};
+
+struct palette_crop_cache {
+ int valid;
+ uint32_t palette;
+ int sx, sy, ex, ey;
+ int result;
+};
+
+typedef struct {
+ packet_t *queue_head;
+ packet_t *queue_tail;
+ unsigned int global_palette[16];
+ unsigned int orig_frame_width, orig_frame_height;
+ unsigned char* packet;
+ size_t packet_reserve; /* size of the memory pointed to by packet */
+ unsigned int packet_offset; /* end of the currently assembled fragment */
+ unsigned int packet_size; /* size of the packet once all fragments are assembled */
+ int packet_pts; /* PTS for this packet */
+ unsigned int palette[4];
+ unsigned int alpha[4];
+ unsigned int cuspal[4];
+ unsigned int custom;
+ unsigned int now_pts;
+ unsigned int start_pts, end_pts;
+ unsigned int start_col;
+ unsigned int start_row;
+ unsigned int width, height, stride;
+ size_t image_size; /* Size of the image buffer */
+ unsigned char *image; /* Grayscale value */
+ unsigned char *aimage; /* Alpha value */
+ unsigned int pal_start_col, pal_start_row;
+ unsigned int pal_width, pal_height;
+ unsigned char *pal_image; /* palette entry value */
+ unsigned int scaled_frame_width, scaled_frame_height;
+ unsigned int scaled_start_col, scaled_start_row;
+ unsigned int scaled_width, scaled_height, scaled_stride;
+ size_t scaled_image_size;
+ unsigned char *scaled_image;
+ unsigned char *scaled_aimage;
+ int auto_palette; /* 1 if we lack a palette and must use an heuristic. */
+ int font_start_level; /* Darkest value used for the computed font */
+ struct vo *hw_spu;
+ int spu_changed;
+ unsigned int forced_subs_only; /* flag: 0=display all subtitle, !0 display only forced subtitles */
+ unsigned int is_forced_sub; /* true if current subtitle is a forced subtitle */
+
+ struct palette_crop_cache palette_crop_cache;
+} spudec_handle_t;
+
+static void spudec_queue_packet(spudec_handle_t *this, packet_t *packet)
+{
+ if (this->queue_head == NULL)
+ this->queue_head = packet;
+ else
+ this->queue_tail->next = packet;
+ this->queue_tail = packet;
+}
+
+static packet_t *spudec_dequeue_packet(spudec_handle_t *this)
+{
+ packet_t *retval = this->queue_head;
+
+ this->queue_head = retval->next;
+ if (this->queue_head == NULL)
+ this->queue_tail = NULL;
+
+ return retval;
+}
+
+static void spudec_free_packet(packet_t *packet)
+{
+ free(packet->packet);
+ free(packet);
+}
+
+static inline unsigned int get_be16(const unsigned char *p)
+{
+ return (p[0] << 8) + p[1];
+}
+
+static inline unsigned int get_be24(const unsigned char *p)
+{
+ return (get_be16(p) << 8) + p[2];
+}
+
+static void next_line(packet_t *packet)
+{
+ if (packet->current_nibble[packet->deinterlace_oddness] % 2)
+ packet->current_nibble[packet->deinterlace_oddness]++;
+ packet->deinterlace_oddness = (packet->deinterlace_oddness + 1) % 2;
+}
+
+static inline unsigned char get_nibble(packet_t *packet)
+{
+ unsigned char nib;
+ unsigned int *nibblep = packet->current_nibble + packet->deinterlace_oddness;
+ if (*nibblep / 2 >= packet->control_start) {
+ mp_msg(MSGT_SPUDEC,MSGL_WARN, "SPUdec: ERROR: get_nibble past end of packet\n");
+ return 0;
+ }
+ nib = packet->packet[*nibblep / 2];
+ if (*nibblep % 2)
+ nib &= 0xf;
+ else
+ nib >>= 4;
+ ++*nibblep;
+ return nib;
+}
+
+/* Cut the sub to visible part */
+static inline void spudec_cut_image(spudec_handle_t *this)
+{
+ unsigned int fy, ly;
+ unsigned int first_y, last_y;
+
+ if (this->stride == 0 || this->height == 0) {
+ return;
+ }
+
+ for (fy = 0; fy < this->image_size && !this->aimage[fy]; fy++);
+ for (ly = this->stride * this->height-1; ly && !this->aimage[ly]; ly--);
+ first_y = fy / this->stride;
+ last_y = ly / this->stride;
+ //printf("first_y: %d, last_y: %d\n", first_y, last_y);
+ this->start_row += first_y;
+
+ // Some subtitles trigger this condition
+ if (last_y + 1 > first_y ) {
+ this->height = last_y - first_y +1;
+ } else {
+ this->height = 0;
+ return;
+ }
+
+// printf("new h %d new start %d (sz %d st %d)---\n\n", this->height, this->start_row, this->image_size, this->stride);
+
+ if (first_y > 0) {
+ memmove(this->image, this->image + this->stride * first_y, this->stride * this->height);
+ memmove(this->aimage, this->aimage + this->stride * first_y, this->stride * this->height);
+ }
+}
+
+
+static int spudec_alloc_image(spudec_handle_t *this, int stride, int height)
+{
+ if (this->width > stride) // just a safeguard
+ this->width = stride;
+ this->stride = stride;
+ this->height = height;
+ if (this->image_size < this->stride * this->height) {
+ if (this->image != NULL) {
+ free(this->image);
+ free(this->pal_image);
+ this->image_size = 0;
+ this->pal_width = this->pal_height = 0;
+ }
+ this->image = malloc(2 * this->stride * this->height);
+ if (this->image) {
+ this->image_size = this->stride * this->height;
+ this->aimage = this->image + this->image_size;
+ // use stride here as well to simplify reallocation checks
+ this->pal_image = malloc(this->stride * this->height);
+ }
+ }
+ return this->image != NULL;
+}
+
+/**
+ * \param pal palette in MPlayer-style gray-alpha values, i.e.
+ * alpha == 0 means transparent, 1 fully opaque,
+ * gray value <= 256 - alpha.
+ */
+static void pal2gray_alpha(const uint16_t *pal,
+ const uint8_t *src, int src_stride,
+ uint8_t *dst, uint8_t *dsta,
+ int dst_stride, int w, int h)
+{
+ int x, y;
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ uint16_t pixel = pal[src[x]];
+ *dst++ = pixel;
+ *dsta++ = pixel >> 8;
+ }
+ for (; x < dst_stride; x++)
+ *dsta++ = *dst++ = 0;
+ src += src_stride;
+ }
+}
+
+static int apply_palette_crop(spudec_handle_t *this,
+ unsigned crop_x, unsigned crop_y,
+ unsigned crop_w, unsigned crop_h)
+{
+ int i;
+ uint8_t *src;
+ uint16_t pal[4];
+ unsigned stride = (crop_w + 7) & ~7;
+ if (crop_x > this->pal_width || crop_y > this->pal_height ||
+ crop_w > this->pal_width - crop_x || crop_h > this->pal_width - crop_y ||
+ crop_w > 0x8000 || crop_h > 0x8000 ||
+ stride * crop_h > this->image_size) {
+ return 0;
+ }
+ for (i = 0; i < 4; ++i) {
+ int color;
+ int alpha = this->alpha[i];
+ // extend 4 -> 8 bit
+ alpha |= alpha << 4;
+ if (this->custom && (this->cuspal[i] >> 31) != 0)
+ alpha = 0;
+ color = this->custom ? this->cuspal[i] :
+ this->global_palette[this->palette[i]];
+ color = (color >> 16) & 0xff;
+ // convert to MPlayer-style gray/alpha palette
+ color = FFMIN(color, alpha);
+ pal[i] = (-alpha << 8) | color;
+ }
+ src = this->pal_image + crop_y * this->pal_width + crop_x;
+ pal2gray_alpha(pal, src, this->pal_width,
+ this->image, this->aimage, stride,
+ crop_w, crop_h);
+ this->width = crop_w;
+ this->height = crop_h;
+ this->stride = stride;
+ this->start_col = this->pal_start_col + crop_x;
+ this->start_row = this->pal_start_row + crop_y;
+ spudec_cut_image(this);
+
+ // reset scaled image
+ this->scaled_frame_width = 0;
+ this->scaled_frame_height = 0;
+ this->palette_crop_cache.valid = 0;
+ return 1;
+}
+
+int spudec_apply_palette_crop(void *this, uint32_t palette,
+ int sx, int sy, int ex, int ey)
+{
+ spudec_handle_t *spu = this;
+ struct palette_crop_cache *c = &spu->palette_crop_cache;
+ if (c->valid && c->palette == palette &&
+ c->sx == sx && c->sy == sy && c->ex == ex && c->ey == ey)
+ return c->result;
+ spu->palette[0] = (palette >> 28) & 0xf;
+ spu->palette[1] = (palette >> 24) & 0xf;
+ spu->palette[2] = (palette >> 20) & 0xf;
+ spu->palette[3] = (palette >> 16) & 0xf;
+ spu->alpha[0] = (palette >> 12) & 0xf;
+ spu->alpha[1] = (palette >> 8) & 0xf;
+ spu->alpha[2] = (palette >> 4) & 0xf;
+ spu->alpha[3] = palette & 0xf;
+ spu->spu_changed = 1;
+ c->result = apply_palette_crop(spu,
+ sx - spu->pal_start_col, sy - spu->pal_start_row,
+ ex - sx, ey - sy);
+ c->palette = palette;
+ c->sx = sx; c->sy = sy;
+ c->ex = ex; c->ey = ey;
+ c->valid = 1;
+ return c->result;
+}
+
+static void spudec_process_data(spudec_handle_t *this, packet_t *packet)
+{
+ unsigned int i, x, y;
+ uint8_t *dst;
+
+ if (!spudec_alloc_image(this, packet->stride, packet->height))
+ return;
+
+ this->pal_start_col = packet->start_col;
+ this->pal_start_row = packet->start_row;
+ this->pal_height = packet->height;
+ this->pal_width = packet->width;
+ this->stride = packet->stride;
+ memcpy(this->palette, packet->palette, sizeof(this->palette));
+ memcpy(this->alpha, packet->alpha, sizeof(this->alpha));
+
+ i = packet->current_nibble[1];
+ x = 0;
+ y = 0;
+ dst = this->pal_image;
+ while (packet->current_nibble[0] < i
+ && packet->current_nibble[1] / 2 < packet->control_start
+ && y < this->pal_height) {
+ unsigned int len, color;
+ unsigned int rle = 0;
+ rle = get_nibble(packet);
+ if (rle < 0x04) {
+ if (rle == 0) {
+ rle = (rle << 4) | get_nibble(packet);
+ if (rle < 0x04)
+ rle = (rle << 4) | get_nibble(packet);
+ }
+ rle = (rle << 4) | get_nibble(packet);
+ }
+ color = 3 - (rle & 0x3);
+ len = rle >> 2;
+ x += len;
+ if (len == 0 || x >= this->pal_width) {
+ len += this->pal_width - x;
+ next_line(packet);
+ x = 0;
+ ++y;
+ }
+ memset(dst, color, len);
+ dst += len;
+ }
+ apply_palette_crop(this, 0, 0, this->pal_width, this->pal_height);
+}
+
+
+/*
+ This function tries to create a usable palette.
+ It determines how many non-transparent colors are used, and assigns different
+gray scale values to each color.
+ I tested it with four streams and even got something readable. Half of the
+times I got black characters with white around and half the reverse.
+*/
+static void compute_palette(spudec_handle_t *this, packet_t *packet)
+{
+ int used[16],i,cused,start,step,color;
+
+ memset(used, 0, sizeof(used));
+ for (i=0; i<4; i++)
+ if (packet->alpha[i]) /* !Transparent? */
+ used[packet->palette[i]] = 1;
+ for (cused=0, i=0; i<16; i++)
+ if (used[i]) cused++;
+ if (!cused) return;
+ if (cused == 1) {
+ start = 0x80;
+ step = 0;
+ } else {
+ start = this->font_start_level;
+ step = (0xF0-this->font_start_level)/(cused-1);
+ }
+ memset(used, 0, sizeof(used));
+ for (i=0; i<4; i++) {
+ color = packet->palette[i];
+ if (packet->alpha[i] && !used[color]) { /* not assigned? */
+ used[color] = 1;
+ this->global_palette[color] = start<<16;
+ start += step;
+ }
+ }
+}
+
+static void spudec_process_control(spudec_handle_t *this, int pts100)
+{
+ int a,b,c,d; /* Temporary vars */
+ unsigned int date, type;
+ unsigned int off;
+ unsigned int start_off = 0;
+ unsigned int next_off;
+ unsigned int start_pts = 0;
+ unsigned int end_pts = 0;
+ unsigned int current_nibble[2] = {0, 0};
+ unsigned int control_start;
+ unsigned int display = 0;
+ unsigned int start_col = 0;
+ unsigned int end_col = 0;
+ unsigned int start_row = 0;
+ unsigned int end_row = 0;
+ unsigned int width = 0;
+ unsigned int height = 0;
+ unsigned int stride = 0;
+
+ control_start = get_be16(this->packet + 2);
+ next_off = control_start;
+ while (start_off != next_off) {
+ start_off = next_off;
+ date = get_be16(this->packet + start_off) * 1024;
+ next_off = get_be16(this->packet + start_off + 2);
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2, "date=%d\n", date);
+ off = start_off + 4;
+ for (type = this->packet[off++]; type != 0xff; type = this->packet[off++]) {
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2, "cmd=%d ",type);
+ switch(type) {
+ case 0x00:
+ /* Menu ID, 1 byte */
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Menu ID\n");
+ /* shouldn't a Menu ID type force display start? */
+ start_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date;
+ end_pts = UINT_MAX;
+ display = 1;
+ this->is_forced_sub=~0; // current subtitle is forced
+ break;
+ case 0x01:
+ /* Start display */
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Start display!\n");
+ start_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date;
+ end_pts = UINT_MAX;
+ display = 1;
+ this->is_forced_sub=0;
+ break;
+ case 0x02:
+ /* Stop display */
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Stop display!\n");
+ end_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date;
+ break;
+ case 0x03:
+ /* Palette */
+ this->palette[0] = this->packet[off] >> 4;
+ this->palette[1] = this->packet[off] & 0xf;
+ this->palette[2] = this->packet[off + 1] >> 4;
+ this->palette[3] = this->packet[off + 1] & 0xf;
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Palette %d, %d, %d, %d\n",
+ this->palette[0], this->palette[1], this->palette[2], this->palette[3]);
+ off+=2;
+ break;
+ case 0x04:
+ /* Alpha */
+ a = this->packet[off] >> 4;
+ b = this->packet[off] & 0xf;
+ c = this->packet[off + 1] >> 4;
+ d = this->packet[off + 1] & 0xf;
+ // Note: some DVDs change these values to create a fade-in/fade-out effect
+ // We can not handle this, so just keep the highest value during the display time.
+ if (display) {
+ a = FFMAX(a, this->alpha[0]);
+ b = FFMAX(b, this->alpha[1]);
+ c = FFMAX(c, this->alpha[2]);
+ d = FFMAX(d, this->alpha[3]);
+ }
+ this->alpha[0] = a;
+ this->alpha[1] = b;
+ this->alpha[2] = c;
+ this->alpha[3] = d;
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Alpha %d, %d, %d, %d\n",
+ this->alpha[0], this->alpha[1], this->alpha[2], this->alpha[3]);
+ off+=2;
+ break;
+ case 0x05:
+ /* Co-ords */
+ a = get_be24(this->packet + off);
+ b = get_be24(this->packet + off + 3);
+ start_col = a >> 12;
+ end_col = a & 0xfff;
+ width = (end_col < start_col) ? 0 : end_col - start_col + 1;
+ stride = (width + 7) & ~7; /* Kludge: draw_alpha needs width multiple of 8 */
+ start_row = b >> 12;
+ end_row = b & 0xfff;
+ height = (end_row < start_row) ? 0 : end_row - start_row /* + 1 */;
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Coords col: %d - %d row: %d - %d (%dx%d)\n",
+ start_col, end_col, start_row, end_row,
+ width, height);
+ off+=6;
+ break;
+ case 0x06:
+ /* Graphic lines */
+ current_nibble[0] = 2 * get_be16(this->packet + off);
+ current_nibble[1] = 2 * get_be16(this->packet + off + 2);
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Graphic offset 1: %d offset 2: %d\n",
+ current_nibble[0] / 2, current_nibble[1] / 2);
+ off+=4;
+ break;
+ case 0xff:
+ /* All done, bye-bye */
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Done!\n");
+ return;
+// break;
+ default:
+ mp_msg(MSGT_SPUDEC,MSGL_WARN,"spudec: Error determining control type 0x%02x. Skipping %d bytes.\n",
+ type, next_off - off);
+ goto next_control;
+ }
+ }
+ next_control:
+ if (!display)
+ continue;
+ if (end_pts == UINT_MAX && start_off != next_off) {
+ end_pts = get_be16(this->packet + next_off) * 1024;
+ end_pts = 1 - pts100 >= end_pts ? 0 : pts100 + end_pts - 1;
+ }
+ if (end_pts > 0) {
+ packet_t *packet = calloc(1, sizeof(packet_t));
+ int i;
+ packet->start_pts = start_pts;
+ packet->end_pts = end_pts;
+ packet->current_nibble[0] = current_nibble[0];
+ packet->current_nibble[1] = current_nibble[1];
+ packet->start_row = start_row;
+ packet->start_col = start_col;
+ packet->width = width;
+ packet->height = height;
+ packet->stride = stride;
+ packet->control_start = control_start;
+ for (i=0; i<4; i++) {
+ packet->alpha[i] = this->alpha[i];
+ packet->palette[i] = this->palette[i];
+ }
+ packet->packet = malloc(this->packet_size);
+ memcpy(packet->packet, this->packet, this->packet_size);
+ spudec_queue_packet(this, packet);
+ }
+ }
+}
+
+static void spudec_decode(spudec_handle_t *this, int pts100)
+{
+ if (!this->hw_spu)
+ spudec_process_control(this, pts100);
+ else if (pts100 >= 0) {
+ static vo_mpegpes_t packet = { NULL, 0, 0x20, 0 };
+ static vo_mpegpes_t *pkg=&packet;
+ packet.data = this->packet;
+ packet.size = this->packet_size;
+ packet.timestamp = pts100;
+ vo_draw_frame(this->hw_spu, (uint8_t**)&pkg);
+ }
+}
+
+int spudec_changed(void * this)
+{
+ spudec_handle_t * spu = this;
+ return spu->spu_changed || spu->now_pts > spu->end_pts;
+}
+
+void spudec_assemble(void *this, unsigned char *packet, unsigned int len, int pts100)
+{
+ spudec_handle_t *spu = this;
+// spudec_heartbeat(this, pts100);
+ if (len < 2) {
+ mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: packet too short\n");
+ return;
+ }
+ spu->packet_pts = pts100;
+ if (spu->packet_offset == 0) {
+ unsigned int len2 = get_be16(packet);
+ // Start new fragment
+ if (spu->packet_reserve < len2) {
+ free(spu->packet);
+ spu->packet = malloc(len2);
+ spu->packet_reserve = spu->packet != NULL ? len2 : 0;
+ }
+ if (spu->packet != NULL) {
+ spu->packet_size = len2;
+ if (len > len2) {
+ mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid frag len / len2: %d / %d \n", len, len2);
+ return;
+ }
+ memcpy(spu->packet, packet, len);
+ spu->packet_offset = len;
+ spu->packet_pts = pts100;
+ }
+ } else {
+ // Continue current fragment
+ if (spu->packet_size < spu->packet_offset + len){
+ mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid fragment\n");
+ spu->packet_size = spu->packet_offset = 0;
+ return;
+ } else {
+ memcpy(spu->packet + spu->packet_offset, packet, len);
+ spu->packet_offset += len;
+ }
+ }
+#if 1
+ // check if we have a complete packet (unfortunatelly packet_size is bad
+ // for some disks)
+ // [cb] packet_size is padded to be even -> may be one byte too long
+ if ((spu->packet_offset == spu->packet_size) ||
+ ((spu->packet_offset + 1) == spu->packet_size)){
+ unsigned int x=0,y;
+ while(x+4<=spu->packet_offset){
+ y=get_be16(spu->packet+x+2); // next control pointer
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUtest: x=%d y=%d off=%d size=%d\n",x,y,spu->packet_offset,spu->packet_size);
+ if(x>=4 && x==y){ // if it points to self - we're done!
+ // we got it!
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUgot: off=%d size=%d \n",spu->packet_offset,spu->packet_size);
+ spudec_decode(spu, pts100);
+ spu->packet_offset = 0;
+ break;
+ }
+ if(y<=x || y>=spu->packet_size){ // invalid?
+ mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUtest: broken packet!!!!! y=%d < x=%d\n",y,x);
+ spu->packet_size = spu->packet_offset = 0;
+ break;
+ }
+ x=y;
+ }
+ // [cb] packet is done; start new packet
+ spu->packet_offset = 0;
+ }
+#else
+ if (spu->packet_offset == spu->packet_size) {
+ spudec_decode(spu, pts100);
+ spu->packet_offset = 0;
+ }
+#endif
+}
+
+void spudec_reset(void *this) // called after seek
+{
+ spudec_handle_t *spu = this;
+ while (spu->queue_head)
+ spudec_free_packet(spudec_dequeue_packet(spu));
+ spu->now_pts = 0;
+ spu->end_pts = 0;
+ spu->packet_size = spu->packet_offset = 0;
+}
+
+void spudec_heartbeat(void *this, unsigned int pts100)
+{
+ spudec_handle_t *spu = this;
+ spu->now_pts = pts100;
+
+ // TODO: detect and handle broken timestamps (e.g. due to wrapping)
+ while (spu->queue_head != NULL && pts100 >= spu->queue_head->start_pts) {
+ packet_t *packet = spudec_dequeue_packet(spu);
+ spu->start_pts = packet->start_pts;
+ spu->end_pts = packet->end_pts;
+ if (packet->is_decoded) {
+ free(spu->image);
+ spu->image_size = packet->data_len;
+ spu->image = packet->packet;
+ spu->aimage = packet->packet + packet->stride * packet->height;
+ packet->packet = NULL;
+ spu->width = packet->width;
+ spu->height = packet->height;
+ spu->stride = packet->stride;
+ spu->start_col = packet->start_col;
+ spu->start_row = packet->start_row;
+
+ // reset scaled image
+ spu->scaled_frame_width = 0;
+ spu->scaled_frame_height = 0;
+ } else {
+ if (spu->auto_palette)
+ compute_palette(spu, packet);
+ spudec_process_data(spu, packet);
+ }
+ spudec_free_packet(packet);
+ spu->spu_changed = 1;
+ }
+}
+
+int spudec_visible(void *this){
+ spudec_handle_t *spu = this;
+ int ret=(spu->start_pts <= spu->now_pts &&
+ spu->now_pts < spu->end_pts &&
+ spu->height > 0);
+// printf("spu visible: %d \n",ret);
+ return ret;
+}
+
+void spudec_set_forced_subs_only(void * const this, const unsigned int flag)
+{
+ if(this){
+ ((spudec_handle_t *)this)->forced_subs_only=flag;
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPU: Display only forced subs now %s\n", flag ? "enabled": "disabled");
+ }
+}
+
+void spudec_draw(void *this, void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx)
+{
+ spudec_handle_t *spu = this;
+ if (spudec_visible(spu))
+ {
+ draw_alpha(ctx, spu->start_col, spu->start_row, spu->width, spu->height,
+ spu->image, spu->aimage, spu->stride);
+ spu->spu_changed = 0;
+ }
+}
+
+/* calc the bbox for spudec subs */
+void spudec_calc_bbox(void *me, unsigned int dxs, unsigned int dys, unsigned int* bbox)
+{
+ spudec_handle_t *spu = me;
+ if (spu->orig_frame_width == 0 || spu->orig_frame_height == 0
+ || (spu->orig_frame_width == dxs && spu->orig_frame_height == dys)) {
+ // unscaled
+ bbox[0] = spu->start_col;
+ bbox[1] = spu->start_col + spu->width;
+ bbox[2] = spu->start_row;
+ bbox[3] = spu->start_row + spu->height;
+ }
+ else {
+ // scaled
+ unsigned int scalex = 0x100 * dxs / spu->orig_frame_width;
+ unsigned int scaley = 0x100 * dys / spu->orig_frame_height;
+ bbox[0] = spu->start_col * scalex / 0x100;
+ bbox[1] = spu->start_col * scalex / 0x100 + spu->width * scalex / 0x100;
+ switch (spu_alignment) {
+ case 0:
+ bbox[3] = dys*sub_pos/100 + spu->height * scaley / 0x100;
+ if (bbox[3] > dys) bbox[3] = dys;
+ bbox[2] = bbox[3] - spu->height * scaley / 0x100;
+ break;
+ case 1:
+ if (sub_pos < 50) {
+ bbox[2] = dys*sub_pos/100 - spu->height * scaley / 0x200;
+ bbox[3] = bbox[2] + spu->height;
+ } else {
+ bbox[3] = dys*sub_pos/100 + spu->height * scaley / 0x200;
+ if (bbox[3] > dys) bbox[3] = dys;
+ bbox[2] = bbox[3] - spu->height * scaley / 0x100;
+ }
+ break;
+ case 2:
+ bbox[2] = dys*sub_pos/100 - spu->height * scaley / 0x100;
+ bbox[3] = bbox[2] + spu->height;
+ break;
+ default: /* -1 */
+ bbox[2] = spu->start_row * scaley / 0x100;
+ bbox[3] = spu->start_row * scaley / 0x100 + spu->height * scaley / 0x100;
+ break;
+ }
+ }
+}
+/* transform mplayer's alpha value into an opacity value that is linear */
+static inline int canon_alpha(int alpha)
+{
+ return (uint8_t)-alpha;
+}
+
+typedef struct {
+ unsigned position;
+ unsigned left_up;
+ unsigned right_down;
+}scale_pixel;
+
+
+static void scale_table(unsigned int start_src, unsigned int start_tar, unsigned int end_src, unsigned int end_tar, scale_pixel * table)
+{
+ unsigned int t;
+ unsigned int delta_src = end_src - start_src;
+ unsigned int delta_tar = end_tar - start_tar;
+ int src = 0;
+ int src_step;
+ if (delta_src == 0 || delta_tar == 0) {
+ return;
+ }
+ src_step = (delta_src << 16) / delta_tar >>1;
+ for (t = 0; t<=delta_tar; src += (src_step << 1), t++){
+ table[t].position= FFMIN(src >> 16, end_src - 1);
+ table[t].right_down = src & 0xffff;
+ table[t].left_up = 0x10000 - table[t].right_down;
+ }
+}
+
+/* bilinear scale, similar to vobsub's code */
+static void scale_image(int x, int y, scale_pixel* table_x, scale_pixel* table_y, spudec_handle_t * spu)
+{
+ int alpha[4];
+ int color[4];
+ unsigned int scale[4];
+ int base = table_y[y].position * spu->stride + table_x[x].position;
+ int scaled = y * spu->scaled_stride + x;
+ alpha[0] = canon_alpha(spu->aimage[base]);
+ alpha[1] = canon_alpha(spu->aimage[base + 1]);
+ alpha[2] = canon_alpha(spu->aimage[base + spu->stride]);
+ alpha[3] = canon_alpha(spu->aimage[base + spu->stride + 1]);
+ color[0] = spu->image[base];
+ color[1] = spu->image[base + 1];
+ color[2] = spu->image[base + spu->stride];
+ color[3] = spu->image[base + spu->stride + 1];
+ scale[0] = (table_x[x].left_up * table_y[y].left_up >> 16) * alpha[0];
+ if (table_y[y].left_up == 0x10000) // necessary to avoid overflow-case
+ scale[0] = table_x[x].left_up * alpha[0];
+ scale[1] = (table_x[x].right_down * table_y[y].left_up >>16) * alpha[1];
+ scale[2] = (table_x[x].left_up * table_y[y].right_down >> 16) * alpha[2];
+ scale[3] = (table_x[x].right_down * table_y[y].right_down >> 16) * alpha[3];
+ spu->scaled_image[scaled] = (color[0] * scale[0] + color[1] * scale[1] + color[2] * scale[2] + color[3] * scale[3])>>24;
+ spu->scaled_aimage[scaled] = (scale[0] + scale[1] + scale[2] + scale[3]) >> 16;
+ if (spu->scaled_aimage[scaled]){
+ // ensure that MPlayer's simplified alpha-blending can not overflow
+ spu->scaled_image[scaled] = FFMIN(spu->scaled_image[scaled], spu->scaled_aimage[scaled]);
+ // convert to MPlayer-style alpha
+ spu->scaled_aimage[scaled] = -spu->scaled_aimage[scaled];
+ }
+}
+
+static void sws_spu_image(unsigned char *d1, unsigned char *d2, int dw, int dh,
+ int ds, const unsigned char* s1, unsigned char* s2,
+ int sw, int sh, int ss)
+{
+ struct SwsContext *ctx;
+ static SwsFilter filter;
+ static int firsttime = 1;
+ static float oldvar;
+ int i;
+
+ if (!firsttime && oldvar != spu_gaussvar) sws_freeVec(filter.lumH);
+ if (firsttime) {
+ filter.lumH = filter.lumV =
+ filter.chrH = filter.chrV = sws_getGaussianVec(spu_gaussvar, 3.0);
+ sws_normalizeVec(filter.lumH, 1.0);
+ firsttime = 0;
+ oldvar = spu_gaussvar;
+ }
+
+ ctx=sws_getContext(sw, sh, PIX_FMT_GRAY8, dw, dh, PIX_FMT_GRAY8, SWS_GAUSS, &filter, NULL, NULL);
+ sws_scale(ctx,&s1,&ss,0,sh,&d1,&ds);
+ for (i=ss*sh-1; i>=0; i--) if (!s2[i]) s2[i] = 255; //else s2[i] = 1;
+ sws_scale(ctx,&s2,&ss,0,sh,&d2,&ds);
+ for (i=ds*dh-1; i>=0; i--) if (d2[i]==0) d2[i] = 1; else if (d2[i]==255) d2[i] = 0;
+ sws_freeContext(ctx);
+}
+
+void spudec_draw_scaled(void *me, unsigned int dxs, unsigned int dys, void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx)
+{
+ spudec_handle_t *spu = me;
+ scale_pixel *table_x;
+ scale_pixel *table_y;
+
+ if (spudec_visible(spu)) {
+
+ // check if only forced subtitles are requested
+ if( (spu->forced_subs_only) && !(spu->is_forced_sub) ){
+ return;
+ }
+
+ if (!(spu_aamode&16) && (spu->orig_frame_width == 0 || spu->orig_frame_height == 0
+ || (spu->orig_frame_width == dxs && spu->orig_frame_height == dys))) {
+ spudec_draw(spu, draw_alpha, ctx);
+ }
+ else {
+ if (spu->scaled_frame_width != dxs || spu->scaled_frame_height != dys) { /* Resizing is needed */
+ /* scaled_x = scalex * x / 0x100
+ scaled_y = scaley * y / 0x100
+ order of operations is important because of rounding. */
+ unsigned int scalex = 0x100 * dxs / spu->orig_frame_width;
+ unsigned int scaley = 0x100 * dys / spu->orig_frame_height;
+ spu->scaled_start_col = spu->start_col * scalex / 0x100;
+ spu->scaled_start_row = spu->start_row * scaley / 0x100;
+ spu->scaled_width = spu->width * scalex / 0x100;
+ spu->scaled_height = spu->height * scaley / 0x100;
+ /* Kludge: draw_alpha needs width multiple of 8 */
+ spu->scaled_stride = (spu->scaled_width + 7) & ~7;
+ if (spu->scaled_image_size < spu->scaled_stride * spu->scaled_height) {
+ if (spu->scaled_image) {
+ free(spu->scaled_image);
+ spu->scaled_image_size = 0;
+ }
+ spu->scaled_image = malloc(2 * spu->scaled_stride * spu->scaled_height);
+ if (spu->scaled_image) {
+ spu->scaled_image_size = spu->scaled_stride * spu->scaled_height;
+ spu->scaled_aimage = spu->scaled_image + spu->scaled_image_size;
+ }
+ }
+ if (spu->scaled_image) {
+ unsigned int x, y;
+ if (spu->scaled_width <= 1 || spu->scaled_height <= 1) {
+ goto nothing_to_do;
+ }
+ switch(spu_aamode&15) {
+ case 4:
+ sws_spu_image(spu->scaled_image, spu->scaled_aimage,
+ spu->scaled_width, spu->scaled_height, spu->scaled_stride,
+ spu->image, spu->aimage, spu->width, spu->height, spu->stride);
+ break;
+ case 3:
+ table_x = calloc(spu->scaled_width, sizeof(scale_pixel));
+ table_y = calloc(spu->scaled_height, sizeof(scale_pixel));
+ if (!table_x || !table_y) {
+ mp_msg(MSGT_SPUDEC, MSGL_FATAL, "Fatal: spudec_draw_scaled: calloc failed\n");
+ }
+ scale_table(0, 0, spu->width - 1, spu->scaled_width - 1, table_x);
+ scale_table(0, 0, spu->height - 1, spu->scaled_height - 1, table_y);
+ for (y = 0; y < spu->scaled_height; y++)
+ for (x = 0; x < spu->scaled_width; x++)
+ scale_image(x, y, table_x, table_y, spu);
+ free(table_x);
+ free(table_y);
+ break;
+ case 0:
+ /* no antialiasing */
+ for (y = 0; y < spu->scaled_height; ++y) {
+ int unscaled_y = y * 0x100 / scaley;
+ int strides = spu->stride * unscaled_y;
+ int scaled_strides = spu->scaled_stride * y;
+ for (x = 0; x < spu->scaled_width; ++x) {
+ int unscaled_x = x * 0x100 / scalex;
+ spu->scaled_image[scaled_strides + x] = spu->image[strides + unscaled_x];
+ spu->scaled_aimage[scaled_strides + x] = spu->aimage[strides + unscaled_x];
+ }
+ }
+ break;
+ case 1:
+ {
+ /* Intermediate antialiasing. */
+ for (y = 0; y < spu->scaled_height; ++y) {
+ const unsigned int unscaled_top = y * spu->orig_frame_height / dys;
+ unsigned int unscaled_bottom = (y + 1) * spu->orig_frame_height / dys;
+ if (unscaled_bottom >= spu->height)
+ unscaled_bottom = spu->height - 1;
+ for (x = 0; x < spu->scaled_width; ++x) {
+ const unsigned int unscaled_left = x * spu->orig_frame_width / dxs;
+ unsigned int unscaled_right = (x + 1) * spu->orig_frame_width / dxs;
+ unsigned int color = 0;
+ unsigned int alpha = 0;
+ unsigned int walkx, walky;
+ unsigned int base, tmp;
+ if (unscaled_right >= spu->width)
+ unscaled_right = spu->width - 1;
+ for (walky = unscaled_top; walky <= unscaled_bottom; ++walky)
+ for (walkx = unscaled_left; walkx <= unscaled_right; ++walkx) {
+ base = walky * spu->stride + walkx;
+ tmp = canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ base = y * spu->scaled_stride + x;
+ spu->scaled_image[base] = alpha ? color / alpha : 0;
+ spu->scaled_aimage[base] =
+ alpha * (1 + unscaled_bottom - unscaled_top) * (1 + unscaled_right - unscaled_left);
+ /* spu->scaled_aimage[base] =
+ alpha * dxs * dys / spu->orig_frame_width / spu->orig_frame_height; */
+ if (spu->scaled_aimage[base]) {
+ spu->scaled_aimage[base] = 256 - spu->scaled_aimage[base];
+ if (spu->scaled_aimage[base] + spu->scaled_image[base] > 255)
+ spu->scaled_image[base] = 256 - spu->scaled_aimage[base];
+ }
+ }
+ }
+ }
+ break;
+ case 2:
+ {
+ /* Best antialiasing. Very slow. */
+ /* Any pixel (x, y) represents pixels from the original
+ rectangular region comprised between the columns
+ unscaled_y and unscaled_y + 0x100 / scaley and the rows
+ unscaled_x and unscaled_x + 0x100 / scalex
+
+ The original rectangular region that the scaled pixel
+ represents is cut in 9 rectangular areas like this:
+
+ +---+-----------------+---+
+ | 1 | 2 | 3 |
+ +---+-----------------+---+
+ | | | |
+ | 4 | 5 | 6 |
+ | | | |
+ +---+-----------------+---+
+ | 7 | 8 | 9 |
+ +---+-----------------+---+
+
+ The width of the left column is at most one pixel and
+ it is never null and its right column is at a pixel
+ boundary. The height of the top row is at most one
+ pixel it is never null and its bottom row is at a
+ pixel boundary. The width and height of region 5 are
+ integral values. The width of the right column is
+ what remains and is less than one pixel. The height
+ of the bottom row is what remains and is less than
+ one pixel.
+
+ The row above 1, 2, 3 is unscaled_y. The row between
+ 1, 2, 3 and 4, 5, 6 is top_low_row. The row between 4,
+ 5, 6 and 7, 8, 9 is (unsigned int)unscaled_y_bottom.
+ The row beneath 7, 8, 9 is unscaled_y_bottom.
+
+ The column left of 1, 4, 7 is unscaled_x. The column
+ between 1, 4, 7 and 2, 5, 8 is left_right_column. The
+ column between 2, 5, 8 and 3, 6, 9 is (unsigned
+ int)unscaled_x_right. The column right of 3, 6, 9 is
+ unscaled_x_right. */
+ const double inv_scalex = (double) 0x100 / scalex;
+ const double inv_scaley = (double) 0x100 / scaley;
+ for (y = 0; y < spu->scaled_height; ++y) {
+ const double unscaled_y = y * inv_scaley;
+ const double unscaled_y_bottom = unscaled_y + inv_scaley;
+ const unsigned int top_low_row = FFMIN(unscaled_y_bottom, unscaled_y + 1.0);
+ const double top = top_low_row - unscaled_y;
+ const unsigned int height = unscaled_y_bottom > top_low_row
+ ? (unsigned int) unscaled_y_bottom - top_low_row
+ : 0;
+ const double bottom = unscaled_y_bottom > top_low_row
+ ? unscaled_y_bottom - floor(unscaled_y_bottom)
+ : 0.0;
+ for (x = 0; x < spu->scaled_width; ++x) {
+ const double unscaled_x = x * inv_scalex;
+ const double unscaled_x_right = unscaled_x + inv_scalex;
+ const unsigned int left_right_column = FFMIN(unscaled_x_right, unscaled_x + 1.0);
+ const double left = left_right_column - unscaled_x;
+ const unsigned int width = unscaled_x_right > left_right_column
+ ? (unsigned int) unscaled_x_right - left_right_column
+ : 0;
+ const double right = unscaled_x_right > left_right_column
+ ? unscaled_x_right - floor(unscaled_x_right)
+ : 0.0;
+ double color = 0.0;
+ double alpha = 0.0;
+ double tmp;
+ unsigned int base;
+ /* Now use these informations to compute a good alpha,
+ and lightness. The sum is on each of the 9
+ region's surface and alpha and lightness.
+
+ transformed alpha = sum(surface * alpha) / sum(surface)
+ transformed color = sum(surface * alpha * color) / sum(surface * alpha)
+ */
+ /* 1: top left part */
+ base = spu->stride * (unsigned int) unscaled_y;
+ tmp = left * top * canon_alpha(spu->aimage[base + (unsigned int) unscaled_x]);
+ alpha += tmp;
+ color += tmp * spu->image[base + (unsigned int) unscaled_x];
+ /* 2: top center part */
+ if (width > 0) {
+ unsigned int walkx;
+ for (walkx = left_right_column; walkx < (unsigned int) unscaled_x_right; ++walkx) {
+ base = spu->stride * (unsigned int) unscaled_y + walkx;
+ tmp = /* 1.0 * */ top * canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ }
+ /* 3: top right part */
+ if (right > 0.0) {
+ base = spu->stride * (unsigned int) unscaled_y + (unsigned int) unscaled_x_right;
+ tmp = right * top * canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ /* 4: center left part */
+ if (height > 0) {
+ unsigned int walky;
+ for (walky = top_low_row; walky < (unsigned int) unscaled_y_bottom; ++walky) {
+ base = spu->stride * walky + (unsigned int) unscaled_x;
+ tmp = left /* * 1.0 */ * canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ }
+ /* 5: center part */
+ if (width > 0 && height > 0) {
+ unsigned int walky;
+ for (walky = top_low_row; walky < (unsigned int) unscaled_y_bottom; ++walky) {
+ unsigned int walkx;
+ base = spu->stride * walky;
+ for (walkx = left_right_column; walkx < (unsigned int) unscaled_x_right; ++walkx) {
+ tmp = /* 1.0 * 1.0 * */ canon_alpha(spu->aimage[base + walkx]);
+ alpha += tmp;
+ color += tmp * spu->image[base + walkx];
+ }
+ }
+ }
+ /* 6: center right part */
+ if (right > 0.0 && height > 0) {
+ unsigned int walky;
+ for (walky = top_low_row; walky < (unsigned int) unscaled_y_bottom; ++walky) {
+ base = spu->stride * walky + (unsigned int) unscaled_x_right;
+ tmp = right /* * 1.0 */ * canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ }
+ /* 7: bottom left part */
+ if (bottom > 0.0) {
+ base = spu->stride * (unsigned int) unscaled_y_bottom + (unsigned int) unscaled_x;
+ tmp = left * bottom * canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ /* 8: bottom center part */
+ if (width > 0 && bottom > 0.0) {
+ unsigned int walkx;
+ base = spu->stride * (unsigned int) unscaled_y_bottom;
+ for (walkx = left_right_column; walkx < (unsigned int) unscaled_x_right; ++walkx) {
+ tmp = /* 1.0 * */ bottom * canon_alpha(spu->aimage[base + walkx]);
+ alpha += tmp;
+ color += tmp * spu->image[base + walkx];
+ }
+ }
+ /* 9: bottom right part */
+ if (right > 0.0 && bottom > 0.0) {
+ base = spu->stride * (unsigned int) unscaled_y_bottom + (unsigned int) unscaled_x_right;
+ tmp = right * bottom * canon_alpha(spu->aimage[base]);
+ alpha += tmp;
+ color += tmp * spu->image[base];
+ }
+ /* Finally mix these transparency and brightness information suitably */
+ base = spu->scaled_stride * y + x;
+ spu->scaled_image[base] = alpha > 0 ? color / alpha : 0;
+ spu->scaled_aimage[base] = alpha * scalex * scaley / 0x10000;
+ if (spu->scaled_aimage[base]) {
+ spu->scaled_aimage[base] = 256 - spu->scaled_aimage[base];
+ if (spu->scaled_aimage[base] + spu->scaled_image[base] > 255)
+ spu->scaled_image[base] = 256 - spu->scaled_aimage[base];
+ }
+ }
+ }
+ }
+ }
+nothing_to_do:
+ /* Kludge: draw_alpha needs width multiple of 8. */
+ if (spu->scaled_width < spu->scaled_stride)
+ for (y = 0; y < spu->scaled_height; ++y) {
+ memset(spu->scaled_aimage + y * spu->scaled_stride + spu->scaled_width, 0,
+ spu->scaled_stride - spu->scaled_width);
+ }
+ spu->scaled_frame_width = dxs;
+ spu->scaled_frame_height = dys;
+ }
+ }
+ if (spu->scaled_image){
+ switch (spu_alignment) {
+ case 0:
+ spu->scaled_start_row = dys*sub_pos/100;
+ if (spu->scaled_start_row + spu->scaled_height > dys)
+ spu->scaled_start_row = dys - spu->scaled_height;
+ break;
+ case 1:
+ spu->scaled_start_row = dys*sub_pos/100 - spu->scaled_height/2;
+ if (sub_pos >= 50 && spu->scaled_start_row + spu->scaled_height > dys)
+ spu->scaled_start_row = dys - spu->scaled_height;
+ break;
+ case 2:
+ spu->scaled_start_row = dys*sub_pos/100 - spu->scaled_height;
+ break;
+ }
+ draw_alpha(ctx, spu->scaled_start_col, spu->scaled_start_row, spu->scaled_width, spu->scaled_height,
+ spu->scaled_image, spu->scaled_aimage, spu->scaled_stride);
+ spu->spu_changed = 0;
+ }
+ }
+ }
+ else
+ {
+ mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPU not displayed: start_pts=%d end_pts=%d now_pts=%d\n",
+ spu->start_pts, spu->end_pts, spu->now_pts);
+ }
+}
+
+void spudec_update_palette(void * this, unsigned int *palette)
+{
+ spudec_handle_t *spu = this;
+ if (spu && palette) {
+ memcpy(spu->global_palette, palette, sizeof(spu->global_palette));
+ if(spu->hw_spu)
+ vo_control(spu->hw_spu, VOCTRL_SET_SPU_PALETTE, spu->global_palette);
+ }
+}
+
+void spudec_set_font_factor(void * this, double factor)
+{
+ spudec_handle_t *spu = this;
+ spu->font_start_level = (int)(0xF0-(0xE0*factor));
+}
+
+static void spudec_parse_extradata(spudec_handle_t *this,
+ uint8_t *extradata, int extradata_len)
+{
+ uint8_t *buffer, *ptr;
+ unsigned int *pal = this->global_palette, *cuspal = this->cuspal;
+ unsigned int tridx;
+ int i;
+
+ if (extradata_len == 16*4) {
+ for (i=0; i<16; i++)
+ pal[i] = AV_RB32(extradata + i*4);
+ this->auto_palette = 0;
+ return;
+ }
+
+ if (!(ptr = buffer = malloc(extradata_len+1)))
+ return;
+ memcpy(buffer, extradata, extradata_len);
+ buffer[extradata_len] = 0;
+
+ do {
+ if (*ptr == '#')
+ continue;
+ if (!strncmp(ptr, "size: ", 6))
+ sscanf(ptr + 6, "%dx%d", &this->orig_frame_width, &this->orig_frame_height);
+ if (!strncmp(ptr, "palette: ", 9) &&
+ sscanf(ptr + 9, "%x, %x, %x, %x, %x, %x, %x, %x, "
+ "%x, %x, %x, %x, %x, %x, %x, %x",
+ &pal[ 0], &pal[ 1], &pal[ 2], &pal[ 3],
+ &pal[ 4], &pal[ 5], &pal[ 6], &pal[ 7],
+ &pal[ 8], &pal[ 9], &pal[10], &pal[11],
+ &pal[12], &pal[13], &pal[14], &pal[15]) == 16) {
+ for (i=0; i<16; i++)
+ pal[i] = vobsub_palette_to_yuv(pal[i]);
+ this->auto_palette = 0;
+ }
+ if (!strncasecmp(ptr, "forced subs: on", 15))
+ this->forced_subs_only = 1;
+ if (!strncmp(ptr, "custom colors: ON, tridx: ", 26) &&
+ sscanf(ptr + 26, "%x, colors: %x, %x, %x, %x",
+ &tridx, cuspal+0, cuspal+1, cuspal+2, cuspal+3) == 5) {
+ for (i=0; i<4; i++) {
+ cuspal[i] = vobsub_rgb_to_yuv(cuspal[i]);
+ if (tridx & (1 << (12-4*i)))
+ cuspal[i] |= 1 << 31;
+ }
+ this->custom = 1;
+ }
+ } while ((ptr=strchr(ptr,'\n')) && *++ptr);
+
+ free(buffer);
+}
+
+void *spudec_new_scaled(unsigned int *palette, unsigned int frame_width, unsigned int frame_height, uint8_t *extradata, int extradata_len)
+{
+ spudec_handle_t *this = calloc(1, sizeof(spudec_handle_t));
+ if (this){
+ this->orig_frame_height = frame_height;
+ this->orig_frame_width = frame_width;
+ // set up palette:
+ if (palette)
+ memcpy(this->global_palette, palette, sizeof(this->global_palette));
+ else
+ this->auto_palette = 1;
+ if (extradata)
+ spudec_parse_extradata(this, extradata, extradata_len);
+ /* XXX Although the video frame is some size, the SPU frame is
+ always maximum size i.e. 720 wide and 576 or 480 high */
+ // For HD files in MKV the VobSub resolution can be higher though,
+ // see largeres_vobsub.mkv
+ if (this->orig_frame_width <= 720 && this->orig_frame_height <= 576) {
+ this->orig_frame_width = 720;
+ if (this->orig_frame_height == 480 || this->orig_frame_height == 240)
+ this->orig_frame_height = 480;
+ else
+ this->orig_frame_height = 576;
+ }
+ }
+ else
+ mp_msg(MSGT_SPUDEC,MSGL_FATAL, "FATAL: spudec_init: calloc");
+ return this;
+}
+
+void *spudec_new(unsigned int *palette)
+{
+ return spudec_new_scaled(palette, 0, 0, NULL, 0);
+}
+
+void spudec_free(void *this)
+{
+ spudec_handle_t *spu = this;
+ if (spu) {
+ while (spu->queue_head)
+ spudec_free_packet(spudec_dequeue_packet(spu));
+ free(spu->packet);
+ spu->packet = NULL;
+ free(spu->scaled_image);
+ spu->scaled_image = NULL;
+ free(spu->image);
+ spu->image = NULL;
+ spu->aimage = NULL;
+ free(spu->pal_image);
+ spu->pal_image = NULL;
+ spu->image_size = 0;
+ spu->pal_width = spu->pal_height = 0;
+ free(spu);
+ }
+}
+
+void spudec_set_hw_spu(void *this, struct vo *hw_spu)
+{
+ spudec_handle_t *spu = this;
+ if (!spu)
+ return;
+ spu->hw_spu = hw_spu;
+ vo_control(hw_spu, VOCTRL_SET_SPU_PALETTE, spu->global_palette);
+}
+
+/**
+ * palette must contain at least 256 32-bit entries, otherwise crashes
+ * are possible
+ */
+void spudec_set_paletted(void *this, const uint8_t *pal_img, int pal_stride,
+ const void *palette,
+ int x, int y, int w, int h,
+ double pts, double endpts)
+{
+ int i;
+ uint16_t g8a8_pal[256];
+ packet_t *packet;
+ const uint32_t *pal = palette;
+ spudec_handle_t *spu = this;
+ uint8_t *img;
+ uint8_t *aimg;
+ int stride = (w + 7) & ~7;
+ if ((unsigned)w >= 0x8000 || (unsigned)h > 0x4000)
+ return;
+ packet = calloc(1, sizeof(packet_t));
+ packet->is_decoded = 1;
+ packet->width = w;
+ packet->height = h;
+ packet->stride = stride;
+ packet->start_col = x;
+ packet->start_row = y;
+ packet->data_len = 2 * stride * h;
+ if (packet->data_len) { // size 0 is a special "clear" packet
+ packet->packet = malloc(packet->data_len);
+ img = packet->packet;
+ aimg = packet->packet + stride * h;
+ for (i = 0; i < 256; i++) {
+ uint32_t pixel = pal[i];
+ int alpha = pixel >> 24;
+ int gray = (((pixel & 0x000000ff) >> 0) +
+ ((pixel & 0x0000ff00) >> 7) +
+ ((pixel & 0x00ff0000) >> 16)) >> 2;
+ gray = FFMIN(gray, alpha);
+ g8a8_pal[i] = (-alpha << 8) | gray;
+ }
+ pal2gray_alpha(g8a8_pal, pal_img, pal_stride,
+ img, aimg, stride, w, h);
+ }
+ packet->start_pts = 0;
+ packet->end_pts = 0x7fffffff;
+ if (pts != MP_NOPTS_VALUE)
+ packet->start_pts = pts * 90000;
+ if (endpts != MP_NOPTS_VALUE)
+ packet->end_pts = endpts * 90000;
+ spudec_queue_packet(spu, packet);
+}
diff --git a/sub/spudec.h b/sub/spudec.h
new file mode 100644
index 0000000000..2b3828c34c
--- /dev/null
+++ b/sub/spudec.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_SPUDEC_H
+#define MPLAYER_SPUDEC_H
+
+#include "libvo/video_out.h"
+
+void spudec_heartbeat(void *this, unsigned int pts100);
+void spudec_assemble(void *this, unsigned char *packet, unsigned int len, int pts100);
+void spudec_draw(void *this, void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx);
+void spudec_draw_scaled(void *this, unsigned int dxs, unsigned int dys, void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx);
+int spudec_apply_palette_crop(void *this, uint32_t palette, int sx, int ex, int sy, int ey);
+void spudec_update_palette(void *this, unsigned int *palette);
+void *spudec_new_scaled(unsigned int *palette, unsigned int frame_width, unsigned int frame_height, uint8_t *extradata, int extradata_len);
+void *spudec_new(unsigned int *palette);
+void spudec_free(void *this);
+void spudec_reset(void *this); // called after seek
+int spudec_visible(void *this); // check if spu is visible
+void spudec_set_font_factor(void * this, double factor); // sets the equivalent to ffactor
+void spudec_set_hw_spu(void *this, struct vo *hw_spu);
+int spudec_changed(void *this);
+void spudec_calc_bbox(void *me, unsigned int dxs, unsigned int dys, unsigned int* bbox);
+void spudec_set_forced_subs_only(void * const this, const unsigned int flag);
+void spudec_set_paletted(void *this, const uint8_t *pal_img, int stride,
+ const void *palette,
+ int x, int y, int w, int h,
+ double pts, double endpts);
+
+#endif /* MPLAYER_SPUDEC_H */
diff --git a/sub/sub.c b/sub/sub.c
new file mode 100644
index 0000000000..75d43c2c48
--- /dev/null
+++ b/sub/sub.c
@@ -0,0 +1,1348 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#if HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+
+#include "stream/stream.h"
+#include "stream/stream_dvdnav.h"
+#define OSD_NAV_BOX_ALPHA 0x7f
+
+#include "libmpcodecs/dec_teletext.h"
+#include "osdep/timer.h"
+
+#include "talloc.h"
+#include "mplayer.h"
+#include "mp_msg.h"
+#include "libvo/video_out.h"
+#include "font_load.h"
+#include "sub.h"
+#include "spudec.h"
+#include "libavutil/common.h"
+
+#define NEW_SPLITTING
+
+
+// Structures needed for the new splitting algorithm.
+// osd_text_t contains the single subtitle word.
+// osd_text_p is used to mark the lines of subtitles
+struct osd_text_t {
+ int osd_kerning, //kerning with the previous word
+ osd_length, //orizontal length inside the bbox
+ text_length, //number of characters
+ *text; //characters
+ struct osd_text_t *prev,
+ *next;
+};
+
+struct osd_text_p {
+ int value;
+ struct osd_text_t *ott;
+ struct osd_text_p *prev,
+ *next;
+};
+//^
+
+char * const sub_osd_names[]={
+ _("Seekbar"),
+ _("Play"),
+ _("Pause"),
+ _("Stop"),
+ _("Rewind"),
+ _("Forward"),
+ _("Clock"),
+ _("Contrast"),
+ _("Saturation"),
+ _("Volume"),
+ _("Brightness"),
+ _("Hue"),
+ _("Balance")
+};
+char * const sub_osd_names_short[] ={ "", "|>", "||", "[]", "<<" , ">>", "", "", "", "", "", "", "" };
+
+//static int vo_font_loaded=-1;
+font_desc_t* vo_font=NULL;
+
+void* vo_osd_teletext_page=NULL;
+int vo_osd_teletext_half = 0;
+int vo_osd_teletext_mode=0;
+int vo_osd_teletext_format=0;
+int vo_osd_teletext_scale=0;
+int sub_unicode=0;
+int sub_utf8=0;
+int sub_pos=100;
+int sub_width_p=100;
+int sub_alignment=2; /* 0=top, 1=center, 2=bottom */
+int sub_visibility=1;
+int sub_bg_color=0; /* subtitles background color */
+int sub_bg_alpha=0;
+int sub_justify=0;
+#ifdef CONFIG_DVDNAV
+static nav_highlight_t nav_hl;
+#endif
+
+// return the real height of a char:
+static inline int get_height(int c,int h){
+ int font;
+ if ((font=vo_font->font[c])>=0)
+ if(h<vo_font->pic_a[font]->h) h=vo_font->pic_a[font]->h;
+ return h;
+}
+
+// renders char to a big per-object buffer where alpha and bitmap are separated
+static void draw_alpha_buf(mp_osd_obj_t* obj, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride)
+{
+ int dststride = obj->stride;
+ int dstskip = obj->stride-w;
+ int srcskip = stride-w;
+ int i, j;
+ unsigned char *b = obj->bitmap_buffer + (y0-obj->bbox.y1)*dststride + (x0-obj->bbox.x1);
+ unsigned char *a = obj->alpha_buffer + (y0-obj->bbox.y1)*dststride + (x0-obj->bbox.x1);
+ unsigned char *bs = src;
+ unsigned char *as = srca;
+
+ if (x0 < obj->bbox.x1 || x0+w > obj->bbox.x2 || y0 < obj->bbox.y1 || y0+h > obj->bbox.y2) {
+ fprintf(stderr, "osd text out of range: bbox [%d %d %d %d], txt [%d %d %d %d]\n",
+ obj->bbox.x1, obj->bbox.x2, obj->bbox.y1, obj->bbox.y2,
+ x0, x0+w, y0, y0+h);
+ return;
+ }
+
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++, b++, a++, bs++, as++) {
+ if (*b < *bs) *b = *bs;
+ if (*as) {
+ if (*a == 0 || *a > *as) *a = *as;
+ }
+ }
+ b+= dstskip;
+ a+= dstskip;
+ bs+= srcskip;
+ as+= srcskip;
+ }
+}
+
+// allocates/enlarges the alpha/bitmap buffer
+static void alloc_buf(mp_osd_obj_t* obj)
+{
+ int len;
+ if (obj->bbox.x2 < obj->bbox.x1) obj->bbox.x2 = obj->bbox.x1;
+ if (obj->bbox.y2 < obj->bbox.y1) obj->bbox.y2 = obj->bbox.y1;
+ obj->stride = ((obj->bbox.x2-obj->bbox.x1)+7)&(~7);
+ len = obj->stride*(obj->bbox.y2-obj->bbox.y1);
+ if (obj->allocated<len) {
+ obj->allocated = len;
+ free(obj->bitmap_buffer);
+ free(obj->alpha_buffer);
+ obj->bitmap_buffer = (unsigned char *)memalign(16, len);
+ obj->alpha_buffer = (unsigned char *)memalign(16, len);
+ }
+ memset(obj->bitmap_buffer, sub_bg_color, len);
+ memset(obj->alpha_buffer, sub_bg_alpha, len);
+}
+
+// renders the buffer
+inline static void vo_draw_text_from_buffer(mp_osd_obj_t* obj,void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx)
+{
+ if (obj->allocated > 0) {
+ draw_alpha(ctx,
+ obj->bbox.x1,obj->bbox.y1,
+ obj->bbox.x2-obj->bbox.x1,
+ obj->bbox.y2-obj->bbox.y1,
+ obj->bitmap_buffer,
+ obj->alpha_buffer,
+ obj->stride);
+ }
+}
+
+unsigned utf8_get_char(const char **str) {
+ const uint8_t *strp = (const uint8_t *)*str;
+ unsigned c;
+ GET_UTF8(c, *strp++, goto no_utf8;);
+ *str = (const char *)strp;
+ return c;
+
+no_utf8:
+ strp = (const uint8_t *)*str;
+ c = *strp++;
+ *str = (const char *)strp;
+ return c;
+}
+
+inline static void vo_update_text_osd(struct osd_state *osd, mp_osd_obj_t* obj,
+ int dxs, int dys)
+{
+ const char *cp = osd->osd_text;
+ int x=20;
+ int h=0;
+ int font;
+
+ obj->bbox.x1=obj->x=x;
+ obj->bbox.y1=obj->y=10;
+
+ while (*cp){
+ uint16_t c=utf8_get_char(&cp);
+ render_one_glyph(vo_font, c);
+ x+=vo_font->width[c]+vo_font->charspace;
+ h=get_height(c,h);
+ }
+
+ obj->bbox.x2=x-vo_font->charspace;
+ obj->bbox.y2=obj->bbox.y1+h;
+ obj->flags|=OSDFLAG_BBOX;
+
+ alloc_buf(obj);
+
+ cp = osd->osd_text;
+ x = obj->x;
+ while (*cp){
+ uint16_t c=utf8_get_char(&cp);
+ if ((font=vo_font->font[c])>=0)
+ draw_alpha_buf(obj,x,obj->y,
+ vo_font->width[c],
+ vo_font->pic_a[font]->h,
+ vo_font->pic_b[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->w);
+ x+=vo_font->width[c]+vo_font->charspace;
+ }
+}
+
+#ifdef CONFIG_DVDNAV
+void osd_set_nav_box (uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey) {
+ nav_hl.sx = sx;
+ nav_hl.sy = sy;
+ nav_hl.ex = ex;
+ nav_hl.ey = ey;
+}
+
+inline static void vo_update_nav (mp_osd_obj_t *obj, int dxs, int dys, int left_border, int top_border,
+ int right_border, int bottom_border, int orig_w, int orig_h) {
+ int len;
+ int sx = nav_hl.sx, sy = nav_hl.sy;
+ int ex = nav_hl.ex, ey = nav_hl.ey;
+ int scaled_w = dxs - left_border - right_border;
+ int scaled_h = dys - top_border - bottom_border;
+ if (scaled_w != orig_w) {
+ sx = sx * scaled_w / orig_w;
+ ex = ex * scaled_w / orig_w;
+ }
+ if (scaled_h != orig_h) {
+ sy = sy * scaled_h / orig_h;
+ ey = ey * scaled_h / orig_h;
+ }
+ sx += left_border; ex += left_border;
+ sy += top_border; ey += top_border;
+ sx = FFMIN(FFMAX(sx, 0), dxs);
+ ex = FFMIN(FFMAX(ex, 0), dxs);
+ sy = FFMIN(FFMAX(sy, 0), dys);
+ ey = FFMIN(FFMAX(ey, 0), dys);
+
+ obj->bbox.x1 = obj->x = sx;
+ obj->bbox.y1 = obj->y = sy;
+ obj->bbox.x2 = ex;
+ obj->bbox.y2 = ey;
+
+ alloc_buf (obj);
+ len = obj->stride * (obj->bbox.y2 - obj->bbox.y1);
+ memset (obj->bitmap_buffer, OSD_NAV_BOX_ALPHA, len);
+ memset (obj->alpha_buffer, OSD_NAV_BOX_ALPHA, len);
+ obj->flags |= OSDFLAG_BBOX | OSDFLAG_CHANGED;
+ if (obj->bbox.y2 > obj->bbox.y1 && obj->bbox.x2 > obj->bbox.x1)
+ obj->flags |= OSDFLAG_VISIBLE;
+}
+#endif
+
+// renders char to a big per-object buffer where alpha and bitmap are separated
+static void tt_draw_alpha_buf(mp_osd_obj_t* obj, int x0,int y0, int w,int h, unsigned char* src, int stride,int fg,int bg,int alpha)
+{
+ int dststride = obj->stride;
+ int dstskip = obj->stride-w;
+ int srcskip = stride-w;
+ int i, j;
+ unsigned char *b = obj->bitmap_buffer + (y0-obj->bbox.y1)*dststride + (x0-obj->bbox.x1);
+ unsigned char *a = obj->alpha_buffer + (y0-obj->bbox.y1)*dststride + (x0-obj->bbox.x1);
+ unsigned char *bs = src;
+ if (x0 < obj->bbox.x1 || x0+w > obj->bbox.x2 || y0 < obj->bbox.y1 || y0+h > obj->bbox.y2) {
+ mp_msg(MSGT_OSD,MSGL_ERR,"tt osd text out of range: bbox [%d %d %d %d], txt [%d %d %d %d]\n",
+ obj->bbox.x1, obj->bbox.x2, obj->bbox.y1, obj->bbox.y2,
+ x0, x0+w, y0, y0+h);
+ return;
+ }
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++, b++, a++, bs++) {
+ *b=(fg-bg)*(*bs)/255+bg;
+ *a=alpha;
+ }
+ b+= dstskip;
+ a+= dstskip;
+ bs+= srcskip;
+ }
+}
+inline static void vo_update_text_teletext(mp_osd_obj_t *obj, int dxs, int dys)
+{
+ int h=0,w=0,i,j,font,flashon;
+ int wm,hm;
+ int color;
+ int x,y,x0,y0;
+ int cols,rows;
+ int wm12;
+ int hm13;
+ int hm23;
+ int start_row,max_rows;
+ int b,ax[6],ay[6],aw[6],ah[6];
+ tt_char tc;
+ tt_char* tdp=vo_osd_teletext_page;
+ static const uint8_t colors[8]={1,85,150,226,70,105,179,254};
+ unsigned char* buf[9];
+
+ obj->flags|=OSDFLAG_CHANGED|OSDFLAG_VISIBLE;
+ if (!tdp || !vo_osd_teletext_mode) {
+ obj->flags&=~OSDFLAG_VISIBLE;
+ return;
+ }
+ flashon=(GetTimer()/1000000)%2;
+ switch(vo_osd_teletext_half){
+ case TT_ZOOM_TOP_HALF:
+ start_row=0;
+ max_rows=VBI_ROWS/2;
+ break;
+ case TT_ZOOM_BOTTOM_HALF:
+ start_row=VBI_ROWS/2;
+ max_rows=VBI_ROWS/2;
+ break;
+ default:
+ start_row=0;
+ max_rows=VBI_ROWS;
+ break;
+ }
+ wm=0;
+ for(i=start_row;i<max_rows;i++){
+ for(j=0;j<VBI_COLUMNS;j++){
+ tc=tdp[i*VBI_COLUMNS+j];
+ if(!tc.ctl && !tc.gfx)
+ {
+ render_one_glyph(vo_font, tc.unicode);
+ if (wm<vo_font->width[tc.unicode])
+ wm=vo_font->width[tc.unicode];
+ }
+ }
+ }
+
+ hm=vo_font->height+1;
+ wm=dxs*hm*max_rows/(dys*VBI_COLUMNS);
+
+#ifdef CONFIG_FREETYPE
+ //very simple teletext font auto scaling
+ if(!vo_osd_teletext_scale && hm*(max_rows+1)>dys){
+ osd_font_scale_factor*=1.0*(dys)/((max_rows+1)*hm);
+ force_load_font=1;
+ vo_osd_teletext_scale=osd_font_scale_factor;
+ obj->flags&=~OSDFLAG_VISIBLE;
+ return;
+ }
+#endif
+
+ cols=dxs/wm;
+ rows=dys/hm;
+
+ if(cols>VBI_COLUMNS)
+ cols=VBI_COLUMNS;
+ if(rows>max_rows)
+ rows=max_rows;
+ w=cols*wm-vo_font->charspace;
+ h=rows*hm-vo_font->charspace;
+
+ if(w<dxs)
+ x0=(dxs-w)/2;
+ else
+ x0=0;
+ if(h<dys)
+ y0=(dys-h)/2;
+ else
+ y0=0;
+
+ wm12=wm>>1;
+ hm13=(hm+1)/3;
+ hm23=hm13<<1;
+
+ for(i=0;i<6;i+=2){
+ ax[i+0]=0;
+ aw[i+0]=wm12;
+
+ ax[i+1]=wm12;
+ aw[i+1]=wm-wm12;
+ }
+
+ for(i=0;i<2;i++){
+ ay[i+0]=0;
+ ah[i+0]=hm13;
+
+ ay[i+2]=hm13;
+ ah[i+2]=hm-hm23;
+
+ ay[i+4]=hm-hm13;
+ ah[i+4]=hm13;
+ }
+
+ obj->x = 0;
+ obj->y = 0;
+ obj->bbox.x1 = x0;
+ obj->bbox.y1 = y0;
+ obj->bbox.x2 = x0+w;
+ obj->bbox.y2 = y0+h;
+ obj->flags |= OSDFLAG_BBOX;
+ alloc_buf(obj);
+
+ for(i=0;i<9;i++)
+ buf[i]=malloc(wm*hm);
+
+ //alpha
+ if(vo_osd_teletext_format==TT_FORMAT_OPAQUE ||vo_osd_teletext_format==TT_FORMAT_OPAQUE_INV)
+ color=1;
+ else
+ color=200;
+ memset(buf[8],color,wm*hm);
+ //colors
+ if(vo_osd_teletext_format==TT_FORMAT_OPAQUE ||vo_osd_teletext_format==TT_FORMAT_TRANSPARENT){
+ for(i=0;i<8;i++){
+ memset(buf[i],(unsigned char)(1.0*(255-color)*colors[i]/255),wm*hm);
+ }
+ }else{
+ for(i=0;i<8;i++)
+ memset(buf[i],(unsigned char)(1.0*(255-color)*colors[7-i]/255),wm*hm);
+ }
+
+ y=y0;
+ for(i=0;i<rows;i++){
+ x=x0;
+ for(j=0;j<cols;j++){
+ tc=tdp[(i+start_row)*VBI_COLUMNS+j];
+ if (tc.hidden) { x+=wm; continue;}
+ if(!tc.gfx || (tc.flh && !flashon)){
+ /* Rendering one text character */
+ draw_alpha_buf(obj,x,y,wm,hm,buf[tc.bg],buf[8],wm);
+ if(tc.unicode!=0x20 && tc.unicode!=0x00 && !tc.ctl &&
+ (!tc.flh || flashon) &&
+ (font=vo_font->font[tc.unicode])>=0 && y+hm<dys){
+ tt_draw_alpha_buf(obj,x,y,vo_font->width[tc.unicode],vo_font->height,
+ vo_font->pic_b[font]->bmp+vo_font->start[tc.unicode]-vo_font->charspace*vo_font->pic_a[font]->w,
+ vo_font->pic_b[font]->w,
+ buf[tc.fg][0],buf[tc.bg][0],buf[8][0]);
+ }
+ }else{
+/*
+Rendering one graphics character
+TODO: support for separated graphics symbols (where six rectangles does not touch each other)
+
+ +--+ +--+ 87654321
+ |01| |12| --------
+ |10| <= |34| <= 00100110 <= 0x26
+ |01| |56|
+ +--+ +--+
+
+(0:wm/2) (wm/2:wm-wm/2)
+
+********** *********** (0:hm/3)
+*** **** **** ****
+*** 1 **** **** 2 ****
+*** **** **** ****
+********** ***********
+********** ***********
+
+********** *********** (hm/3:hm-2*hm/3)
+********** ***********
+*** **** **** ****
+*** 3 **** **** 4 ****
+*** **** **** ****
+********** ***********
+********** ***********
+********** ***********
+
+********** *********** (hm-hm/3:hm/3)
+*** **** **** ****
+*** 5 **** **** 6 ****
+*** **** **** ****
+********** ***********
+********** ***********
+
+*/
+ if(tc.gfx>1){ //separated gfx
+ for(b=0;b<6;b++){
+ color=(tc.unicode>>b)&1?tc.fg:tc.bg;
+ draw_alpha_buf(obj,x+ax[b]+1,y+ay[b]+1,aw[b]-2,ah[b]-2,buf[color],buf[8],wm);
+ }
+ //separated gfx (background borders)
+ //vertical
+ draw_alpha_buf(obj,x ,y,1,hm,buf[tc.bg],buf[8],wm);
+ draw_alpha_buf(obj,x+ax[1]-1,y,2,hm,buf[tc.bg],buf[8],wm);
+ draw_alpha_buf(obj,x+ax[1]+aw[1]-1,y,wm-ax[1]-aw[1]+1,hm,buf[tc.bg],buf[8],wm);
+ //horizontal
+ draw_alpha_buf(obj,x,y ,wm,1,buf[tc.bg],buf[8],wm);
+ draw_alpha_buf(obj,x,y+ay[0]+ah[0]-1,wm,2,buf[tc.bg],buf[8],wm);
+ draw_alpha_buf(obj,x,y+ay[2]+ah[2]-1,wm,2,buf[tc.bg],buf[8],wm);
+ draw_alpha_buf(obj,x,y+ay[4]+ah[4]-1,wm,hm-ay[4]-ah[4]+1,buf[tc.bg],buf[8],wm);
+ }else{
+ for(b=0;b<6;b++){
+ color=(tc.unicode>>b)&1?tc.fg:tc.bg;
+ draw_alpha_buf(obj,x+ax[b],y+ay[b],aw[b],ah[b],buf[color],buf[8],wm);
+ }
+ }
+ }
+ x+=wm;
+ }
+ y+=hm;
+ }
+ for(i=0;i<9;i++)
+ free(buf[i]);
+}
+
+int vo_osd_progbar_type=-1;
+int vo_osd_progbar_value=100; // 0..256
+
+// if we have n=256 bars then OSD progbar looks like below
+//
+// 0 1 2 3 ... 256 <= vo_osd_progbar_value
+// | | | | |
+// [ === === === ... === ]
+//
+// the above schema is rescalled to n=elems bars
+
+inline static void vo_update_text_progbar(mp_osd_obj_t* obj,int dxs,int dys){
+
+ obj->flags|=OSDFLAG_CHANGED|OSDFLAG_VISIBLE;
+
+ if(vo_osd_progbar_type<0 || !vo_font){
+ obj->flags&=~OSDFLAG_VISIBLE;
+ return;
+ }
+
+ render_one_glyph(vo_font, OSD_PB_START);
+ render_one_glyph(vo_font, OSD_PB_END);
+ render_one_glyph(vo_font, OSD_PB_0);
+ render_one_glyph(vo_font, OSD_PB_1);
+ render_one_glyph(vo_font, vo_osd_progbar_type);
+
+ // calculate bbox corners:
+ { int h=0;
+ int y=(dys-vo_font->height)/2;
+ int delimw=vo_font->width[OSD_PB_START]
+ +vo_font->width[OSD_PB_END]
+ +vo_font->charspace;
+ int width=(2*dxs-3*delimw)/3;
+ int charw=vo_font->width[OSD_PB_0]+vo_font->charspace;
+ int elems=width/charw;
+ int x=(dxs-elems*charw-delimw)/2;
+ int delta = 0;
+ h=get_height(OSD_PB_START,h);
+ h=get_height(OSD_PB_END,h);
+ h=get_height(OSD_PB_0,h);
+ h=get_height(OSD_PB_1,h);
+ if (vo_osd_progbar_type>0 && vo_font->font[vo_osd_progbar_type]>=0){
+ delta = vo_font->width[vo_osd_progbar_type]+vo_font->spacewidth;
+ delta = (x-delta > 0) ? delta : x;
+ h=get_height(vo_osd_progbar_type,h);
+ }
+ obj->bbox.x1=obj->x=x;
+ obj->bbox.y1=obj->y=y;
+ obj->bbox.x2=x+width+delimw;
+ obj->bbox.y2=y+h; //vo_font->height;
+ obj->flags|=OSDFLAG_BBOX;
+ obj->params.progbar.elems=elems;
+ obj->bbox.x1-=delta; // space for an icon
+ }
+
+ alloc_buf(obj);
+
+ {
+ int minw = vo_font->width[OSD_PB_START]+vo_font->width[OSD_PB_END]+vo_font->width[OSD_PB_0];
+ if (vo_osd_progbar_type>0 && vo_font->font[vo_osd_progbar_type]>=0){
+ minw += vo_font->width[vo_osd_progbar_type]+vo_font->charspace+vo_font->spacewidth;
+ }
+ if (obj->bbox.x2 - obj->bbox.x1 < minw) return; // space too small, don't render anything
+ }
+
+ // render it:
+ { unsigned char *s;
+ unsigned char *sa;
+ int i,w,h,st,mark;
+ int x=obj->x;
+ int y=obj->y;
+ int c,font;
+ int charw=vo_font->width[OSD_PB_0]+vo_font->charspace;
+ int elems=obj->params.progbar.elems;
+
+ if (vo_osd_progbar_value<=0)
+ mark=0;
+ else {
+ int ev=vo_osd_progbar_value*elems;
+ mark=ev>>8;
+ if (ev & 0xFF) mark++;
+ if (mark>elems) mark=elems;
+ }
+
+
+// printf("osd.progbar width=%d xpos=%d\n",width,x);
+
+ c=vo_osd_progbar_type;
+ if(vo_osd_progbar_type>0 && (font=vo_font->font[c])>=0) {
+ int xp=x-vo_font->width[c]-vo_font->spacewidth;
+ draw_alpha_buf(obj,(xp<0?0:xp),y,
+ vo_font->width[c],
+ vo_font->pic_a[font]->h,
+ vo_font->pic_b[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->w);
+ }
+
+ c=OSD_PB_START;
+ if ((font=vo_font->font[c])>=0)
+ draw_alpha_buf(obj,x,y,
+ vo_font->width[c],
+ vo_font->pic_a[font]->h,
+ vo_font->pic_b[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->w);
+ x+=vo_font->width[c]+vo_font->charspace;
+
+ c=OSD_PB_0;
+ if ((font=vo_font->font[c])>=0){
+ w=vo_font->width[c];
+ h=vo_font->pic_a[font]->h;
+ s=vo_font->pic_b[font]->bmp+vo_font->start[c];
+ sa=vo_font->pic_a[font]->bmp+vo_font->start[c];
+ st=vo_font->pic_a[font]->w;
+ if ((i=mark)) do {
+ draw_alpha_buf(obj,x,y,w,h,s,sa,st);
+ x+=charw;
+ } while(--i);
+ }
+
+ c=OSD_PB_1;
+ if ((font=vo_font->font[c])>=0){
+ w=vo_font->width[c];
+ h=vo_font->pic_a[font]->h;
+ s =vo_font->pic_b[font]->bmp+vo_font->start[c];
+ sa=vo_font->pic_a[font]->bmp+vo_font->start[c];
+ st=vo_font->pic_a[font]->w;
+ if ((i=elems-mark)) do {
+ draw_alpha_buf(obj,x,y,w,h,s,sa,st);
+ x+=charw;
+ } while(--i);
+ }
+
+ c=OSD_PB_END;
+ if ((font=vo_font->font[c])>=0)
+ draw_alpha_buf(obj,x,y,
+ vo_font->width[c],
+ vo_font->pic_a[font]->h,
+ vo_font->pic_b[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->bmp+vo_font->start[c],
+ vo_font->pic_a[font]->w);
+// x+=vo_font->width[c]+vo_font->charspace;
+
+ }
+// vo_osd_progbar_value=(vo_osd_progbar_value+1)&0xFF;
+
+}
+
+subtitle* vo_sub=NULL;
+
+inline static void vo_update_text_sub(struct osd_state *osd, mp_osd_obj_t* obj,int dxs,int dys){
+ unsigned char *t;
+ int c,i,j,l,x,y,font,prevc,counter;
+ int k;
+ int lastStripPosition;
+ int xsize;
+ int xmin=dxs,xmax=0;
+ int h,lasth;
+ int xtblc, utblc;
+ struct font_desc *sub_font = osd->sub_font;
+
+ obj->flags|=OSDFLAG_CHANGED|OSDFLAG_VISIBLE;
+
+ if(!vo_sub || !osd->sub_font || !sub_visibility || (sub_font->font[40]<0)){
+ obj->flags&=~OSDFLAG_VISIBLE;
+ return;
+ }
+
+ obj->bbox.y2=obj->y=dys;
+ obj->params.subtitle.lines=0;
+
+ // too long lines divide into a smaller ones
+ i=k=lasth=0;
+ h=sub_font->height;
+ lastStripPosition=-1;
+ l=vo_sub->lines;
+
+ {
+ struct osd_text_t *osl, *cp_ott, *tmp_ott, *tmp;
+ struct osd_text_p *otp_sub = NULL, *otp_sub_tmp, // these are used to store the whole sub text osd
+ *otp, *tmp_otp, *pmt; // these are used to manage sub text osd coming from a single sub line
+ int *char_seq, char_position, xlimit = dxs * sub_width_p / 100, counter;
+
+ while (l) {
+ xsize = -sub_font->charspace;
+ l--;
+ t=vo_sub->text[i++];
+ char_position = 0;
+ char_seq = calloc(strlen(t), sizeof(int));
+
+ prevc = -1;
+
+ otp = NULL;
+ osl = NULL;
+ x = 1;
+
+ // reading the subtitle words from vo_sub->text[]
+ while (*t) {
+ if (sub_utf8)
+ c = utf8_get_char(&t);
+ else if ((c = *t++) >= 0x80 && sub_unicode)
+ c = (c<<8) + *t++;
+ if (k==MAX_UCS){
+ t += strlen(t); // end here
+ mp_msg(MSGT_OSD,MSGL_WARN,"\nMAX_UCS exceeded!\n");
+ }
+ if (!c) c++; // avoid UCS 0
+ render_one_glyph(sub_font, c);
+
+ if (c == ' ') {
+ struct osd_text_t *tmp_ott = calloc(1, sizeof(struct osd_text_t));
+
+ if (osl == NULL) {
+ osl = cp_ott = tmp_ott;
+ } else {
+ tmp_ott->prev = cp_ott;
+ cp_ott->next = tmp_ott;
+ tmp_ott->osd_kerning =
+ sub_font->charspace + sub_font->width[' '];
+ cp_ott = tmp_ott;
+ }
+ tmp_ott->osd_length = xsize;
+ tmp_ott->text_length = char_position;
+ tmp_ott->text = malloc(char_position * sizeof(int));
+ for (counter = 0; counter < char_position; ++counter)
+ tmp_ott->text[counter] = char_seq[counter];
+ char_position = 0;
+ xsize = 0;
+ prevc = c;
+ } else {
+ int delta_xsize = sub_font->width[c] + sub_font->charspace + kerning(sub_font, prevc, c);
+
+ if (xsize + delta_xsize <= dxs) {
+ if (!x) x = 1;
+ prevc = c;
+ char_seq[char_position++] = c;
+ xsize += delta_xsize;
+ if ((!suboverlap_enabled) && ((font = sub_font->font[c]) >= 0)) {
+ if (sub_font->pic_a[font]->h > h) {
+ h = sub_font->pic_a[font]->h;
+ }
+ }
+ } else {
+ if (x) {
+ mp_msg(MSGT_OSD, MSGL_WARN, "\nSubtitle word '%s' too long!\n", t);
+ x = 0;
+ }
+ }
+ }
+ }// for len (all words from subtitle line read)
+
+ // osl holds an ordered (as they appear in the lines) chain of the subtitle words
+ {
+ struct osd_text_t *tmp_ott = calloc(1, sizeof(struct osd_text_t));
+
+ if (osl == NULL) {
+ osl = cp_ott = tmp_ott;
+ } else {
+ tmp_ott->prev = cp_ott;
+ cp_ott->next = tmp_ott;
+ tmp_ott->osd_kerning =
+ sub_font->charspace + sub_font->width[' '];
+ cp_ott = tmp_ott;
+ }
+ tmp_ott->osd_length = xsize;
+ tmp_ott->text_length = char_position;
+ tmp_ott->text = malloc(char_position * sizeof(int));
+ for (counter = 0; counter < char_position; ++counter)
+ tmp_ott->text[counter] = char_seq[counter];
+ char_position = 0;
+ xsize = -sub_font->charspace;
+ }
+ free(char_seq);
+
+ if (osl != NULL) {
+ int value = 0, exit = 0, minimum = 0;
+
+ // otp will contain the chain of the osd subtitle lines coming from the single vo_sub line.
+ otp = tmp_otp = calloc(1, sizeof(struct osd_text_p));
+ tmp_otp->ott = osl;
+ for (tmp_ott = tmp_otp->ott; exit == 0; ) {
+ do {
+ value += tmp_ott->osd_kerning + tmp_ott->osd_length;
+ tmp_ott = tmp_ott->next;
+ } while ((tmp_ott != NULL) && (value + tmp_ott->osd_kerning + tmp_ott->osd_length <= xlimit));
+ if (tmp_ott != NULL) {
+ struct osd_text_p *tmp = calloc(1, sizeof(struct osd_text_p));
+
+ tmp_otp->value = value;
+ tmp_otp->next = tmp;
+ tmp->prev = tmp_otp;
+ tmp_otp = tmp;
+ tmp_otp->ott = tmp_ott;
+ value = -2 * sub_font->charspace - sub_font->width[' '];
+ } else {
+ tmp_otp->value = value;
+ exit = 1;
+ }
+ }
+
+
+#ifdef NEW_SPLITTING
+ // minimum holds the 'sum of the differences in length among the lines',
+ // a measure of the evenness of the lengths of the lines
+ for (tmp_otp = otp; tmp_otp->next != NULL; tmp_otp = tmp_otp->next) {
+ pmt = tmp_otp->next;
+ while (pmt != NULL) {
+ minimum += abs(tmp_otp->value - pmt->value);
+ pmt = pmt->next;
+ }
+ }
+
+ if (otp->next != NULL) {
+ int mem1, mem2;
+ struct osd_text_p *mem, *hold;
+
+ exit = 0;
+ // until the last word of a line can be moved to the beginning of following line
+ // reducing the 'sum of the differences in length among the lines', it is done
+ while (exit == 0) {
+ hold = NULL;
+ exit = 1;
+ for (tmp_otp = otp; tmp_otp->next != NULL; tmp_otp = tmp_otp->next) {
+ pmt = tmp_otp->next;
+ for (tmp = tmp_otp->ott; tmp->next != pmt->ott; tmp = tmp->next);
+ if (pmt->value + tmp->osd_length + pmt->ott->osd_kerning <= xlimit) {
+ mem1 = tmp_otp->value;
+ mem2 = pmt->value;
+ tmp_otp->value = mem1 - tmp->osd_length - tmp->osd_kerning;
+ pmt->value = mem2 + tmp->osd_length + pmt->ott->osd_kerning;
+
+ value = 0;
+ for (mem = otp; mem->next != NULL; mem = mem->next) {
+ pmt = mem->next;
+ while (pmt != NULL) {
+ value += abs(mem->value - pmt->value);
+ pmt = pmt->next;
+ }
+ }
+ if (value < minimum) {
+ minimum = value;
+ hold = tmp_otp;
+ exit = 0;
+ }
+ tmp_otp->value = mem1;
+ tmp_otp->next->value = mem2;
+ }
+ }
+ // merging
+ if (exit == 0) {
+ tmp_otp = hold;
+ pmt = tmp_otp->next;
+ for (tmp = tmp_otp->ott; tmp->next != pmt->ott; tmp = tmp->next);
+ mem1 = tmp_otp->value;
+ mem2 = pmt->value;
+ tmp_otp->value = mem1 - tmp->osd_length - tmp->osd_kerning;
+ pmt->value = mem2 + tmp->osd_length + pmt->ott->osd_kerning;
+ pmt->ott = tmp;
+ }//~merging
+ }//~while(exit == 0)
+ }//~if(otp->next!=NULL)
+#endif
+
+ // adding otp (containing splitted lines) to otp chain
+ if (otp_sub == NULL) {
+ otp_sub = otp;
+ for (otp_sub_tmp = otp_sub; otp_sub_tmp->next != NULL; otp_sub_tmp = otp_sub_tmp->next);
+ } else {
+ //updating ott chain
+ tmp = otp_sub->ott;
+ while (tmp->next != NULL) tmp = tmp->next;
+ tmp->next = otp->ott;
+ otp->ott->prev = tmp;
+ //attaching new subtitle line at the end
+ otp_sub_tmp->next = otp;
+ otp->prev = otp_sub_tmp;
+ do
+ otp_sub_tmp = otp_sub_tmp->next;
+ while (otp_sub_tmp->next != NULL);
+ }
+ }//~ if(osl != NULL)
+ } // while
+
+ // write lines into utbl
+ xtblc = 0;
+ utblc = 0;
+ obj->y = dys;
+ obj->params.subtitle.lines = 0;
+ for (tmp_otp = otp_sub; tmp_otp != NULL; tmp_otp = tmp_otp->next) {
+
+ if ((obj->params.subtitle.lines++) >= MAX_UCSLINES)
+ break;
+
+ if (h > obj->y) { // out of the screen so end parsing
+ obj->y -= lasth - sub_font->height; // correct the y position
+ break;
+ }
+ xsize = tmp_otp->value;
+ obj->params.subtitle.xtbl[xtblc++] = (dxs - xsize) / 2;
+ if (xmin > (dxs - xsize) / 2)
+ xmin = (dxs - xsize) / 2;
+ if (xmax < (dxs + xsize) / 2)
+ xmax = (dxs + xsize) / 2;
+
+ tmp = (tmp_otp->next == NULL) ? NULL : tmp_otp->next->ott;
+ for (tmp_ott = tmp_otp->ott; tmp_ott != tmp; tmp_ott = tmp_ott->next) {
+ for (counter = 0; counter < tmp_ott->text_length; ++counter) {
+ if (utblc > MAX_UCS) {
+ break;
+ }
+ c = tmp_ott->text[counter];
+ render_one_glyph(sub_font, c);
+ obj->params.subtitle.utbl[utblc++] = c;
+ k++;
+ }
+ obj->params.subtitle.utbl[utblc++] = ' ';
+ }
+ obj->params.subtitle.utbl[utblc - 1] = 0;
+ obj->y -= sub_font->height;
+ }
+ if(obj->params.subtitle.lines)
+ obj->y = dys - ((obj->params.subtitle.lines - 1) * sub_font->height + sub_font->pic_a[sub_font->font[40]]->h);
+
+ // free memory
+ if (otp_sub != NULL) {
+ for (tmp = otp_sub->ott; tmp->next != NULL; free(tmp->prev)) {
+ free(tmp->text);
+ tmp = tmp->next;
+ }
+ free(tmp->text);
+ free(tmp);
+
+ for(pmt = otp_sub; pmt->next != NULL; free(pmt->prev)) {
+ pmt = pmt->next;
+ }
+ free(pmt);
+ }
+
+ }
+ /// vertical alignment
+ h = dys - obj->y;
+ if (sub_alignment == 2)
+ obj->y = dys * sub_pos / 100 - h;
+ else if (sub_alignment == 1)
+ obj->y = dys * sub_pos / 100 - h / 2;
+ else
+ obj->y = dys * sub_pos / 100;
+
+ if (obj->y < 0)
+ obj->y = 0;
+ if (obj->y > dys - h)
+ obj->y = dys - h;
+
+ obj->bbox.y2 = obj->y + h;
+
+ // calculate bbox:
+ if (sub_justify) xmin = 10;
+ obj->bbox.x1=xmin;
+ obj->bbox.x2=xmax;
+ obj->bbox.y1=obj->y;
+// obj->bbox.y2=obj->y+obj->params.subtitle.lines*sub_font->height;
+ obj->flags|=OSDFLAG_BBOX;
+
+ alloc_buf(obj);
+
+ y = obj->y;
+
+ obj->alignment = 0;
+ switch(vo_sub->alignment) {
+ case SUB_ALIGNMENT_BOTTOMLEFT:
+ case SUB_ALIGNMENT_MIDDLELEFT:
+ case SUB_ALIGNMENT_TOPLEFT:
+ obj->alignment |= 0x1;
+ break;
+ case SUB_ALIGNMENT_BOTTOMRIGHT:
+ case SUB_ALIGNMENT_MIDDLERIGHT:
+ case SUB_ALIGNMENT_TOPRIGHT:
+ obj->alignment |= 0x2;
+ break;
+ case SUB_ALIGNMENT_BOTTOMCENTER:
+ case SUB_ALIGNMENT_MIDDLECENTER:
+ case SUB_ALIGNMENT_TOPCENTER:
+ default:
+ obj->alignment |= 0x0;
+ }
+
+ i=j=0;
+ if ((l = obj->params.subtitle.lines)) {
+ for(counter = dxs; i < l; ++i)
+ if (obj->params.subtitle.xtbl[i] < counter) counter = obj->params.subtitle.xtbl[i];
+ for (i = 0; i < l; ++i) {
+ switch (obj->alignment&0x3) {
+ case 1:
+ // left
+ x = counter;
+ break;
+ case 2:
+ // right
+ x = 2 * obj->params.subtitle.xtbl[i] - counter - ((obj->params.subtitle.xtbl[i] == counter) ? 0 : 1);
+ break;
+ default:
+ //center
+ x = obj->params.subtitle.xtbl[i];
+ }
+ prevc = -1;
+ while ((c=obj->params.subtitle.utbl[j++])){
+ x += kerning(sub_font,prevc,c);
+ if ((font=sub_font->font[c])>=0)
+ draw_alpha_buf(obj,x,y,
+ sub_font->width[c],
+ sub_font->pic_a[font]->h+y<obj->dys ? sub_font->pic_a[font]->h : obj->dys-y,
+ sub_font->pic_b[font]->bmp+sub_font->start[c],
+ sub_font->pic_a[font]->bmp+sub_font->start[c],
+ sub_font->pic_a[font]->w);
+ x+=sub_font->width[c]+sub_font->charspace;
+ prevc = c;
+ }
+ y+=sub_font->height;
+ }
+ }
+
+}
+
+inline static void vo_update_spudec_sub(mp_osd_obj_t* obj, int dxs, int dys)
+{
+ unsigned int bbox[4];
+ spudec_calc_bbox(vo_spudec, dxs, dys, bbox);
+ obj->bbox.x1 = bbox[0];
+ obj->bbox.x2 = bbox[1];
+ obj->bbox.y1 = bbox[2];
+ obj->bbox.y2 = bbox[3];
+ obj->flags |= OSDFLAG_BBOX;
+}
+
+inline static void vo_draw_spudec_sub(mp_osd_obj_t* obj, void (*draw_alpha)(void *ctx, int x0, int y0, int w, int h, unsigned char* src, unsigned char* srca, int stride), void *ctx)
+{
+ spudec_draw_scaled(vo_spudec, obj->dxs, obj->dys, draw_alpha, ctx);
+}
+
+void *vo_spudec=NULL;
+void *vo_vobsub=NULL;
+
+static int draw_alpha_init_flag=0;
+
+void vo_draw_alpha_init(void);
+
+ mp_osd_obj_t* vo_osd_list=NULL;
+
+static mp_osd_obj_t* new_osd_obj(int type){
+ mp_osd_obj_t* osd=malloc(sizeof(mp_osd_obj_t));
+ memset(osd,0,sizeof(mp_osd_obj_t));
+ osd->next=vo_osd_list;
+ vo_osd_list=osd;
+ osd->type=type;
+ osd->alpha_buffer = NULL;
+ osd->bitmap_buffer = NULL;
+ osd->allocated = -1;
+ return osd;
+}
+
+void osd_free(struct osd_state *osd)
+{
+ mp_osd_obj_t* obj=vo_osd_list;
+ while(obj){
+ mp_osd_obj_t* next=obj->next;
+ free(obj->alpha_buffer);
+ free(obj->bitmap_buffer);
+ free(obj);
+ obj=next;
+ }
+ vo_osd_list=NULL;
+ talloc_free(osd);
+}
+
+#define FONT_LOAD_DEFER 6
+
+static int osd_update_ext(struct osd_state *osd, int dxs, int dys,
+ int left_border, int top_border, int right_border,
+ int bottom_border, int orig_w, int orig_h)
+{
+ mp_osd_obj_t* obj=vo_osd_list;
+ int chg=0;
+#ifdef CONFIG_FREETYPE
+ static int defer_counter = 0, prev_dxs = 0, prev_dys = 0;
+#endif
+
+#ifdef CONFIG_FREETYPE
+ // here is the right place to get screen dimensions
+ if (((dxs != vo_image_width)
+ && (subtitle_autoscale == 2 || subtitle_autoscale == 3))
+ || ((dys != vo_image_height)
+ && (subtitle_autoscale == 1 || subtitle_autoscale == 3)))
+ {
+ // screen dimensions changed
+ // wait a while to avoid useless reloading of the font
+ if (dxs == prev_dxs || dys == prev_dys) {
+ defer_counter++;
+ } else {
+ prev_dxs = dxs;
+ prev_dys = dys;
+ defer_counter = 0;
+ }
+ if (defer_counter >= FONT_LOAD_DEFER) force_load_font = 1;
+ }
+
+ if (force_load_font) {
+ force_load_font = 0;
+ load_font_ft(dxs, dys, &vo_font, font_name, osd_font_scale_factor);
+ if (sub_font_name)
+ load_font_ft(dxs, dys, &osd->sub_font, sub_font_name, text_font_scale_factor);
+ else
+ load_font_ft(dxs, dys, &osd->sub_font, font_name, text_font_scale_factor);
+ prev_dxs = dxs;
+ prev_dys = dys;
+ defer_counter = 0;
+ } else {
+ if (!vo_font)
+ load_font_ft(dxs, dys, &vo_font, font_name, osd_font_scale_factor);
+ if (!osd->sub_font) {
+ if (sub_font_name)
+ load_font_ft(dxs, dys, &osd->sub_font, sub_font_name, text_font_scale_factor);
+ else
+ load_font_ft(dxs, dys, &osd->sub_font, font_name, text_font_scale_factor);
+ }
+ }
+#endif
+
+ while(obj){
+ if(dxs!=obj->dxs || dys!=obj->dys || obj->flags&OSDFLAG_FORCE_UPDATE){
+ int vis=obj->flags&OSDFLAG_VISIBLE;
+ obj->flags&=~OSDFLAG_BBOX;
+ switch(obj->type){
+#ifdef CONFIG_DVDNAV
+ case OSDTYPE_DVDNAV:
+ vo_update_nav(obj,dxs,dys, left_border, top_border, right_border, bottom_border, orig_w, orig_h);
+ break;
+#endif
+ case OSDTYPE_SUBTITLE:
+ vo_update_text_sub(osd, obj,dxs,dys);
+ break;
+ case OSDTYPE_TELETEXT:
+ vo_update_text_teletext(obj,dxs,dys);
+ break;
+ case OSDTYPE_PROGBAR:
+ vo_update_text_progbar(obj,dxs,dys);
+ break;
+ case OSDTYPE_SPU:
+ if(sub_visibility && vo_spudec && spudec_visible(vo_spudec)){
+ vo_update_spudec_sub(obj, dxs, dys);
+ obj->flags|=OSDFLAG_VISIBLE|OSDFLAG_CHANGED;
+ }
+ else
+ obj->flags&=~OSDFLAG_VISIBLE;
+ break;
+ case OSDTYPE_OSD:
+ if(vo_font && osd->osd_text[0]){
+ vo_update_text_osd(osd, obj, dxs, dys); // update bbox
+ obj->flags|=OSDFLAG_VISIBLE|OSDFLAG_CHANGED;
+ } else
+ obj->flags&=~OSDFLAG_VISIBLE;
+ break;
+ }
+ // check bbox:
+ if(!(obj->flags&OSDFLAG_BBOX)){
+ // we don't know, so assume the whole screen changed :(
+ obj->bbox.x1=obj->bbox.y1=0;
+ obj->bbox.x2=dxs;
+ obj->bbox.y2=dys;
+ obj->flags|=OSDFLAG_BBOX;
+ } else {
+ // check bbox, reduce it if it's out of bounds (corners):
+ if(obj->bbox.x1<0) obj->bbox.x1=0;
+ if(obj->bbox.y1<0) obj->bbox.y1=0;
+ if(obj->bbox.x2>dxs) obj->bbox.x2=dxs;
+ if(obj->bbox.y2>dys) obj->bbox.y2=dys;
+ if(obj->flags&OSDFLAG_VISIBLE)
+ // debug:
+ mp_msg(MSGT_OSD,MSGL_DBG2,"OSD update: %d;%d %dx%d \n",
+ obj->bbox.x1,obj->bbox.y1,obj->bbox.x2-obj->bbox.x1,
+ obj->bbox.y2-obj->bbox.y1);
+ }
+ // check if visibility changed:
+ if(vis != (obj->flags&OSDFLAG_VISIBLE) ) obj->flags|=OSDFLAG_CHANGED;
+ // remove the cause of automatic update:
+ obj->dxs=dxs; obj->dys=dys;
+ obj->flags&=~OSDFLAG_FORCE_UPDATE;
+ }
+ if(obj->flags&OSDFLAG_CHANGED){
+ chg|=1<<obj->type;
+ mp_msg(MSGT_OSD,MSGL_DBG2,"OSD chg: %d V: %s pb:%d \n",obj->type,(obj->flags&OSDFLAG_VISIBLE)?"yes":"no",vo_osd_progbar_type);
+ }
+ obj=obj->next;
+ }
+ return chg;
+}
+
+int osd_update(struct osd_state *osd, int dxs, int dys)
+{
+ return osd_update_ext(osd, dxs, dys, 0, 0, 0, 0, dxs, dys);
+}
+
+struct osd_state *osd_create(void)
+{
+ struct osd_state *osd = talloc_zero(NULL, struct osd_state);
+ *osd = (struct osd_state){
+ };
+ if(!draw_alpha_init_flag){
+ draw_alpha_init_flag=1;
+ vo_draw_alpha_init();
+ }
+ // temp hack, should be moved to mplayer/mencoder later
+ new_osd_obj(OSDTYPE_OSD);
+ new_osd_obj(OSDTYPE_SUBTITLE);
+ new_osd_obj(OSDTYPE_PROGBAR);
+ new_osd_obj(OSDTYPE_SPU);
+#ifdef CONFIG_DVDNAV
+ new_osd_obj(OSDTYPE_DVDNAV);
+#endif
+ new_osd_obj(OSDTYPE_TELETEXT);
+#ifdef CONFIG_FREETYPE
+ force_load_font = 1;
+#endif
+ return osd;
+}
+
+int vo_osd_changed_flag=0;
+
+void osd_remove_text(struct osd_state *osd, int dxs, int dys,
+ void (*remove)(int x0, int y0, int w, int h))
+{
+ mp_osd_obj_t* obj=vo_osd_list;
+ osd_update(osd, dxs, dys);
+ while(obj){
+ if(((obj->flags&OSDFLAG_CHANGED) || (obj->flags&OSDFLAG_VISIBLE)) &&
+ (obj->flags&OSDFLAG_OLD_BBOX)){
+ int w=obj->old_bbox.x2-obj->old_bbox.x1;
+ int h=obj->old_bbox.y2-obj->old_bbox.y1;
+ if(w>0 && h>0){
+ vo_osd_changed_flag=obj->flags&OSDFLAG_CHANGED; // temp hack
+ remove(obj->old_bbox.x1,obj->old_bbox.y1,w,h);
+ }
+// obj->flags&=~OSDFLAG_OLD_BBOX;
+ }
+ obj=obj->next;
+ }
+}
+
+void osd_draw_text_ext(struct osd_state *osd, int dxs, int dys,
+ int left_border, int top_border, int right_border,
+ int bottom_border, int orig_w, int orig_h,
+ void (*draw_alpha)(void *ctx, int x0, int y0, int w,
+ int h, unsigned char* src,
+ unsigned char *srca,
+ int stride),
+ void *ctx)
+{
+ mp_osd_obj_t* obj=vo_osd_list;
+ osd_update_ext(osd, dxs, dys, left_border, top_border, right_border,
+ bottom_border, orig_w, orig_h);
+ while(obj){
+ if(obj->flags&OSDFLAG_VISIBLE){
+ vo_osd_changed_flag=obj->flags&OSDFLAG_CHANGED; // temp hack
+ switch(obj->type){
+ case OSDTYPE_SPU:
+ vo_draw_spudec_sub(obj, draw_alpha, ctx); // FIXME
+ break;
+#ifdef CONFIG_DVDNAV
+ case OSDTYPE_DVDNAV:
+#endif
+ case OSDTYPE_TELETEXT:
+ case OSDTYPE_OSD:
+ case OSDTYPE_SUBTITLE:
+ case OSDTYPE_PROGBAR:
+ vo_draw_text_from_buffer(obj, draw_alpha, ctx);
+ break;
+ }
+ obj->old_bbox=obj->bbox;
+ obj->flags|=OSDFLAG_OLD_BBOX;
+ }
+ obj->flags&=~OSDFLAG_CHANGED;
+ obj=obj->next;
+ }
+}
+
+void osd_draw_text(struct osd_state *osd, int dxs, int dys,
+ void (*draw_alpha)(void *ctx, int x0, int y0, int w, int h,
+ unsigned char* src, unsigned char *srca,
+ int stride),
+ void *ctx)
+{
+ osd_draw_text_ext(osd, dxs, dys, 0, 0, 0, 0, dxs, dys, draw_alpha, ctx);
+}
+
+static int vo_osd_changed_status = 0;
+
+int vo_osd_changed(int new_value)
+{
+ mp_osd_obj_t* obj=vo_osd_list;
+ int ret = vo_osd_changed_status;
+ vo_osd_changed_status = new_value;
+
+ while(obj){
+ if(obj->type==new_value) obj->flags|=OSDFLAG_FORCE_UPDATE;
+ obj=obj->next;
+ }
+
+ return ret;
+}
+
+// BBBBBBBBBBBB AAAAAAAAAAAAA BBBBBBBBBBB
+// BBBBBBBBBBBB BBBBBBBBBBBBB
+// BBBBBBB
+
+// return TRUE if we have osd in the specified rectangular area:
+int vo_osd_check_range_update(int x1,int y1,int x2,int y2){
+ mp_osd_obj_t* obj=vo_osd_list;
+ while(obj){
+ if(obj->flags&OSDFLAG_VISIBLE){
+ if( (obj->bbox.x1<=x2 && obj->bbox.x2>=x1) &&
+ (obj->bbox.y1<=y2 && obj->bbox.y2>=y1) &&
+ obj->bbox.y2 > obj->bbox.y1 && obj->bbox.x2 > obj->bbox.x1
+ ) return 1;
+ }
+ obj=obj->next;
+ }
+ return 0;
+}
diff --git a/sub/sub.h b/sub/sub.h
new file mode 100644
index 0000000000..8309613ea6
--- /dev/null
+++ b/sub/sub.h
@@ -0,0 +1,167 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_SUB_H
+#define MPLAYER_SUB_H
+
+#include <stdbool.h>
+
+typedef struct mp_osd_bbox_s {
+ int x1,y1,x2,y2;
+} mp_osd_bbox_t;
+
+#define OSDTYPE_OSD 1
+#define OSDTYPE_SUBTITLE 2
+#define OSDTYPE_PROGBAR 3
+#define OSDTYPE_SPU 4
+#define OSDTYPE_DVDNAV 5
+#define OSDTYPE_TELETEXT 6
+
+#define OSDFLAG_VISIBLE 1
+#define OSDFLAG_CHANGED 2
+#define OSDFLAG_BBOX 4
+#define OSDFLAG_OLD_BBOX 8
+#define OSDFLAG_FORCE_UPDATE 16
+
+#define MAX_UCS 1600
+#define MAX_UCSLINES 16
+
+typedef struct mp_osd_obj_s {
+ struct mp_osd_obj_s* next;
+ unsigned char type;
+ unsigned char alignment; // 2 bits: x;y percentages, 2 bits: x;y relative to parent; 2 bits: alignment left/right/center
+ unsigned short flags;
+ int x,y;
+ int dxs,dys;
+ mp_osd_bbox_t bbox; // bounding box
+ mp_osd_bbox_t old_bbox; // the renderer will save bbox here
+ union {
+ struct {
+ void* sub; // value of vo_sub at last update
+ int utbl[MAX_UCS+1]; // subtitle text
+ int xtbl[MAX_UCSLINES]; // x positions
+ int lines; // no. of lines
+ } subtitle;
+ struct {
+ int elems;
+ } progbar;
+ } params;
+ int stride;
+
+ int allocated;
+ unsigned char *alpha_buffer;
+ unsigned char *bitmap_buffer;
+} mp_osd_obj_t;
+
+struct osd_state {
+ unsigned char osd_text[128];
+ struct font_desc *sub_font;
+ struct ass_track *ass_track;
+ bool ass_track_changed;
+ bool vsfilter_aspect;
+};
+
+#include "subreader.h"
+
+extern subtitle* vo_sub;
+
+extern void* vo_osd_teletext_page;
+extern int vo_osd_teletext_half;
+extern int vo_osd_teletext_mode;
+extern int vo_osd_teletext_format;
+
+extern int vo_osd_progbar_type;
+extern int vo_osd_progbar_value; // 0..255
+
+extern void* vo_spudec;
+extern void* vo_vobsub;
+
+#define OSD_PLAY 0x01
+#define OSD_PAUSE 0x02
+#define OSD_STOP 0x03
+#define OSD_REW 0x04
+#define OSD_FFW 0x05
+#define OSD_CLOCK 0x06
+#define OSD_CONTRAST 0x07
+#define OSD_SATURATION 0x08
+#define OSD_VOLUME 0x09
+#define OSD_BRIGHTNESS 0x0A
+#define OSD_HUE 0x0B
+#define OSD_BALANCE 0x0C
+#define OSD_PANSCAN 0x50
+
+#define OSD_PB_START 0x10
+#define OSD_PB_0 0x11
+#define OSD_PB_END 0x12
+#define OSD_PB_1 0x13
+
+/* now in textform */
+extern char * const sub_osd_names[];
+extern char * const sub_osd_names_short[];
+
+extern int sub_unicode;
+extern int sub_utf8;
+
+extern char *sub_cp;
+extern int sub_pos;
+extern int sub_width_p;
+extern int sub_alignment;
+extern int sub_visibility;
+extern int sub_bg_color; /* subtitles background color */
+extern int sub_bg_alpha;
+extern int spu_alignment;
+extern int spu_aamode;
+extern float spu_gaussvar;
+
+void osd_draw_text(struct osd_state *osd, int dxs, int dys,
+ void (*draw_alpha)(void *ctx, int x0, int y0, int w, int h,
+ unsigned char* src, unsigned char *srca,
+ int stride),
+ void *ctx);
+void osd_draw_text_ext(struct osd_state *osd, int dxs, int dys,
+ int left_border, int top_border, int right_border,
+ int bottom_border, int orig_w, int orig_h,
+ void (*draw_alpha)(void *ctx, int x0, int y0, int w,
+ int h, unsigned char* src,
+ unsigned char *srca,
+ int stride),
+ void *ctx);
+void osd_remove_text(struct osd_state *osd, int dxs, int dys,
+ void (*remove)(int x0, int y0, int w, int h));
+
+struct osd_state *osd_create(void);
+int osd_update(struct osd_state *osd, int dxs, int dys);
+int vo_osd_changed(int new_value);
+int vo_osd_check_range_update(int,int,int,int);
+void osd_free(struct osd_state *osd);
+
+extern int vo_osd_changed_flag;
+
+unsigned utf8_get_char(const char **str);
+
+#ifdef CONFIG_DVDNAV
+#include <inttypes.h>
+void osd_set_nav_box (uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey);
+#endif
+
+
+#ifdef IS_OLD_VO
+#define vo_remove_text(...) osd_remove_text(global_osd, __VA_ARGS__)
+#endif
+
+#endif /* MPLAYER_SUB_H */
diff --git a/sub/sub_cc.c b/sub/sub_cc.c
new file mode 100644
index 0000000000..f18fa28c54
--- /dev/null
+++ b/sub/sub_cc.c
@@ -0,0 +1,347 @@
+/*
+ * decoder for Closed Captions
+ *
+ * This decoder relies on MPlayer's OSD to display subtitles.
+ * Be warned that decoding is somewhat preliminary, though it basically works.
+ *
+ * Most notably, only the text information is decoded as of now, discarding
+ * color, background and position info (see source below).
+ *
+ * uses source from the xine closed captions decoder
+ *
+ * Copyright (C) 2002 Matteo Giani
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "sub_cc.h"
+
+#include "subreader.h"
+
+#include "libvo/video_out.h"
+#include "sub.h"
+
+
+#define CC_MAX_LINE_LENGTH 64
+
+static char chartbl[128];
+
+static subtitle buf1,buf2;
+static subtitle *fb,*bb;
+
+static unsigned int cursor_pos=0;
+
+static int initialized=0;
+
+#define CC_ROLLON 1
+#define CC_ROLLUP 2
+
+static int cc_mode=CC_ROLLON;
+static int cc_lines=4; ///< number of visible rows in CC roll-up mode, not used in CC roll-on mode
+
+static void build_char_table(void)
+{
+ int i;
+ /* first the normal ASCII codes */
+ for (i = 0; i < 128; i++)
+ chartbl[i] = (char) i;
+ /* now the special codes */
+ chartbl[0x2a] = 'á';
+ chartbl[0x5c] = 'é';
+ chartbl[0x5e] = 'í';
+ chartbl[0x5f] = 'ó';
+ chartbl[0x60] = 'ú';
+ chartbl[0x7b] = 'ç';
+ chartbl[0x7c] = '÷';
+ chartbl[0x7d] = 'Ñ';
+ chartbl[0x7e] = 'ñ';
+ chartbl[0x7f] = '¤'; /* FIXME: this should be a solid block */
+}
+
+static void clear_buffer(subtitle *buf)
+{
+ int i;
+ buf->lines=0;
+ for (i = 0; i < SUB_MAX_TEXT; i++) {
+ free(buf->text[i]);
+ buf->text[i] = NULL;
+ }
+}
+
+
+/**
+ \brief scroll buffer one line up
+ \param buf buffer to scroll
+*/
+static void scroll_buffer(subtitle* buf)
+{
+ int i;
+
+ while(buf->lines > cc_lines)
+ {
+ free(buf->text[0]);
+
+ for(i = 0; i < buf->lines - 1; i++) buf->text[i] = buf->text[i+1];
+
+ buf->text[buf->lines-1] = NULL;
+ buf->lines--;
+ }
+}
+
+static int channel;
+
+void subcc_init(void)
+{
+ int i;
+ //printf("subcc_init(): initing...\n");
+ build_char_table();
+ for(i=0;i<SUB_MAX_TEXT;i++) {buf1.text[i]=buf2.text[i]=NULL;}
+ buf1.lines=buf2.lines=0;
+ fb=&buf1;
+ bb=&buf2;
+ channel = -1;
+
+ initialized=1;
+}
+
+
+static void display_buffer(subtitle *buf)
+{
+ vo_sub = buf;
+ vo_osd_changed(OSDTYPE_SUBTITLE);
+}
+
+
+static void append_char(char c)
+{
+ if(!bb->lines) {bb->lines++; cursor_pos=0;}
+ if(bb->text[bb->lines - 1]==NULL)
+ {
+ bb->text[bb->lines - 1] = calloc(1, CC_MAX_LINE_LENGTH);
+ cursor_pos=0;
+ }
+
+ if(c=='\n')
+ {
+ if(cursor_pos>0 && bb->lines < SUB_MAX_TEXT)
+ {
+ bb->lines++;cursor_pos=0;
+ if(cc_mode==CC_ROLLUP){ //Carriage return - scroll buffer one line up
+ bb->text[bb->lines - 1]=calloc(1, CC_MAX_LINE_LENGTH);
+ scroll_buffer(bb);
+ }
+ }
+ }
+ else
+ {
+ if(cursor_pos==CC_MAX_LINE_LENGTH-1)
+ {
+ fprintf(stderr,"CC: append_char() reached CC_MAX_LINE_LENGTH!\n");
+ return;
+ }
+ bb->text[bb->lines - 1][cursor_pos++]=c;
+ }
+ //In CC roll-up mode data should be shown immediately
+ if(cc_mode==CC_ROLLUP) display_buffer(bb);
+}
+
+
+static void swap_buffers(void)
+{
+ subtitle *foo;
+ foo=fb;
+ fb=bb;
+ bb=foo;
+}
+
+static int selected_channel(void)
+{
+ return subcc_enabled - 1;
+}
+
+static void cc_decode_EIA608(unsigned short int data)
+{
+
+ static unsigned short int lastcode=0x0000;
+ uint8_t c1 = data & 0x7f;
+ uint8_t c2 = (data >> 8) & 0x7f;
+
+ if (c1 & 0x60) { /* normal character, 0x20 <= c1 <= 0x7f */
+ if (channel != (selected_channel() & 1))
+ return;
+ append_char(chartbl[c1]);
+ if(c2 & 0x60) /*c2 might not be a normal char even if c1 is*/
+ append_char(chartbl[c2]);
+ }
+ else if (c1 & 0x10) // control code / special char
+ {
+ channel = (c1 & 0x08) >> 3;
+ if (channel != (selected_channel() & 1))
+ return;
+ c1&=~0x08;
+ if(data!=lastcode)
+ {
+ if(c2 & 0x40) { /*PAC, Preamble Address Code */
+ append_char('\n'); /*FIXME properly interpret PACs*/
+ }
+ else
+ switch(c1)
+ {
+ case 0x10: break; // ext attribute
+ case 0x11:
+ if((c2 & 0x30)==0x30)
+ {
+ //printf("[debug]:Special char (ignored)\n");
+ /*cc_decode_special_char()*/;
+ }
+ else if (c2 & 0x20)
+ {
+ //printf("[debug]: midrow_attr (ignored)\n");
+ /*cc_decode_midrow_attr()*/;
+ }
+ break;
+ case 0x14:
+ switch(c2)
+ {
+ case 0x00: //CC roll-on mode
+ cc_mode=CC_ROLLON;
+ break;
+ case 0x25: //CC roll-up, 2 rows
+ case 0x26: //CC roll-up, 3 rows
+ case 0x27: //CC roll-up, 4 rows
+ cc_lines=c2-0x23;
+ cc_mode=CC_ROLLUP;
+ break;
+ case 0x2C: display_buffer(NULL); //EDM
+ clear_buffer(fb); break;
+ case 0x2d: append_char('\n'); //carriage return
+ break;
+ case 0x2e: clear_buffer(bb); //ENM
+ break;
+ case 0x2f: swap_buffers(); //Swap buffers
+ display_buffer(fb);
+ clear_buffer(bb);
+ break;
+ }
+ break;
+ case 0x17:
+ if( c2>=0x21 && c2<=0x23) //TAB
+ {
+ break;
+ }
+ }
+ }
+ }
+ lastcode=data;
+}
+
+static void subcc_decode(const uint8_t *inputbuffer, unsigned int inputlength)
+{
+ /* The first number may denote a channel number. I don't have the
+ * EIA-708 standard, so it is hard to say.
+ * From what I could figure out so far, the general format seems to be:
+ *
+ * repeat
+ *
+ * 0xfe starts 2 byte sequence of unknown purpose. It might denote
+ * field #2 in line 21 of the VBI.
+ * Treating it identical of 0xff fixes
+ * http://samples.mplayerhq.hu/MPEG-VOB/ClosedCaptions/Starship_Troopers.vob
+ *
+ * 0xff starts 2 byte EIA-608 sequence, field #1 in line 21 of the VBI.
+ * Followed by a 3-code triplet that starts either with 0xff or
+ * 0xfe. In either case, the following triplet needs to be ignored
+ * for line 21, field 1.
+ *
+ * 0x00 is padding, followed by 2 more 0x00.
+ *
+ * 0x01 always seems to appear at the beginning, always seems to
+ * be followed by 0xf8, 8-bit number.
+ * The lower 7 bits of this 8-bit number seem to denote the
+ * number of code triplets that follow.
+ * The most significant bit denotes whether the Line 21 field 1
+ * captioning information is at odd or even triplet offsets from this
+ * beginning triplet. 1 denotes odd offsets, 0 denotes even offsets.
+ *
+ * Most captions are encoded with odd offsets, so this is what we
+ * will assume.
+ *
+ * until end of packet
+ */
+ const uint8_t *current = inputbuffer;
+ unsigned int curbytes = 0;
+ uint8_t data1, data2;
+ uint8_t cc_code;
+ int odd_offset = 1;
+
+ while (curbytes < inputlength) {
+ cc_code = current[0];
+
+ if (inputlength - curbytes < 2) {
+#ifdef LOG_DEBUG
+ fprintf(stderr, "Not enough data for 2-byte CC encoding\n");
+#endif
+ break;
+ }
+
+ data1 = current[1];
+ data2 = current[2];
+ current += 3; curbytes += 3;
+
+ switch (cc_code) {
+ case 0xfe:
+ case 0xff:
+ odd_offset ^= 1;
+ if (odd_offset != selected_channel() >> 1)
+ break;
+ /* expect EIA-608 CC1/CC2 encoding */
+ // FIXME check parity!
+ // Parity check omitted assuming we are reading from a DVD and therefore
+ // we should encounter no "transmission errors".
+ cc_decode_EIA608(data1 | (data2 << 8));
+ break;
+
+ case 0x00:
+ /* This seems to be just padding */
+ break;
+
+ case 0x01:
+ odd_offset = data2 >> 7;
+ break;
+
+ default:
+//#ifdef LOG_DEBUG
+ fprintf(stderr, "Unknown CC encoding: %x\n", cc_code);
+//#endif
+ break;
+ }
+ }
+}
+
+
+void subcc_process_data(const uint8_t *inputdata, unsigned int len)
+{
+ if(!subcc_enabled) return;
+ if(!initialized) subcc_init();
+
+ subcc_decode(inputdata, len);
+}
diff --git a/sub/sub_cc.h b/sub/sub_cc.h
new file mode 100644
index 0000000000..f5f6924057
--- /dev/null
+++ b/sub/sub_cc.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_SUB_CC_H
+#define MPLAYER_SUB_CC_H
+
+#include <stdint.h>
+
+extern int subcc_enabled;
+
+void subcc_init(void);
+void subcc_process_data(const uint8_t *inputdata, unsigned int len);
+
+#endif /* MPLAYER_SUB_CC_H */
diff --git a/sub/subreader.c b/sub/subreader.c
new file mode 100644
index 0000000000..1f9294ec47
--- /dev/null
+++ b/sub/subreader.c
@@ -0,0 +1,2504 @@
+/*
+ * Subtitle reader with format autodetection
+ *
+ * Copyright (c) 2001 laaz
+ * Some code cleanup & realloc() by A'rpi/ESP-team
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "config.h"
+#include "mp_msg.h"
+#include "subreader.h"
+#include "mpcommon.h"
+#include "subassconvert.h"
+#include "options.h"
+#include "stream/stream.h"
+#include "libavutil/common.h"
+#include "libavutil/avstring.h"
+
+#ifdef CONFIG_ENCA
+#include <enca.h>
+#endif
+
+#define ERR ((void *) -1)
+
+#ifdef CONFIG_ICONV
+#include <iconv.h>
+char *sub_cp=NULL;
+#endif
+#ifdef CONFIG_FRIBIDI
+#include <fribidi/fribidi.h>
+char *fribidi_charset = NULL; ///character set that will be passed to FriBiDi
+int flip_hebrew = 1; ///flip subtitles using fribidi
+int fribidi_flip_commas = 0; ///flip comma when fribidi is used
+#endif
+
+// Parameter struct for the format-specific readline functions
+struct readline_args {
+ int utf16;
+ struct MPOpts *opts;
+};
+
+/* Maximal length of line of a subtitle */
+#define LINE_LEN 1000
+static float mpsub_position=0;
+static float mpsub_multiplier=1.;
+static int sub_slacktime = 20000; //20 sec
+
+int sub_no_text_pp=0; // 1 => do not apply text post-processing
+ // like {\...} elimination in SSA format.
+
+int sub_match_fuzziness=0; // level of sub name matching fuzziness
+
+/* Use the SUB_* constant defined in the header file */
+int sub_format=SUB_INVALID;
+#ifdef CONFIG_SORTSUB
+/*
+ Some subtitling formats, namely AQT and Subrip09, define the end of a
+ subtitle as the beginning of the following. Since currently we read one
+ subtitle at time, for these format we keep two global *subtitle,
+ previous_aqt_sub and previous_subrip09_sub, pointing to previous subtitle,
+ so we can change its end when we read current subtitle starting time.
+ When CONFIG_SORTSUB is defined, we use a single global unsigned long,
+ previous_sub_end, for both (and even future) formats, to store the end of
+ the previous sub: it is initialized to 0 in sub_read_file and eventually
+ modified by sub_read_aqt_line or sub_read_subrip09_line.
+ */
+unsigned long previous_sub_end;
+#endif
+
+static int eol(char p) {
+ return p=='\r' || p=='\n' || p=='\0';
+}
+
+/* Remove leading and trailing space */
+static void trail_space(char *s) {
+ int i = 0;
+ while (isspace(s[i])) ++i;
+ if (i) strcpy(s, s + i);
+ i = strlen(s) - 1;
+ while (i > 0 && isspace(s[i])) s[i--] = '\0';
+}
+
+static char *stristr(const char *haystack, const char *needle) {
+ int len = 0;
+ const char *p = haystack;
+
+ if (!(haystack && needle)) return NULL;
+
+ len=strlen(needle);
+ while (*p != '\0') {
+ if (strncasecmp(p, needle, len) == 0) return (char*)p;
+ p++;
+ }
+
+ return NULL;
+}
+
+static void sami_add_line(subtitle *current, char *buffer, char **pos) {
+ char *p = *pos;
+ *p = 0;
+ trail_space(buffer);
+ if (*buffer && current->lines < SUB_MAX_TEXT)
+ current->text[current->lines++] = strdup(buffer);
+ *pos = buffer;
+}
+
+static subtitle *sub_read_line_sami(stream_t* st, subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ static char line[LINE_LEN+1];
+ static char *s = NULL, *slacktime_s;
+ char text[LINE_LEN+1], *p=NULL, *q;
+ int state;
+
+ current->lines = current->start = current->end = 0;
+ current->alignment = SUB_ALIGNMENT_BOTTOMCENTER;
+ state = 0;
+
+ /* read the first line */
+ if (!s)
+ if (!(s = stream_read_line(st, line, LINE_LEN, utf16))) return 0;
+
+ do {
+ switch (state) {
+
+ case 0: /* find "START=" or "Slacktime:" */
+ slacktime_s = stristr (s, "Slacktime:");
+ if (slacktime_s)
+ sub_slacktime = strtol (slacktime_s+10, NULL, 0) / 10;
+
+ s = stristr (s, "Start=");
+ if (s) {
+ current->start = strtol (s + 6, &s, 0) / 10;
+ /* eat '>' */
+ for (; *s != '>' && *s != '\0'; s++);
+ s++;
+ state = 1; continue;
+ }
+ break;
+
+ case 1: /* find (optional) "<P", skip other TAGs */
+ for (; *s == ' ' || *s == '\t'; s++); /* strip blanks, if any */
+ if (*s == '\0') break;
+ if (*s != '<') { state = 3; p = text; continue; } /* not a TAG */
+ s++;
+ if (*s == 'P' || *s == 'p') { s++; state = 2; continue; } /* found '<P' */
+ for (; *s != '>' && *s != '\0'; s++); /* skip remains of non-<P> TAG */
+ if (s == '\0')
+ break;
+ s++;
+ continue;
+
+ case 2: /* find ">" */
+ if ((s = strchr (s, '>'))) { s++; state = 3; p = text; continue; }
+ break;
+
+ case 3: /* get all text until '<' appears */
+ if (*s == '\0') break;
+ else if (!strncasecmp (s, "<br>", 4)) {
+ sami_add_line(current, text, &p);
+ s += 4;
+ }
+ else if ((*s == '{') && !sub_no_text_pp) { state = 5; ++s; continue; }
+ else if (*s == '<') { state = 4; }
+ else if (!strncasecmp (s, "&nbsp;", 6)) { *p++ = ' '; s += 6; }
+ else if (*s == '\t') { *p++ = ' '; s++; }
+ else if (*s == '\r' || *s == '\n') { s++; }
+ else *p++ = *s++;
+
+ /* skip duplicated space */
+ if (p > text + 2) if (*(p-1) == ' ' && *(p-2) == ' ') p--;
+
+ continue;
+
+ case 4: /* get current->end or skip <TAG> */
+ q = stristr (s, "Start=");
+ if (q) {
+ current->end = strtol (q + 6, &q, 0) / 10 - 1;
+ *p = '\0'; trail_space (text);
+ if (text[0] != '\0')
+ current->text[current->lines++] = strdup (text);
+ if (current->lines > 0) { state = 99; break; }
+ state = 0; continue;
+ }
+ s = strchr (s, '>');
+ if (s) { s++; state = 3; continue; }
+ break;
+ case 5: /* get rid of {...} text, but read the alignment code */
+ if ((*s == '\\') && (*(s + 1) == 'a') && !sub_no_text_pp) {
+ if (stristr(s, "\\a1") != NULL) {
+ current->alignment = SUB_ALIGNMENT_BOTTOMLEFT;
+ s = s + 3;
+ }
+ if (stristr(s, "\\a2") != NULL) {
+ current->alignment = SUB_ALIGNMENT_BOTTOMCENTER;
+ s = s + 3;
+ } else if (stristr(s, "\\a3") != NULL) {
+ current->alignment = SUB_ALIGNMENT_BOTTOMRIGHT;
+ s = s + 3;
+ } else if ((stristr(s, "\\a4") != NULL) || (stristr(s, "\\a5") != NULL) || (stristr(s, "\\a8") != NULL)) {
+ current->alignment = SUB_ALIGNMENT_TOPLEFT;
+ s = s + 3;
+ } else if (stristr(s, "\\a6") != NULL) {
+ current->alignment = SUB_ALIGNMENT_TOPCENTER;
+ s = s + 3;
+ } else if (stristr(s, "\\a7") != NULL) {
+ current->alignment = SUB_ALIGNMENT_TOPRIGHT;
+ s = s + 3;
+ } else if (stristr(s, "\\a9") != NULL) {
+ current->alignment = SUB_ALIGNMENT_MIDDLELEFT;
+ s = s + 3;
+ } else if (stristr(s, "\\a10") != NULL) {
+ current->alignment = SUB_ALIGNMENT_MIDDLECENTER;
+ s = s + 4;
+ } else if (stristr(s, "\\a11") != NULL) {
+ current->alignment = SUB_ALIGNMENT_MIDDLERIGHT;
+ s = s + 4;
+ }
+ }
+ if (*s == '}') state = 3;
+ ++s;
+ continue;
+ }
+
+ /* read next line */
+ if (state != 99 && !(s = stream_read_line (st, line, LINE_LEN, utf16))) {
+ if (current->start > 0) {
+ break; // if it is the last subtitle
+ } else {
+ return 0;
+ }
+ }
+
+ } while (state != 99);
+
+ // For the last subtitle
+ if (current->end <= 0) {
+ current->end = current->start + sub_slacktime;
+ sami_add_line(current, text, &p);
+ }
+
+ return current;
+}
+
+
+static char *sub_readtext(char *source, char **dest) {
+ int len=0;
+ char *p=source;
+
+// printf("src=%p dest=%p \n",source,dest);
+
+ while ( !eol(*p) && *p!= '|' ) {
+ p++,len++;
+ }
+
+ *dest= malloc (len+1);
+ if (!dest) {return ERR;}
+
+ strncpy(*dest, source, len);
+ (*dest)[len]=0;
+
+ while (*p=='\r' || *p=='\n' || *p=='|') p++;
+
+ if (*p) return p; // not-last text field
+ else return NULL; // last text field
+}
+
+static subtitle *sub_read_line_microdvd(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ char line2[LINE_LEN+1];
+ char *p, *next;
+ int i;
+
+ do {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ } while ((sscanf (line,
+ "{%ld}{}%[^\r\n]",
+ &(current->start), line2) < 2) &&
+ (sscanf (line,
+ "{%ld}{%ld}%[^\r\n]",
+ &(current->start), &(current->end), line2) < 3));
+
+ if (args->opts->ass_enabled) {
+ subassconvert_microdvd(line2, line, LINE_LEN + 1);
+ p = line;
+ } else
+ p = line2;
+
+ next=p, i=0;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) {return ERR;}
+ i++;
+ if (i>=SUB_MAX_TEXT) { mp_msg(MSGT_SUBREADER,MSGL_WARN,"Too many lines in a subtitle\n");current->lines=i;return current;}
+ }
+ current->lines= ++i;
+
+ return current;
+}
+
+static subtitle *sub_read_line_mpl2(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ char line2[LINE_LEN+1];
+ char *p, *next;
+ int i;
+
+ do {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ } while ((sscanf (line,
+ "[%ld][%ld]%[^\r\n]",
+ &(current->start), &(current->end), line2) < 3));
+ current->start *= 10;
+ current->end *= 10;
+ p=line2;
+
+ next=p, i=0;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) {return ERR;}
+ i++;
+ if (i>=SUB_MAX_TEXT) { mp_msg(MSGT_SUBREADER,MSGL_WARN,"Too many lines in a subtitle\n");current->lines=i;return current;}
+ }
+ current->lines= ++i;
+
+ return current;
+}
+
+static subtitle *sub_read_line_subrip(stream_t* st, subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ int a1,a2,a3,a4,b1,b2,b3,b4;
+ char *p=NULL, *q=NULL;
+ int len;
+
+ while (1) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8) continue;
+ current->start = a1*360000+a2*6000+a3*100+a4;
+ current->end = b1*360000+b2*6000+b3*100+b4;
+
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+
+ p=q=line;
+ for (current->lines=1; current->lines < SUB_MAX_TEXT; current->lines++) {
+ for (q=p,len=0; *p && *p!='\r' && *p!='\n' && *p!='|' && strncmp(p,"[br]",4); p++,len++);
+ current->text[current->lines-1]=malloc (len+1);
+ if (!current->text[current->lines-1]) return ERR;
+ strncpy (current->text[current->lines-1], q, len);
+ current->text[current->lines-1][len]='\0';
+ if (!*p || *p=='\r' || *p=='\n') break;
+ if (*p=='|') p++;
+ else while (*p++!=']');
+ }
+ break;
+ }
+ return current;
+}
+
+static subtitle *sub_ass_read_line_subviewer(stream_t *st, subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ int a1, a2, a3, a4, b1, b2, b3, b4, j = 0;
+
+ while (!current->text[0]) {
+ char line[LINE_LEN + 1], full_line[LINE_LEN + 1], sep;
+ int i;
+
+ /* Parse SubRip header */
+ if (!stream_read_line(st, line, LINE_LEN, utf16))
+ return NULL;
+ if (sscanf(line, "%d:%d:%d%[,.:]%d --> %d:%d:%d%[,.:]%d",
+ &a1, &a2, &a3, &sep, &a4, &b1, &b2, &b3, &sep, &b4) < 10)
+ continue;
+
+ current->start = a1 * 360000 + a2 * 6000 + a3 * 100 + a4 / 10;
+ current->end = b1 * 360000 + b2 * 6000 + b3 * 100 + b4 / 10;
+
+ /* Concat lines */
+ full_line[0] = 0;
+ for (i = 0; i < SUB_MAX_TEXT; i++) {
+ int blank = 1, len = 0;
+ char *p;
+
+ if (!stream_read_line(st, line, LINE_LEN, utf16))
+ break;
+
+ for (p = line; *p != '\n' && *p != '\r' && *p; p++, len++)
+ if (*p != ' ' && *p != '\t')
+ blank = 0;
+
+ if (blank)
+ break;
+
+ *p = 0;
+
+ if (!(j + 1 + len < sizeof(full_line) - 1))
+ break;
+
+ if (j != 0)
+ full_line[j++] = '\n';
+ strcpy(&full_line[j], line);
+ j += len;
+ }
+
+ /* Use the ASS/SSA converter to transform the whole lines */
+ if (full_line[0]) {
+ char converted_line[LINE_LEN + 1];
+ subassconvert_subrip(full_line, converted_line, LINE_LEN + 1);
+ current->text[0] = strdup(converted_line);
+ current->lines = 1;
+ }
+ }
+ return current;
+}
+
+static subtitle *sub_read_line_subviewer(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ int a1,a2,a3,a4,b1,b2,b3,b4;
+ char *p=NULL;
+ int i,len;
+
+ if (args->opts->ass_enabled)
+ return sub_ass_read_line_subviewer(st, current, args);
+ while (!current->text[0]) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ if ((len=sscanf (line, "%d:%d:%d%[,.:]%d --> %d:%d:%d%[,.:]%d",&a1,&a2,&a3,(char *)&i,&a4,&b1,&b2,&b3,(char *)&i,&b4)) < 10)
+ continue;
+ current->start = a1*360000+a2*6000+a3*100+a4/10;
+ current->end = b1*360000+b2*6000+b3*100+b4/10;
+ for (i=0; i<SUB_MAX_TEXT;) {
+ int blank = 1;
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) break;
+ len=0;
+ for (p=line; *p!='\n' && *p!='\r' && *p; p++,len++)
+ if (*p != ' ' && *p != '\t')
+ blank = 0;
+ if (len && !blank) {
+ int j=0,skip=0;
+ char *curptr=current->text[i]=malloc (len+1);
+ if (!current->text[i]) return ERR;
+ //strncpy (current->text[i], line, len); current->text[i][len]='\0';
+ for(; j<len; j++) {
+ /* let's filter html tags ::atmos */
+ if(line[j]=='>') {
+ skip=0;
+ continue;
+ }
+ if(line[j]=='<') {
+ skip=1;
+ continue;
+ }
+ if(skip) {
+ continue;
+ }
+ *curptr=line[j];
+ curptr++;
+ }
+ *curptr='\0';
+
+ i++;
+ } else {
+ break;
+ }
+ }
+ current->lines=i;
+ }
+ return current;
+}
+
+static subtitle *sub_read_line_subviewer2(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ int a1,a2,a3,a4;
+ char *p=NULL;
+ int i,len;
+
+ while (!current->text[0]) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ if (line[0]!='{')
+ continue;
+ if ((len=sscanf (line, "{T %d:%d:%d:%d",&a1,&a2,&a3,&a4)) < 4)
+ continue;
+ current->start = a1*360000+a2*6000+a3*100+a4/10;
+ for (i=0; i<SUB_MAX_TEXT;) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) break;
+ if (line[0]=='}') break;
+ len=0;
+ for (p=line; *p!='\n' && *p!='\r' && *p; ++p,++len);
+ if (len) {
+ current->text[i]=malloc (len+1);
+ if (!current->text[i]) return ERR;
+ strncpy (current->text[i], line, len); current->text[i][len]='\0';
+ ++i;
+ } else {
+ break;
+ }
+ }
+ current->lines=i;
+ }
+ return current;
+}
+
+
+static subtitle *sub_read_line_vplayer(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ int a1,a2,a3;
+ char *p=NULL, *next,separator;
+ int i,len,plen;
+
+ while (!current->text[0]) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ if ((len=sscanf (line, "%d:%d:%d%c%n",&a1,&a2,&a3,&separator,&plen)) < 4)
+ continue;
+
+ if (!(current->start = a1*360000+a2*6000+a3*100))
+ continue;
+ /* removed by wodzu
+ p=line;
+ // finds the body of the subtitle
+ for (i=0; i<3; i++){
+ p=strchr(p,':');
+ if (p==NULL) break;
+ ++p;
+ }
+ if (p==NULL) {
+ printf("SUB: Skipping incorrect subtitle line!\n");
+ continue;
+ }
+ */
+ // by wodzu: hey! this time we know what length it has! what is
+ // that magic for? it can't deal with space instead of third
+ // colon! look, what simple it can be:
+ p = &line[ plen ];
+
+ i=0;
+ if (*p!='|') {
+ //
+ next = p,i=0;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) {return ERR;}
+ i++;
+ if (i>=SUB_MAX_TEXT) { mp_msg(MSGT_SUBREADER,MSGL_WARN,"Too many lines in a subtitle\n");current->lines=i;return current;}
+ }
+ current->lines=i+1;
+ }
+ }
+ return current;
+}
+
+static subtitle *sub_read_line_rt(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+
+ //TODO: This format uses quite rich (sub/super)set of xhtml
+ // I couldn't check it since DTD is not included.
+ // WARNING: full XML parses can be required for proper parsing
+ char line[LINE_LEN+1];
+ int a1,a2,a3,a4,b1,b2,b3,b4;
+ char *p=NULL,*next=NULL;
+ int i,len,plen;
+
+ while (!current->text[0]) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ //TODO: it seems that format of time is not easily determined, it may be 1:12, 1:12.0 or 0:1:12.0
+ //to describe the same moment in time. Maybe there are even more formats in use.
+ //if ((len=sscanf (line, "<Time Begin=\"%d:%d:%d.%d\" End=\"%d:%d:%d.%d\"",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4)) < 8)
+ plen=a1=a2=a3=a4=b1=b2=b3=b4=0;
+ if (
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d.%d\" %*[Ee]nd=\"%d.%d\"%*[^<]<clear/>%n",&a3,&a4,&b3,&b4,&plen)) < 4) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d.%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a3,&a4,&b2,&b3,&b4,&plen)) < 5) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\" %*[Ee]nd=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&b2,&b3,&plen)) < 4) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&b2,&b3,&b4,&plen)) < 5) &&
+// ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\" %*[Ee]nd=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&b2,&b3,&plen)) < 5) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&b2,&b3,&b4,&plen)) < 6) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d:%d.%d\" %*[Ee]nd=\"%d:%d:%d.%d\"%*[^<]<clear/>%n",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4,&plen)) < 8) &&
+ //now try it without end time
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d.%d\"%*[^<]<clear/>%n",&a3,&a4,&plen)) < 2) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&plen)) < 2) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&plen)) < 3) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d:%d.%d\"%*[^<]<clear/>%n",&a1,&a2,&a3,&a4,&plen)) < 4)
+ )
+ continue;
+ current->start = a1*360000+a2*6000+a3*100+a4/10;
+ current->end = b1*360000+b2*6000+b3*100+b4/10;
+ if (b1 == 0 && b2 == 0 && b3 == 0 && b4 == 0)
+ current->end = current->start+200;
+ p=line; p+=plen;i=0;
+ // TODO: I don't know what kind of convention is here for marking multiline subs, maybe <br/> like in xml?
+ next = strstr(line,"<clear/>");
+ if(next && strlen(next)>8){
+ next+=8;i=0;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) {return ERR;}
+ i++;
+ if (i>=SUB_MAX_TEXT) { mp_msg(MSGT_SUBREADER,MSGL_WARN,"Too many lines in a subtitle\n");current->lines=i;return current;}
+ }
+ }
+ current->lines=i+1;
+ }
+ return current;
+}
+
+static subtitle *sub_read_line_ssa(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+/*
+ * Sub Station Alpha v4 (and v2?) scripts have 9 commas before subtitle
+ * other Sub Station Alpha scripts have only 8 commas before subtitle
+ * Reading the "ScriptType:" field is not reliable since many scripts appear
+ * w/o it
+ *
+ * http://www.scriptclub.org is a good place to find more examples
+ * http://www.eswat.demon.co.uk is where the SSA specs can be found
+ */
+ int utf16 = args->utf16;
+ int comma;
+ static int max_comma = 32; /* let's use 32 for the case that the */
+ /* amount of commas increase with newer SSA versions */
+
+ int hour1, min1, sec1, hunsec1,
+ hour2, min2, sec2, hunsec2, nothing;
+ int num;
+
+ char line[LINE_LEN+1],
+ line3[LINE_LEN+1],
+ *line2;
+ char *tmp;
+
+ do {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
+ } while (sscanf (line, "Dialogue: Marked=%d,%d:%d:%d.%d,%d:%d:%d.%d"
+ "%[^\n\r]", &nothing,
+ &hour1, &min1, &sec1, &hunsec1,
+ &hour2, &min2, &sec2, &hunsec2,
+ line3) < 9
+ &&
+ sscanf (line, "Dialogue: %d,%d:%d:%d.%d,%d:%d:%d.%d"
+ "%[^\n\r]", &nothing,
+ &hour1, &min1, &sec1, &hunsec1,
+ &hour2, &min2, &sec2, &hunsec2,
+ line3) < 9 );
+
+ line2=strchr(line3, ',');
+ if (!line2) return NULL;
+
+ for (comma = 4; comma < max_comma; comma ++)
+ {
+ tmp = line2;
+ if(!(tmp=strchr(++tmp, ','))) break;
+ if(*(++tmp) == ' ') break;
+ /* a space after a comma means we're already in a sentence */
+ line2 = tmp;
+ }
+
+ if(comma < max_comma)max_comma = comma;
+ /* eliminate the trailing comma */
+ if(*line2 == ',') line2++;
+
+ current->lines=0;num=0;
+ current->start = 360000*hour1 + 6000*min1 + 100*sec1 + hunsec1;
+ current->end = 360000*hour2 + 6000*min2 + 100*sec2 + hunsec2;
+
+ while (((tmp=strstr(line2, "\\n")) != NULL) || ((tmp=strstr(line2, "\\N")) != NULL) ){
+ current->text[num]=malloc(tmp-line2+1);
+ strncpy (current->text[num], line2, tmp-line2);
+ current->text[num][tmp-line2]='\0';
+ line2=tmp+2;
+ num++;
+ current->lines++;
+ if (current->lines >= SUB_MAX_TEXT) return current;
+ }
+
+ current->text[num]=strdup(line2);
+ current->lines++;
+
+ return current;
+}
+
+static void sub_pp_ssa(subtitle *sub) {
+ int l=sub->lines;
+ char *so,*de,*start;
+
+ while (l){
+ /* eliminate any text enclosed with {}, they are font and color settings */
+ so=de=sub->text[--l];
+ while (*so) {
+ if(*so == '{' && so[1]=='\\') {
+ for (start=so; *so && *so!='}'; so++);
+ if(*so) so++; else so=start;
+ }
+ if(*so) {
+ *de=*so;
+ so++; de++;
+ }
+ }
+ *de=*so;
+ }
+}
+
+/*
+ * PJS subtitles reader.
+ * That's the "Phoenix Japanimation Society" format.
+ * I found some of them in http://www.scriptsclub.org/ (used for anime).
+ * The time is in tenths of second.
+ *
+ * by set, based on code by szabi (dunnowhat sub format ;-)
+ */
+static subtitle *sub_read_line_pjs(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ char text[LINE_LEN+1], *s, *d;
+
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return NULL;
+ /* skip spaces */
+ for (s=line; *s && isspace(*s); s++);
+ /* allow empty lines at the end of the file */
+ if (*s==0)
+ return NULL;
+ /* get the time */
+ if (sscanf (s, "%ld,%ld,", &(current->start),
+ &(current->end)) <2) {
+ return ERR;
+ }
+ /* the files I have are in tenths of second */
+ current->start *= 10;
+ current->end *= 10;
+ /* walk to the beggining of the string */
+ for (; *s; s++) if (*s==',') break;
+ if (*s) {
+ for (s++; *s; s++) if (*s==',') break;
+ if (*s) s++;
+ }
+ if (*s!='"') {
+ return ERR;
+ }
+ /* copy the string to the text buffer */
+ for (s++, d=text; *s && *s!='"'; s++, d++)
+ *d=*s;
+ *d=0;
+ current->text[0] = strdup(text);
+ current->lines = 1;
+
+ return current;
+}
+
+static subtitle *sub_read_line_mpsub(stream_t *st, subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ float a,b;
+ int num=0;
+ char *p, *q;
+
+ do
+ {
+ if (!stream_read_line(st, line, LINE_LEN, utf16)) return NULL;
+ } while (sscanf (line, "%f %f", &a, &b) !=2);
+
+ mpsub_position += a*mpsub_multiplier;
+ current->start=(int) mpsub_position;
+ mpsub_position += b*mpsub_multiplier;
+ current->end=(int) mpsub_position;
+
+ while (num < SUB_MAX_TEXT) {
+ if (!stream_read_line (st, line, LINE_LEN, utf16)) {
+ if (num == 0) return NULL;
+ else return current;
+ }
+ p=line;
+ while (isspace(*p)) p++;
+ if (eol(*p) && num > 0) return current;
+ if (eol(*p)) return NULL;
+
+ for (q=p; !eol(*q); q++);
+ *q='\0';
+ if (strlen(p)) {
+ current->text[num]=strdup(p);
+// printf (">%s<\n",p);
+ current->lines = ++num;
+ } else {
+ if (num) return current;
+ else return NULL;
+ }
+ }
+ return NULL; // we should have returned before if it's OK
+}
+
+#ifndef CONFIG_SORTSUB
+//we don't need this if we use previous_sub_end
+subtitle *previous_aqt_sub = NULL;
+#endif
+
+static subtitle *sub_read_line_aqt(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ char *next;
+ int i;
+
+ while (1) {
+ // try to locate next subtitle
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return NULL;
+ if (!(sscanf (line, "-->> %ld", &(current->start)) <1))
+ break;
+ }
+
+#ifdef CONFIG_SORTSUB
+ previous_sub_end = (current->start) ? current->start - 1 : 0;
+#else
+ if (previous_aqt_sub != NULL)
+ previous_aqt_sub->end = current->start-1;
+
+ previous_aqt_sub = current;
+#endif
+
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return NULL;
+
+ sub_readtext((char *) &line,&current->text[0]);
+ current->lines = 1;
+ current->end = current->start; // will be corrected by next subtitle
+
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return current;
+
+ next = line,i=1;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) {return ERR;}
+ i++;
+ if (i>=SUB_MAX_TEXT) { mp_msg(MSGT_SUBREADER,MSGL_WARN,"Too many lines in a subtitle\n");current->lines=i;return current;}
+ }
+ current->lines=i+1;
+
+ if (!strlen(current->text[0]) && !strlen(current->text[1])) {
+#ifdef CONFIG_SORTSUB
+ previous_sub_end = 0;
+#else
+ // void subtitle -> end of previous marked and exit
+ previous_aqt_sub = NULL;
+#endif
+ return NULL;
+ }
+
+ return current;
+}
+
+#ifndef CONFIG_SORTSUB
+subtitle *previous_subrip09_sub = NULL;
+#endif
+
+static subtitle *sub_read_line_subrip09(stream_t *st,subtitle *current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line[LINE_LEN+1];
+ int a1,a2,a3;
+ char * next=NULL;
+ int i,len;
+
+ while (1) {
+ // try to locate next subtitle
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return NULL;
+ if (!((len=sscanf (line, "[%d:%d:%d]",&a1,&a2,&a3)) < 3))
+ break;
+ }
+
+ current->start = a1*360000+a2*6000+a3*100;
+
+#ifdef CONFIG_SORTSUB
+ previous_sub_end = (current->start) ? current->start - 1 : 0;
+#else
+ if (previous_subrip09_sub != NULL)
+ previous_subrip09_sub->end = current->start-1;
+
+ previous_subrip09_sub = current;
+#endif
+
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return NULL;
+
+ next = line,i=0;
+
+ current->text[0]=""; // just to be sure that string is clear
+
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) {return ERR;}
+ i++;
+ if (i>=SUB_MAX_TEXT) { mp_msg(MSGT_SUBREADER,MSGL_WARN,"Too many lines in a subtitle\n");current->lines=i;return current;}
+ }
+ current->lines=i+1;
+
+ if (!strlen(current->text[0]) && (i==0)) {
+#ifdef CONFIG_SORTSUB
+ previous_sub_end = 0;
+#else
+ // void subtitle -> end of previous marked and exit
+ previous_subrip09_sub = NULL;
+#endif
+ return NULL;
+ }
+
+ return current;
+}
+
+static subtitle *sub_read_line_jacosub(stream_t* st, subtitle * current,
+ struct readline_args *args)
+{
+ int utf16 = args->utf16;
+ char line1[LINE_LEN], line2[LINE_LEN], directive[LINE_LEN], *p, *q;
+ unsigned a1, a2, a3, a4, b1, b2, b3, b4, comment = 0;
+ static unsigned jacoTimeres = 30;
+ static int jacoShift = 0;
+
+ memset(current, 0, sizeof(subtitle));
+ memset(line1, 0, LINE_LEN);
+ memset(line2, 0, LINE_LEN);
+ memset(directive, 0, LINE_LEN);
+ while (!current->text[0]) {
+ if (!stream_read_line(st, line1, LINE_LEN, utf16)) {
+ return NULL;
+ }
+ if (sscanf
+ (line1, "%u:%u:%u.%u %u:%u:%u.%u %[^\n\r]", &a1, &a2, &a3, &a4,
+ &b1, &b2, &b3, &b4, line2) < 9) {
+ if (sscanf(line1, "@%u @%u %[^\n\r]", &a4, &b4, line2) < 3) {
+ if (line1[0] == '#') {
+ int hours = 0, minutes = 0, seconds, delta, inverter =
+ 1;
+ unsigned units = jacoShift;
+ switch (toupper(line1[1])) {
+ case 'S':
+ if (isalpha(line1[2])) {
+ delta = 6;
+ } else {
+ delta = 2;
+ }
+ if (sscanf(&line1[delta], "%d", &hours)) {
+ if (hours < 0) {
+ hours *= -1;
+ inverter = -1;
+ }
+ if (sscanf(&line1[delta], "%*d:%d", &minutes)) {
+ if (sscanf
+ (&line1[delta], "%*d:%*d:%d",
+ &seconds)) {
+ sscanf(&line1[delta], "%*d:%*d:%*d.%d",
+ &units);
+ } else {
+ hours = 0;
+ sscanf(&line1[delta], "%d:%d.%d",
+ &minutes, &seconds, &units);
+ minutes *= inverter;
+ }
+ } else {
+ hours = minutes = 0;
+ sscanf(&line1[delta], "%d.%d", &seconds,
+ &units);
+ seconds *= inverter;
+ }
+ jacoShift =
+ ((hours * 3600 + minutes * 60 +
+ seconds) * jacoTimeres +
+ units) * inverter;
+ }
+ break;
+ case 'T':
+ if (isalpha(line1[2])) {
+ delta = 8;
+ } else {
+ delta = 2;
+ }
+ sscanf(&line1[delta], "%u", &jacoTimeres);
+ break;
+ }
+ }
+ continue;
+ } else {
+ current->start =
+ (unsigned long) ((a4 + jacoShift) * 100.0 /
+ jacoTimeres);
+ current->end =
+ (unsigned long) ((b4 + jacoShift) * 100.0 /
+ jacoTimeres);
+ }
+ } else {
+ current->start =
+ (unsigned
+ long) (((a1 * 3600 + a2 * 60 + a3) * jacoTimeres + a4 +
+ jacoShift) * 100.0 / jacoTimeres);
+ current->end =
+ (unsigned
+ long) (((b1 * 3600 + b2 * 60 + b3) * jacoTimeres + b4 +
+ jacoShift) * 100.0 / jacoTimeres);
+ }
+ current->lines = 0;
+ p = line2;
+ while ((*p == ' ') || (*p == '\t')) {
+ ++p;
+ }
+ if (isalpha(*p)||*p == '[') {
+ int cont, jLength;
+
+ if (sscanf(p, "%s %[^\n\r]", directive, line1) < 2)
+ return (subtitle *) ERR;
+ jLength = strlen(directive);
+ for (cont = 0; cont < jLength; ++cont) {
+ if (isalpha(*(directive + cont)))
+ *(directive + cont) = toupper(*(directive + cont));
+ }
+ if ((strstr(directive, "RDB") != NULL)
+ || (strstr(directive, "RDC") != NULL)
+ || (strstr(directive, "RLB") != NULL)
+ || (strstr(directive, "RLG") != NULL)) {
+ continue;
+ }
+ if (strstr(directive, "JL") != NULL) {
+ current->alignment = SUB_ALIGNMENT_BOTTOMLEFT;
+ } else if (strstr(directive, "JR") != NULL) {
+ current->alignment = SUB_ALIGNMENT_BOTTOMRIGHT;
+ } else {
+ current->alignment = SUB_ALIGNMENT_BOTTOMCENTER;
+ }
+ strcpy(line2, line1);
+ p = line2;
+ }
+ for (q = line1; (!eol(*p)) && (current->lines < SUB_MAX_TEXT); ++p) {
+ switch (*p) {
+ case '{':
+ comment++;
+ break;
+ case '}':
+ if (comment) {
+ --comment;
+ //the next line to get rid of a blank after the comment
+ if ((*(p + 1)) == ' ')
+ p++;
+ }
+ break;
+ case '~':
+ if (!comment) {
+ *q = ' ';
+ ++q;
+ }
+ break;
+ case ' ':
+ case '\t':
+ if ((*(p + 1) == ' ') || (*(p + 1) == '\t'))
+ break;
+ if (!comment) {
+ *q = ' ';
+ ++q;
+ }
+ break;
+ case '\\':
+ if (*(p + 1) == 'n') {
+ *q = '\0';
+ q = line1;
+ current->text[current->lines++] = strdup(line1);
+ ++p;
+ break;
+ }
+ if ((toupper(*(p + 1)) == 'C')
+ || (toupper(*(p + 1)) == 'F')) {
+ ++p,++p;
+ break;
+ }
+ if ((*(p + 1) == 'B') || (*(p + 1) == 'b') || (*(p + 1) == 'D') || //actually this means "insert current date here"
+ (*(p + 1) == 'I') || (*(p + 1) == 'i') || (*(p + 1) == 'N') || (*(p + 1) == 'T') || //actually this means "insert current time here"
+ (*(p + 1) == 'U') || (*(p + 1) == 'u')) {
+ ++p;
+ break;
+ }
+ if ((*(p + 1) == '\\') ||
+ (*(p + 1) == '~') || (*(p + 1) == '{')) {
+ ++p;
+ } else if (eol(*(p + 1))) {
+ if (!stream_read_line(st, directive, LINE_LEN, utf16))
+ return NULL;
+ trail_space(directive);
+ av_strlcat(line2, directive, LINE_LEN);
+ break;
+ }
+ default:
+ if (!comment) {
+ *q = *p;
+ ++q;
+ }
+ } //-- switch
+ } //-- for
+ *q = '\0';
+ current->text[current->lines] = strdup(line1);
+ } //-- while
+ current->lines++;
+ return current;
+}
+
+static int sub_autodetect (stream_t* st, int *uses_time, int utf16) {
+ char line[LINE_LEN+1];
+ int i,j=0;
+
+ while (j < 100) {
+ j++;
+ if (!stream_read_line (st, line, LINE_LEN, utf16))
+ return SUB_INVALID;
+
+ if (sscanf (line, "{%d}{%d}", &i, &i)==2)
+ {*uses_time=0;return SUB_MICRODVD;}
+ if (sscanf (line, "{%d}{}", &i)==1)
+ {*uses_time=0;return SUB_MICRODVD;}
+ if (sscanf (line, "[%d][%d]", &i, &i)==2)
+ {*uses_time=1;return SUB_MPL2;}
+ if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d", &i, &i, &i, &i, &i, &i, &i, &i)==8)
+ {*uses_time=1;return SUB_SUBRIP;}
+ if (sscanf (line, "%d:%d:%d%[,.:]%d --> %d:%d:%d%[,.:]%d", &i, &i, &i, (char *)&i, &i, &i, &i, &i, (char *)&i, &i)==10)
+ {*uses_time=1;return SUB_SUBVIEWER;}
+ if (sscanf (line, "{T %d:%d:%d:%d",&i, &i, &i, &i)==4)
+ {*uses_time=1;return SUB_SUBVIEWER2;}
+ if (strstr (line, "<SAMI>"))
+ {*uses_time=1; return SUB_SAMI;}
+ if (sscanf(line, "%d:%d:%d.%d %d:%d:%d.%d", &i, &i, &i, &i, &i, &i, &i, &i) == 8)
+ {*uses_time = 1; return SUB_JACOSUB;}
+ if (sscanf(line, "@%d @%d", &i, &i) == 2)
+ {*uses_time = 1; return SUB_JACOSUB;}
+ if (sscanf (line, "%d:%d:%d:", &i, &i, &i )==3)
+ {*uses_time=1;return SUB_VPLAYER;}
+ if (sscanf (line, "%d:%d:%d ", &i, &i, &i )==3)
+ {*uses_time=1;return SUB_VPLAYER;}
+ if (!strncasecmp(line, "<window", 7))
+ {*uses_time=1;return SUB_RT;}
+ if (!memcmp(line, "Dialogue: Marked", 16))
+ {*uses_time=1; return SUB_SSA;}
+ if (!memcmp(line, "Dialogue: ", 10))
+ {*uses_time=1; return SUB_SSA;}
+ if (sscanf (line, "%d,%d,\"%c", &i, &i, (char *) &i) == 3)
+ {*uses_time=1;return SUB_PJS;}
+ if (sscanf (line, "FORMAT=%d", &i) == 1)
+ {*uses_time=0; return SUB_MPSUB;}
+ if (!memcmp(line, "FORMAT=TIME", 11))
+ {*uses_time=1; return SUB_MPSUB;}
+ if (strstr (line, "-->>"))
+ {*uses_time=0; return SUB_AQTITLE;}
+ if (sscanf (line, "[%d:%d:%d]", &i, &i, &i)==3)
+ {*uses_time=1;return SUB_SUBRIP09;}
+ }
+
+ return SUB_INVALID; // too many bad lines
+}
+
+extern int sub_utf8;
+int sub_utf8_prev=0;
+
+extern float sub_delay;
+extern float sub_fps;
+
+#ifdef CONFIG_ICONV
+static iconv_t icdsc = (iconv_t)(-1);
+
+void subcp_open (stream_t *st)
+{
+ char *tocp = "UTF-8";
+
+ if (sub_cp){
+ const char *cp_tmp = sub_cp;
+#ifdef CONFIG_ENCA
+ char enca_lang[3], enca_fallback[100];
+ if (sscanf(sub_cp, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
+ || sscanf(sub_cp, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
+ if (st && st->flags & MP_STREAM_SEEK ) {
+ cp_tmp = guess_cp(st, enca_lang, enca_fallback);
+ } else {
+ cp_tmp = enca_fallback;
+ if (st)
+ mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: enca failed, stream must be seekable.\n");
+ }
+ }
+#endif
+ if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
+ mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: opened iconv descriptor.\n");
+ sub_utf8 = 2;
+ } else
+ mp_msg(MSGT_SUBREADER,MSGL_ERR,"SUB: error opening iconv descriptor.\n");
+ }
+}
+
+void subcp_close (void)
+{
+ if (icdsc != (iconv_t)(-1)){
+ (void) iconv_close (icdsc);
+ icdsc = (iconv_t)(-1);
+ mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: closed iconv descriptor.\n");
+ }
+}
+
+subtitle* subcp_recode (subtitle *sub)
+{
+ int l=sub->lines;
+ size_t ileft, oleft;
+ char *op, *ip, *ot;
+ if(icdsc == (iconv_t)(-1)) return sub;
+
+ while (l){
+ ip = sub->text[--l];
+ ileft = strlen(ip);
+ oleft = 4 * ileft;
+
+ if (!(ot = malloc(oleft + 1))){
+ mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: error allocating mem.\n");
+ continue;
+ }
+ op = ot;
+ if (iconv(icdsc, &ip, &ileft,
+ &op, &oleft) == (size_t)(-1)) {
+ mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: error recoding line.\n");
+ free(ot);
+ continue;
+ }
+ // In some stateful encodings, we must clear the state to handle the last character
+ if (iconv(icdsc, NULL, NULL,
+ &op, &oleft) == (size_t)(-1)) {
+ mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: error recoding line, can't clear encoding state.\n");
+ }
+ *op='\0' ;
+ free (sub->text[l]);
+ sub->text[l] = ot;
+ }
+ return sub;
+}
+#endif
+
+#ifdef CONFIG_FRIBIDI
+/**
+ * Do conversion necessary for right-to-left language support via fribidi.
+ * @param sub subtitle to convert
+ * @param sub_utf8 whether the subtitle is encoded in UTF-8
+ * @param from first new subtitle, all lines before this are assumed to be already converted
+ */
+static subtitle* sub_fribidi (subtitle *sub, int sub_utf8, int from)
+{
+ FriBidiChar logical[LINE_LEN+1], visual[LINE_LEN+1]; // Hopefully these two won't smash the stack
+ char *ip = NULL, *op = NULL;
+ size_t len,orig_len;
+ int l=sub->lines;
+ int char_set_num;
+ fribidi_boolean log2vis;
+ if (!flip_hebrew)
+ return sub;
+ fribidi_set_mirroring(1);
+ fribidi_set_reorder_nsm(0);
+
+ if( sub_utf8 == 0 ) {
+ char_set_num = fribidi_parse_charset (fribidi_charset?fribidi_charset:"ISO8859-8");
+ }else {
+ char_set_num = fribidi_parse_charset ("UTF-8");
+ }
+ while (l > from) {
+ ip = sub->text[--l];
+ orig_len = len = strlen( ip ); // We assume that we don't use full unicode, only UTF-8 or ISO8859-x
+ if(len > LINE_LEN) {
+ mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: sub->text is longer than LINE_LEN.\n");
+ l++;
+ break;
+ }
+ len = fribidi_charset_to_unicode (char_set_num, ip, len, logical);
+#if FRIBIDI_INTERFACE_VERSION < 3
+ FriBidiCharType base = fribidi_flip_commas?FRIBIDI_TYPE_ON:FRIBIDI_TYPE_L;
+#else
+ FriBidiParType base = fribidi_flip_commas?FRIBIDI_TYPE_ON:FRIBIDI_TYPE_L;
+#endif
+ log2vis = fribidi_log2vis (logical, len, &base,
+ /* output */
+ visual, NULL, NULL, NULL);
+ if(log2vis) {
+ len = fribidi_remove_bidi_marks (visual, len, NULL, NULL,
+ NULL);
+ if((op = malloc((FFMAX(2*orig_len,2*len) + 1))) == NULL) {
+ mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: error allocating mem.\n");
+ l++;
+ break;
+ }
+ fribidi_unicode_to_charset ( char_set_num, visual, len,op);
+ free (ip);
+ sub->text[l] = op;
+ }
+ }
+ if (!from && l){
+ for (l = sub->lines; l;)
+ free (sub->text[--l]);
+ return ERR;
+ }
+ return sub;
+}
+
+#endif
+
+static void adjust_subs_time(subtitle* sub, float subtime, float fps, int block,
+ int sub_num, int sub_uses_time) {
+ int n,m;
+ subtitle* nextsub;
+ int i = sub_num;
+ unsigned long subfms = (sub_uses_time ? 100 : fps) * subtime;
+ unsigned long overlap = (sub_uses_time ? 100 : fps) / 5; // 0.2s
+
+ n=m=0;
+ if (i) for (;;){
+ if (sub->end <= sub->start){
+ sub->end = sub->start + subfms;
+ m++;
+ n++;
+ }
+ if (!--i) break;
+ nextsub = sub + 1;
+ if(block){
+ if ((sub->end > nextsub->start) && (sub->end <= nextsub->start + overlap)) {
+ // these subtitles overlap for less than 0.2 seconds
+ // and would result in very short overlapping subtitle
+ // so let's fix the problem here, before overlapping code
+ // get its hands on them
+ unsigned delta = sub->end - nextsub->start, half = delta / 2;
+ sub->end -= half + 1;
+ nextsub->start += delta - half;
+ }
+ if (sub->end >= nextsub->start){
+ sub->end = nextsub->start - 1;
+ if (sub->end - sub->start > subfms)
+ sub->end = sub->start + subfms;
+ if (!m)
+ n++;
+ }
+ }
+
+ /* Theory:
+ * Movies are often converted from FILM (24 fps)
+ * to PAL (25) by simply speeding it up, so we
+ * to multiply the original timestmaps by
+ * (Movie's FPS / Subtitle's (guessed) FPS)
+ * so eg. for 23.98 fps movie and PAL time based
+ * subtitles we say -subfps 25 and we're fine!
+ */
+
+ /* timed sub fps correction ::atmos */
+ /* the frame-based case is handled in mpcommon.c
+ * where find_sub is called */
+ if(sub_uses_time && sub_fps) {
+ sub->start *= sub_fps/fps;
+ sub->end *= sub_fps/fps;
+ }
+
+ sub = nextsub;
+ m = 0;
+ }
+ if (n) mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: Adjusted %d subtitle(s).\n", n);
+}
+
+struct subreader {
+ subtitle * (*read)(stream_t *st, subtitle *dest,
+ struct readline_args *args);
+ void (*post)(subtitle *dest);
+ const char *name;
+};
+
+#ifdef CONFIG_ENCA
+const char* guess_buffer_cp(unsigned char* buffer, int buflen, const char *preferred_language, const char *fallback)
+{
+ const char **languages;
+ size_t langcnt;
+ EncaAnalyser analyser;
+ EncaEncoding encoding;
+ const char *detected_sub_cp = NULL;
+ int i;
+
+ languages = enca_get_languages(&langcnt);
+ mp_msg(MSGT_SUBREADER, MSGL_V, "ENCA supported languages: ");
+ for (i = 0; i < langcnt; i++) {
+ mp_msg(MSGT_SUBREADER, MSGL_V, "%s ", languages[i]);
+ }
+ mp_msg(MSGT_SUBREADER, MSGL_V, "\n");
+
+ for (i = 0; i < langcnt; i++) {
+ if (strcasecmp(languages[i], preferred_language) != 0) continue;
+ analyser = enca_analyser_alloc(languages[i]);
+ encoding = enca_analyse_const(analyser, buffer, buflen);
+ enca_analyser_free(analyser);
+ if (encoding.charset != ENCA_CS_UNKNOWN) {
+ detected_sub_cp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV);
+ break;
+ }
+ }
+
+ free(languages);
+
+ if (!detected_sub_cp) {
+ detected_sub_cp = fallback;
+ mp_msg(MSGT_SUBREADER, MSGL_INFO, "ENCA detection failed: fallback to %s\n", fallback);
+ }else{
+ mp_msg(MSGT_SUBREADER, MSGL_INFO, "ENCA detected charset: %s\n", detected_sub_cp);
+ }
+
+ return detected_sub_cp;
+}
+
+#define MAX_GUESS_BUFFER_SIZE (256*1024)
+const char* guess_cp(stream_t *st, const char *preferred_language, const char *fallback)
+{
+ size_t buflen;
+ unsigned char *buffer;
+ const char *detected_sub_cp = NULL;
+
+ buffer = malloc(MAX_GUESS_BUFFER_SIZE);
+ buflen = stream_read(st,buffer, MAX_GUESS_BUFFER_SIZE);
+
+ detected_sub_cp = guess_buffer_cp(buffer, buflen, preferred_language, fallback);
+
+ free(buffer);
+ stream_reset(st);
+ stream_seek(st,0);
+
+ return detected_sub_cp;
+}
+#undef MAX_GUESS_BUFFER_SIZE
+#endif
+
+sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts)
+{
+ int utf16;
+ stream_t* fd;
+ int n_max, n_first, i, j, sub_first, sub_orig;
+ subtitle *first, *second, *sub, *return_sub, *alloced_sub = NULL;
+ sub_data *subt_data;
+ int uses_time = 0, sub_num = 0, sub_errs = 0;
+ static const struct subreader sr[]=
+ {
+ { sub_read_line_microdvd, NULL, "microdvd" },
+ { sub_read_line_subrip, NULL, "subrip" },
+ { sub_read_line_subviewer, NULL, "subviewer" },
+ { sub_read_line_sami, NULL, "sami" },
+ { sub_read_line_vplayer, NULL, "vplayer" },
+ { sub_read_line_rt, NULL, "rt" },
+ { sub_read_line_ssa, sub_pp_ssa, "ssa" },
+ { sub_read_line_pjs, NULL, "pjs" },
+ { sub_read_line_mpsub, NULL, "mpsub" },
+ { sub_read_line_aqt, NULL, "aqt" },
+ { sub_read_line_subviewer2, NULL, "subviewer 2.0" },
+ { sub_read_line_subrip09, NULL, "subrip 0.9" },
+ { sub_read_line_jacosub, NULL, "jacosub" },
+ { sub_read_line_mpl2, NULL, "mpl2" }
+ };
+ const struct subreader *srp;
+
+ if(filename==NULL) return NULL; //qnx segfault
+ fd=open_stream (filename, NULL, NULL); if (!fd) return NULL;
+
+ sub_format = SUB_INVALID;
+ for (utf16 = 0; sub_format == SUB_INVALID && utf16 < 3; utf16++) {
+ sub_format=sub_autodetect (fd, &uses_time, utf16);
+ stream_reset(fd);
+ stream_seek(fd,0);
+ }
+ utf16--;
+
+ mpsub_multiplier = (uses_time ? 100.0 : 1.0);
+ if (sub_format==SUB_INVALID) {mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: Could not determine file format\n");return NULL;}
+ srp=sr+sub_format;
+ mp_msg(MSGT_SUBREADER, MSGL_V, "SUB: Detected subtitle file format: %s\n", srp->name);
+
+#ifdef CONFIG_ICONV
+ sub_utf8_prev=sub_utf8;
+ {
+ int l,k;
+ k = -1;
+ if ((l=strlen(filename))>4){
+ char *exts[] = {".utf", ".utf8", ".utf-8" };
+ for (k=3;--k>=0;)
+ if (l >= strlen(exts[k]) && !strcasecmp(filename+(l - strlen(exts[k])), exts[k])){
+ sub_utf8 = 1;
+ break;
+ }
+ }
+ if (k<0) subcp_open(fd);
+ }
+#endif
+
+ sub_num=0;n_max=32;
+ first=malloc(n_max*sizeof(subtitle));
+ if(!first){
+#ifdef CONFIG_ICONV
+ subcp_close();
+ sub_utf8=sub_utf8_prev;
+#endif
+ return NULL;
+ }
+
+#ifdef CONFIG_SORTSUB
+ alloced_sub =
+ sub = malloc(sizeof(subtitle));
+ //This is to deal with those formats (AQT & Subrip) which define the end of a subtitle
+ //as the beginning of the following
+ previous_sub_end = 0;
+#endif
+ while(1){
+ if(sub_num>=n_max){
+ n_max+=16;
+ first=realloc(first,n_max*sizeof(subtitle));
+ }
+#ifndef CONFIG_SORTSUB
+ sub = &first[sub_num];
+#endif
+ memset(sub, '\0', sizeof(subtitle));
+ sub=srp->read(fd, sub, &(struct readline_args){utf16, opts});
+ if(!sub) break; // EOF
+#ifdef CONFIG_ICONV
+ if ((sub!=ERR) && sub_utf8 == 2) sub=subcp_recode(sub);
+#endif
+#ifdef CONFIG_FRIBIDI
+ if (sub!=ERR) sub=sub_fribidi(sub,sub_utf8,0);
+#endif
+ if ( sub == ERR )
+ {
+#ifdef CONFIG_ICONV
+ subcp_close();
+#endif
+ free(first);
+ free(alloced_sub);
+ return NULL;
+ }
+ // Apply any post processing that needs recoding first
+ if ((sub!=ERR) && !sub_no_text_pp && srp->post) srp->post(sub);
+#ifdef CONFIG_SORTSUB
+ if(!sub_num || (first[sub_num - 1].start <= sub->start)){
+ first[sub_num].start = sub->start;
+ first[sub_num].end = sub->end;
+ first[sub_num].lines = sub->lines;
+ first[sub_num].alignment = sub->alignment;
+ for(i = 0; i < sub->lines; ++i){
+ first[sub_num].text[i] = sub->text[i];
+ }
+ if (previous_sub_end){
+ first[sub_num - 1].end = previous_sub_end;
+ previous_sub_end = 0;
+ }
+ } else {
+ for(j = sub_num - 1; j >= 0; --j){
+ first[j + 1].start = first[j].start;
+ first[j + 1].end = first[j].end;
+ first[j + 1].lines = first[j].lines;
+ first[j + 1].alignment = first[j].alignment;
+ for(i = 0; i < first[j].lines; ++i){
+ first[j + 1].text[i] = first[j].text[i];
+ }
+ if(!j || (first[j - 1].start <= sub->start)){
+ first[j].start = sub->start;
+ first[j].end = sub->end;
+ first[j].lines = sub->lines;
+ first[j].alignment = sub->alignment;
+ for(i = 0; i < SUB_MAX_TEXT; ++i){
+ first[j].text[i] = sub->text[i];
+ }
+ if (previous_sub_end){
+ first[j].end = first[j - 1].end;
+ first[j - 1].end = previous_sub_end;
+ previous_sub_end = 0;
+ }
+ break;
+ }
+ }
+ }
+#endif
+ if(sub==ERR) ++sub_errs; else ++sub_num; // Error vs. Valid
+ }
+
+ free_stream(fd);
+
+#ifdef CONFIG_ICONV
+ subcp_close();
+#endif
+ free(alloced_sub);
+
+// printf ("SUB: Subtitle format %s time.\n", uses_time?"uses":"doesn't use");
+ mp_msg(MSGT_SUBREADER, MSGL_V,"SUB: Read %i subtitles, %i bad line(s).\n",
+ sub_num, sub_errs);
+
+ if(sub_num<=0){
+ free(first);
+ return NULL;
+ }
+
+ // we do overlap if the user forced it (suboverlap_enable == 2) or
+ // the user didn't forced no-overlapsub and the format is Jacosub or Ssa.
+ // this is because usually overlapping subtitles are found in these formats,
+ // while in others they are probably result of bad timing
+if ((suboverlap_enabled == 2) ||
+ ((suboverlap_enabled) && ((sub_format == SUB_JACOSUB) || (sub_format == SUB_SSA)))) {
+ adjust_subs_time(first, 6.0, fps, 0, sub_num, uses_time);/*~6 secs AST*/
+// here we manage overlapping subtitles
+ sub_orig = sub_num;
+ n_first = sub_num;
+ sub_num = 0;
+ second = NULL;
+ // for each subtitle in first[] we deal with its 'block' of
+ // bonded subtitles
+ for (sub_first = 0; sub_first < n_first; ++sub_first) {
+ unsigned long global_start = first[sub_first].start,
+ global_end = first[sub_first].end, local_start, local_end;
+ int lines_to_add = first[sub_first].lines, sub_to_add = 0,
+ **placeholder = NULL, higher_line = 0, counter, start_block_sub = sub_num;
+ char real_block = 1;
+
+ // here we find the number of subtitles inside the 'block'
+ // and its span interval. this works well only with sorted
+ // subtitles
+ while ((sub_first + sub_to_add + 1 < n_first) && (first[sub_first + sub_to_add + 1].start < global_end)) {
+ ++sub_to_add;
+ lines_to_add += first[sub_first + sub_to_add].lines;
+ if (first[sub_first + sub_to_add].start < global_start) {
+ global_start = first[sub_first + sub_to_add].start;
+ }
+ if (first[sub_first + sub_to_add].end > global_end) {
+ global_end = first[sub_first + sub_to_add].end;
+ }
+ }
+
+ /* Avoid n^2 memory use for the "placeholder" data structure
+ * below with subtitles that have a huge number of
+ * consecutive overlapping lines. */
+ lines_to_add = FFMIN(lines_to_add, SUB_MAX_TEXT);
+
+ // we need a structure to keep trace of the screen lines
+ // used by the subs, a 'placeholder'
+ counter = 2 * sub_to_add + 1; // the maximum number of subs derived
+ // from a block of sub_to_add+1 subs
+ placeholder = malloc(sizeof(int *) * counter);
+ for (i = 0; i < counter; ++i) {
+ placeholder[i] = malloc(sizeof(int) * lines_to_add);
+ for (j = 0; j < lines_to_add; ++j) {
+ placeholder[i][j] = -1;
+ }
+ }
+
+ counter = 0;
+ local_end = global_start - 1;
+ do {
+ int ls;
+
+ // here we find the beginning and the end of a new
+ // subtitle in the block
+ local_start = local_end + 1;
+ local_end = global_end;
+ for (j = 0; j <= sub_to_add; ++j) {
+ if ((first[sub_first + j].start - 1 > local_start) && (first[sub_first + j].start - 1 < local_end)) {
+ local_end = first[sub_first + j].start - 1;
+ } else if ((first[sub_first + j].end > local_start) && (first[sub_first + j].end < local_end)) {
+ local_end = first[sub_first + j].end;
+ }
+ }
+ // here we allocate the screen lines to subs we must
+ // display in current local_start-local_end interval.
+ // if the subs were yet presents in the previous interval
+ // they keep the same lines, otherside they get unused lines
+ for (j = 0; j <= sub_to_add; ++j) {
+ if ((first[sub_first + j].start <= local_end) && (first[sub_first + j].end > local_start)) {
+ unsigned long sub_lines = first[sub_first + j].lines, fragment_length = lines_to_add + 1,
+ tmp = 0;
+ char boolean = 0;
+ int fragment_position = -1;
+
+ // if this is not the first new sub of the block
+ // we find if this sub was present in the previous
+ // new sub
+ if (counter)
+ for (i = 0; i < lines_to_add; ++i) {
+ if (placeholder[counter - 1][i] == sub_first + j) {
+ placeholder[counter][i] = sub_first + j;
+ boolean = 1;
+ }
+ }
+ if (boolean)
+ continue;
+
+ // we are looking for the shortest among all groups of
+ // sequential blank lines whose length is greater than or
+ // equal to sub_lines. we store in fragment_position the
+ // position of the shortest group, in fragment_length its
+ // length, and in tmp the length of the group currently
+ // examinated
+ for (i = 0; i < lines_to_add; ++i) {
+ if (placeholder[counter][i] == -1) {
+ // placeholder[counter][i] is part of the current group
+ // of blank lines
+ ++tmp;
+ } else {
+ if (tmp == sub_lines) {
+ // current group's size fits exactly the one we
+ // need, so we stop looking
+ fragment_position = i - tmp;
+ tmp = 0;
+ break;
+ }
+ if ((tmp) && (tmp > sub_lines) && (tmp < fragment_length)) {
+ // current group is the best we found till here,
+ // but is still bigger than the one we are looking
+ // for, so we keep on looking
+ fragment_length = tmp;
+ fragment_position = i - tmp;
+ tmp = 0;
+ } else {
+ // current group doesn't fit at all, so we forget it
+ tmp = 0;
+ }
+ }
+ }
+ if (tmp) {
+ // last screen line is blank, a group ends with it
+ if ((tmp >= sub_lines) && (tmp < fragment_length)) {
+ fragment_position = i - tmp;
+ }
+ }
+ if (fragment_position == -1) {
+ // it was not possible to find free screen line(s) for a subtitle,
+ // usually this means a bug in the code; however we do not overlap
+ mp_msg(MSGT_SUBREADER, MSGL_WARN, "SUB: we could not find a suitable position for an overlapping subtitle\n");
+ higher_line = SUB_MAX_TEXT + 1;
+ break;
+ } else {
+ for (tmp = 0; tmp < sub_lines; ++tmp) {
+ placeholder[counter][fragment_position + tmp] = sub_first + j;
+ }
+ }
+ }
+ }
+ for (j = higher_line + 1; j < lines_to_add; ++j) {
+ if (placeholder[counter][j] != -1)
+ higher_line = j;
+ else
+ break;
+ }
+ if (higher_line >= SUB_MAX_TEXT) {
+ // the 'block' has too much lines, so we don't overlap the
+ // subtitles
+ second = realloc(second, (sub_num + sub_to_add + 1) * sizeof(subtitle));
+ for (j = 0; j <= sub_to_add; ++j) {
+ int ls;
+ memset(&second[sub_num + j], '\0', sizeof(subtitle));
+ second[sub_num + j].start = first[sub_first + j].start;
+ second[sub_num + j].end = first[sub_first + j].end;
+ second[sub_num + j].lines = first[sub_first + j].lines;
+ second[sub_num + j].alignment = first[sub_first + j].alignment;
+ for (ls = 0; ls < second[sub_num + j].lines; ls++) {
+ second[sub_num + j].text[ls] = strdup(first[sub_first + j].text[ls]);
+ }
+ }
+ sub_num += sub_to_add + 1;
+ sub_first += sub_to_add;
+ real_block = 0;
+ break;
+ }
+
+ // we read the placeholder structure and create the new
+ // subs.
+ second = realloc(second, (sub_num + 1) * sizeof(subtitle));
+ memset(&second[sub_num], '\0', sizeof(subtitle));
+ second[sub_num].start = local_start;
+ second[sub_num].end = local_end;
+ second[sub_num].alignment = first[sub_first].alignment;
+ n_max = (lines_to_add < SUB_MAX_TEXT) ? lines_to_add : SUB_MAX_TEXT;
+ for (i = 0, j = 0; j < n_max; ++j) {
+ if (placeholder[counter][j] != -1) {
+ int lines = first[placeholder[counter][j]].lines;
+ for (ls = 0; ls < lines; ++ls) {
+ second[sub_num].text[i++] = strdup(first[placeholder[counter][j]].text[ls]);
+ }
+ j += lines - 1;
+ } else {
+ second[sub_num].text[i++] = strdup(" ");
+ }
+ }
+ ++sub_num;
+ ++counter;
+ } while (local_end < global_end);
+ if (real_block)
+ for (i = 0; i < counter; ++i)
+ second[start_block_sub + i].lines = higher_line + 1;
+
+ counter = 2 * sub_to_add + 1;
+ for (i = 0; i < counter; ++i) {
+ free(placeholder[i]);
+ }
+ free(placeholder);
+ sub_first += sub_to_add;
+ }
+
+ for (j = sub_orig - 1; j >= 0; --j) {
+ for (i = first[j].lines - 1; i >= 0; --i) {
+ free(first[j].text[i]);
+ }
+ }
+ free(first);
+
+ return_sub = second;
+} else { //if(suboverlap_enabled)
+ adjust_subs_time(first, 6.0, fps, 1, sub_num, uses_time);/*~6 secs AST*/
+ return_sub = first;
+}
+ if (return_sub == NULL) return NULL;
+ subt_data = malloc(sizeof(sub_data));
+ subt_data->filename = strdup(filename);
+ subt_data->sub_uses_time = uses_time;
+ subt_data->sub_num = sub_num;
+ subt_data->sub_errs = sub_errs;
+ subt_data->subtitles = return_sub;
+ return subt_data;
+}
+
+#if 0
+char * strreplace( char * in,char * what,char * whereof )
+{
+ int i;
+ char * tmp;
+
+ if ( ( in == NULL )||( what == NULL )||( whereof == NULL )||( ( tmp=strstr( in,what ) ) == NULL ) ) return NULL;
+ for( i=0;i<strlen( whereof );i++ ) tmp[i]=whereof[i];
+ if ( strlen( what ) > strlen( whereof ) ) tmp[i]=0;
+ return in;
+}
+#endif
+
+
+static void strcpy_trim(char *d, char *s)
+{
+ // skip leading whitespace
+ while (*s && isspace(*s)) {
+ s++;
+ }
+ for (;;) {
+ // copy word
+ while (*s && !isspace(*s)) {
+ *d = tolower(*s);
+ s++; d++;
+ }
+ if (*s == 0) break;
+ // trim excess whitespace
+ while (*s && isspace(*s)) {
+ s++;
+ }
+ if (*s == 0) break;
+ *d++ = ' ';
+ }
+ *d = 0;
+}
+
+static void strcpy_strip_ext(char *d, char *s)
+{
+ char *tmp = strrchr(s,'.');
+ if (!tmp) {
+ strcpy(d, s);
+ return;
+ } else {
+ strncpy(d, s, tmp-s);
+ d[tmp-s] = 0;
+ }
+ while (*d) {
+ *d = tolower(*d);
+ d++;
+ }
+}
+
+static void strcpy_get_ext(char *d, char *s)
+{
+ char *tmp = strrchr(s,'.');
+ if (!tmp) {
+ strcpy(d, "");
+ return;
+ } else {
+ strcpy(d, tmp+1);
+ }
+}
+
+static int whiteonly(char *s)
+{
+ while (*s) {
+ if (!isspace(*s)) return 0;
+ s++;
+ }
+ return 1;
+}
+
+typedef struct subfn
+{
+ int priority;
+ char *fname;
+} subfn;
+
+static int compare_sub_priority(const void *a, const void *b)
+{
+ if (((const subfn*)a)->priority > ((const subfn*)b)->priority) {
+ return -1;
+ } else if (((const subfn*)a)->priority < ((const subfn*)b)->priority) {
+ return 1;
+ } else {
+ return strcoll(((const subfn*)a)->fname, ((const subfn*)b)->fname);
+ }
+}
+
+char** sub_filenames(const char* path, char *fname)
+{
+ char *f_dir, *f_fname, *f_fname_noext, *f_fname_trim, *tmp, *tmp_sub_id;
+ char *tmp_fname_noext, *tmp_fname_trim, *tmp_fname_ext, *tmpresult;
+
+ int len, pos, found, i, j;
+ char * sub_exts[] = { "utf", "utf8", "utf-8", "sub", "srt", "smi", "rt", "txt", "ssa", "aqt", "jss", "js", "ass", NULL};
+ subfn *result;
+ char **result2;
+
+ int subcnt;
+
+ FILE *f;
+
+ DIR *d;
+ struct dirent *de;
+
+ len = (strlen(fname) > 256 ? strlen(fname) : 256)
+ +(strlen(path) > 256 ? strlen(path) : 256)+2;
+
+ f_dir = malloc(len);
+ f_fname = malloc(len);
+ f_fname_noext = malloc(len);
+ f_fname_trim = malloc(len);
+
+ tmp_fname_noext = malloc(len);
+ tmp_fname_trim = malloc(len);
+ tmp_fname_ext = malloc(len);
+
+ tmpresult = malloc(len);
+
+ result = calloc(MAX_SUBTITLE_FILES, sizeof(*result));
+
+ subcnt = 0;
+
+ tmp = strrchr(fname,'/');
+#if HAVE_DOS_PATHS
+ if(!tmp)tmp = strrchr(fname,'\\');
+ if(!tmp)tmp = strrchr(fname,':');
+#endif
+
+ // extract filename & dirname from fname
+ if (tmp) {
+ strcpy(f_fname, tmp+1);
+ pos = tmp - fname;
+ strncpy(f_dir, fname, pos+1);
+ f_dir[pos+1] = 0;
+ } else {
+ strcpy(f_fname, fname);
+ strcpy(f_dir, "./");
+ }
+
+ strcpy_strip_ext(f_fname_noext, f_fname);
+ strcpy_trim(f_fname_trim, f_fname_noext);
+
+ /* The code using sub language here is broken - it assumes strict
+ * "videoname languagename" syntax for the subtitle file, which is
+ * very unlikely to match especially if language name uses "en,de"
+ * syntax... */
+ tmp_sub_id = NULL;
+#if 0
+ if (dvdsub_lang && !whiteonly(dvdsub_lang)) {
+ tmp_sub_id = malloc(strlen(dvdsub_lang)+1);
+ strcpy_trim(tmp_sub_id, dvdsub_lang);
+ }
+#endif
+
+ // 0 = nothing
+ // 1 = any subtitle file
+ // 2 = any sub file containing movie name
+ // 3 = sub file containing movie name and the lang extension
+ for (j = 0; j <= 1; j++) {
+ d = opendir(j == 0 ? f_dir : path);
+ if (d) {
+ while ((de = readdir(d))) {
+ // retrieve various parts of the filename
+ strcpy_strip_ext(tmp_fname_noext, de->d_name);
+ strcpy_get_ext(tmp_fname_ext, de->d_name);
+ strcpy_trim(tmp_fname_trim, tmp_fname_noext);
+
+ // does it end with a subtitle extension?
+ found = 0;
+#ifdef CONFIG_ICONV
+#ifdef CONFIG_ENCA
+ for (i = ((sub_cp && strncasecmp(sub_cp, "enca", 4) != 0) ? 3 : 0); sub_exts[i]; i++) {
+#else
+ for (i = (sub_cp ? 3 : 0); sub_exts[i]; i++) {
+#endif
+#else
+ for (i = 0; sub_exts[i]; i++) {
+#endif
+ if (strcasecmp(sub_exts[i], tmp_fname_ext) == 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ // we have a (likely) subtitle file
+ if (found) {
+ int prio = 0;
+ if (!prio && tmp_sub_id)
+ {
+ sprintf(tmpresult, "%s %s", f_fname_trim, tmp_sub_id);
+ if (strcmp(tmp_fname_trim, tmpresult) == 0 && sub_match_fuzziness >= 1) {
+ // matches the movie name + lang extension
+ prio = 5;
+ }
+ }
+ if (!prio && strcmp(tmp_fname_trim, f_fname_trim) == 0) {
+ // matches the movie name
+ prio = 4;
+ }
+ if (!prio && (tmp = strstr(tmp_fname_trim, f_fname_trim)) && (sub_match_fuzziness >= 1)) {
+ // contains the movie name
+ tmp += strlen(f_fname_trim);
+ if (tmp_sub_id && strstr(tmp, tmp_sub_id)) {
+ // with sub_id specified prefer localized subtitles
+ prio = 3;
+ } else if ((tmp_sub_id == NULL) && whiteonly(tmp)) {
+ // without sub_id prefer "plain" name
+ prio = 3;
+ } else {
+ // with no localized subs found, try any else instead
+ prio = 2;
+ }
+ }
+ if (!prio) {
+ // doesn't contain the movie name
+ // don't try in the mplayer subtitle directory
+ if ((j == 0) && (sub_match_fuzziness >= 2)) {
+ prio = 1;
+ }
+ }
+
+ mp_msg(MSGT_SUBREADER, MSGL_DBG2, "Potential sub file: "
+ "\"%s\" Priority: %d\n", de->d_name, prio);
+ if (prio) {
+ prio += prio;
+#ifdef CONFIG_ICONV
+ if (i<3){ // prefer UTF-8 coded
+ prio++;
+ }
+#endif
+ sprintf(tmpresult, "%s%s", j == 0 ? f_dir : path, de->d_name);
+// fprintf(stderr, "%s priority %d\n", tmpresult, prio);
+ if ((f = fopen(tmpresult, "rt"))) {
+ fclose(f);
+ result[subcnt].priority = prio;
+ result[subcnt].fname = strdup(tmpresult);
+ subcnt++;
+ }
+ }
+
+ }
+ if (subcnt >= MAX_SUBTITLE_FILES) break;
+ }
+ closedir(d);
+ }
+
+ }
+
+ free(tmp_sub_id);
+
+ free(f_dir);
+ free(f_fname);
+ free(f_fname_noext);
+ free(f_fname_trim);
+
+ free(tmp_fname_noext);
+ free(tmp_fname_trim);
+ free(tmp_fname_ext);
+
+ free(tmpresult);
+
+ qsort(result, subcnt, sizeof(subfn), compare_sub_priority);
+
+ result2 = calloc(subcnt + 1, sizeof(*result2));
+
+ for (i = 0; i < subcnt; i++) {
+ result2[i] = result[i].fname;
+ }
+ result2[subcnt] = NULL;
+
+ free(result);
+
+ return result2;
+}
+
+void list_sub_file(sub_data* subd){
+ int i,j;
+ subtitle *subs = subd->subtitles;
+
+ for(j=0; j < subd->sub_num; j++){
+ subtitle* egysub=&subs[j];
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"%i line%c (%li-%li)\n",
+ egysub->lines,
+ (1==egysub->lines)?' ':'s',
+ egysub->start,
+ egysub->end);
+ for (i=0; i<egysub->lines; i++) {
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"\t\t%d: %s%s", i,egysub->text[i], i==egysub->lines-1?"":" \n ");
+ }
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"\n");
+ }
+
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"Subtitle format %s time.\n",
+ subd->sub_uses_time ? "uses":"doesn't use");
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"Read %i subtitles, %i errors.\n", subd->sub_num, subd->sub_errs);
+}
+
+void dump_srt(sub_data* subd, float fps){
+ int i,j;
+ int h,m,s,ms;
+ FILE * fd;
+ subtitle * onesub;
+ unsigned long temp;
+ subtitle *subs = subd->subtitles;
+
+ if (!subd->sub_uses_time && sub_fps == 0)
+ sub_fps = fps;
+ fd=fopen("dumpsub.srt","w");
+ if(!fd)
+ {
+ perror("dump_srt: fopen");
+ return;
+ }
+ for(i=0; i < subd->sub_num; i++)
+ {
+ onesub=subs+i; //=&subs[i];
+ fprintf(fd,"%d\n",i+1);//line number
+
+ temp=onesub->start;
+ if (!subd->sub_uses_time)
+ temp = temp * 100 / sub_fps;
+ temp -= sub_delay * 100;
+ h=temp/360000;temp%=360000; //h =1*100*60*60
+ m=temp/6000; temp%=6000; //m =1*100*60
+ s=temp/100; temp%=100; //s =1*100
+ ms=temp*10; //ms=1*10
+ fprintf(fd,"%02d:%02d:%02d,%03d --> ",h,m,s,ms);
+
+ temp=onesub->end;
+ if (!subd->sub_uses_time)
+ temp = temp * 100 / sub_fps;
+ temp -= sub_delay * 100;
+ h=temp/360000;temp%=360000;
+ m=temp/6000; temp%=6000;
+ s=temp/100; temp%=100;
+ ms=temp*10;
+ fprintf(fd,"%02d:%02d:%02d,%03d\n",h,m,s,ms);
+
+ for(j=0;j<onesub->lines;j++)
+ fprintf(fd,"%s\n",onesub->text[j]);
+
+ fprintf(fd,"\n");
+ }
+ fclose(fd);
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"SUB: Subtitles dumped in \'dumpsub.srt\'.\n");
+}
+
+void dump_mpsub(sub_data* subd, float fps){
+ int i,j;
+ FILE *fd;
+ float a,b;
+ subtitle *subs = subd->subtitles;
+
+ mpsub_position = subd->sub_uses_time? (sub_delay*100) : (sub_delay*fps);
+ if (sub_fps==0) sub_fps=fps;
+
+ fd=fopen ("dump.mpsub", "w");
+ if (!fd) {
+ perror ("dump_mpsub: fopen");
+ return;
+ }
+
+
+ if (subd->sub_uses_time) fprintf (fd,"FORMAT=TIME\n\n");
+ else fprintf (fd, "FORMAT=%5.2f\n\n", fps);
+
+ for(j=0; j < subd->sub_num; j++){
+ subtitle* egysub=&subs[j];
+ if (subd->sub_uses_time) {
+ a=((egysub->start-mpsub_position)/100.0);
+ b=((egysub->end-egysub->start)/100.0);
+ if ( (float)((int)a) == a)
+ fprintf (fd, "%.0f",a);
+ else
+ fprintf (fd, "%.2f",a);
+
+ if ( (float)((int)b) == b)
+ fprintf (fd, " %.0f\n",b);
+ else
+ fprintf (fd, " %.2f\n",b);
+ } else {
+ fprintf (fd, "%ld %ld\n", (long)((egysub->start*(fps/sub_fps))-((mpsub_position*(fps/sub_fps)))),
+ (long)(((egysub->end)-(egysub->start))*(fps/sub_fps)));
+ }
+
+ mpsub_position = egysub->end;
+ for (i=0; i<egysub->lines; i++) {
+ fprintf (fd, "%s\n",egysub->text[i]);
+ }
+ fprintf (fd, "\n");
+ }
+ fclose (fd);
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"SUB: Subtitles dumped in \'dump.mpsub\'.\n");
+}
+
+void dump_microdvd(sub_data* subd, float fps) {
+ int i, delay;
+ FILE *fd;
+ subtitle *subs = subd->subtitles;
+ if (sub_fps == 0)
+ sub_fps = fps;
+ fd = fopen("dumpsub.sub", "w");
+ if (!fd) {
+ perror("dumpsub.sub: fopen");
+ return;
+ }
+ delay = sub_delay * sub_fps;
+ for (i = 0; i < subd->sub_num; ++i) {
+ int j, start, end;
+ start = subs[i].start;
+ end = subs[i].end;
+ if (subd->sub_uses_time) {
+ start = start * sub_fps / 100 ;
+ end = end * sub_fps / 100;
+ }
+ else {
+ start = start * sub_fps / fps;
+ end = end * sub_fps / fps;
+ }
+ start -= delay;
+ end -= delay;
+ fprintf(fd, "{%d}{%d}", start, end);
+ for (j = 0; j < subs[i].lines; ++j)
+ fprintf(fd, "%s%s", j ? "|" : "", subs[i].text[j]);
+ fprintf(fd, "\n");
+ }
+ fclose(fd);
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"SUB: Subtitles dumped in \'dumpsub.sub\'.\n");
+}
+
+void dump_jacosub(sub_data* subd, float fps) {
+ int i,j;
+ int h,m,s,cs;
+ FILE * fd;
+ subtitle * onesub;
+ unsigned long temp;
+ subtitle *subs = subd->subtitles;
+
+ if (!subd->sub_uses_time && sub_fps == 0)
+ sub_fps = fps;
+ fd=fopen("dumpsub.jss","w");
+ if(!fd)
+ {
+ perror("dump_jacosub: fopen");
+ return;
+ }
+ fprintf(fd, "#TIMERES %d\n", (subd->sub_uses_time) ? 100 : (int)sub_fps);
+ for(i=0; i < subd->sub_num; i++)
+ {
+ onesub=subs+i; //=&subs[i];
+
+ temp=onesub->start;
+ if (!subd->sub_uses_time)
+ temp = temp * 100 / sub_fps;
+ temp -= sub_delay * 100;
+ h=temp/360000;temp%=360000; //h =1*100*60*60
+ m=temp/6000; temp%=6000; //m =1*100*60
+ s=temp/100; temp%=100; //s =1*100
+ cs=temp; //cs=1*10
+ fprintf(fd,"%02d:%02d:%02d.%02d ",h,m,s,cs);
+
+ temp=onesub->end;
+ if (!subd->sub_uses_time)
+ temp = temp * 100 / sub_fps;
+ temp -= sub_delay * 100;
+ h=temp/360000;temp%=360000;
+ m=temp/6000; temp%=6000;
+ s=temp/100; temp%=100;
+ cs=temp;
+ fprintf(fd,"%02d:%02d:%02d.%02d {~} ",h,m,s,cs);
+
+ for(j=0;j<onesub->lines;j++)
+ fprintf(fd,"%s%s",j ? "\\n" : "", onesub->text[j]);
+
+ fprintf(fd,"\n");
+ }
+ fclose(fd);
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"SUB: Subtitles dumped in \'dumpsub.js\'.\n");
+}
+
+void dump_sami(sub_data* subd, float fps) {
+ int i,j;
+ FILE * fd;
+ subtitle * onesub;
+ unsigned long temp;
+ subtitle *subs = subd->subtitles;
+
+ if (!subd->sub_uses_time && sub_fps == 0)
+ sub_fps = fps;
+ fd=fopen("dumpsub.smi","w");
+ if(!fd)
+ {
+ perror("dump_jacosub: fopen");
+ return;
+ }
+ fprintf(fd, "<SAMI>\n"
+ "<HEAD>\n"
+ " <STYLE TYPE=\"Text/css\">\n"
+ " <!--\n"
+ " P {margin-left: 29pt; margin-right: 29pt; font-size: 24pt; text-align: center; font-family: Tahoma; font-weight: bold; color: #FCDD03; background-color: #000000;}\n"
+ " .SUBTTL {Name: 'Subtitles'; Lang: en-US; SAMIType: CC;}\n"
+ " -->\n"
+ " </STYLE>\n"
+ "</HEAD>\n"
+ "<BODY>\n");
+ for(i=0; i < subd->sub_num; i++)
+ {
+ onesub=subs+i; //=&subs[i];
+
+ temp=onesub->start;
+ if (!subd->sub_uses_time)
+ temp = temp * 100 / sub_fps;
+ temp -= sub_delay * 100;
+ fprintf(fd,"\t<SYNC Start=%lu>\n"
+ "\t <P>", temp * 10);
+
+ for(j=0;j<onesub->lines;j++)
+ fprintf(fd,"%s%s",j ? "<br>" : "", onesub->text[j]);
+
+ fprintf(fd,"\n");
+
+ temp=onesub->end;
+ if (!subd->sub_uses_time)
+ temp = temp * 100 / sub_fps;
+ temp -= sub_delay * 100;
+ fprintf(fd,"\t<SYNC Start=%lu>\n"
+ "\t <P>&nbsp;\n", temp * 10);
+ }
+ fprintf(fd, "</BODY>\n"
+ "</SAMI>\n");
+ fclose(fd);
+ mp_msg(MSGT_SUBREADER,MSGL_INFO,"SUB: Subtitles dumped in \'dumpsub.smi\'.\n");
+}
+
+void sub_free( sub_data * subd )
+{
+ int i, j;
+
+ if ( !subd ) return;
+
+ for (i = 0; i < subd->sub_num; i++)
+ for (j = 0; j < subd->subtitles[i].lines; j++)
+ free( subd->subtitles[i].text[j] );
+ free( subd->subtitles );
+ free( subd->filename );
+ free( subd );
+}
+
+#define MAX_SUBLINE 512
+/**
+ * \brief parse text and append it to subtitle in sub
+ * \param sub subtitle struct to add text to
+ * \param txt text to parse
+ * \param len length of text in txt
+ * \param endpts pts at which this subtitle text should be removed again
+ *
+ * <> and {} are interpreted as comment delimiters, "\n", "\N", '\n', '\r'
+ * and '\0' are interpreted as newlines, duplicate, leading and trailing
+ * newlines are ignored.
+ */
+void sub_add_text(subtitle *sub, const char *txt, int len, double endpts) {
+ int comment = 0;
+ int double_newline = 1; // ignore newlines at the beginning
+ int i, pos;
+ char *buf;
+#ifdef CONFIG_FRIBIDI
+ int orig_lines = sub->lines;
+#endif
+ if (sub->lines >= SUB_MAX_TEXT) return;
+ pos = 0;
+ buf = malloc(MAX_SUBLINE + 1);
+ sub->text[sub->lines] = buf;
+ sub->endpts[sub->lines] = endpts;
+ for (i = 0; i < len && pos < MAX_SUBLINE; i++) {
+ char c = txt[i];
+ if (c == '<') comment |= 1;
+ if (c == '{') comment |= 2;
+ if (comment) {
+ if (c == '}') comment &= ~2;
+ if (c == '>') comment &= ~1;
+ continue;
+ }
+ if (pos == MAX_SUBLINE - 1) {
+ i--;
+ c = 0;
+ }
+ if (c == '\\' && i + 1 < len) {
+ c = txt[++i];
+ if (c == 'n' || c == 'N') c = 0;
+ }
+ if (c == '\n' || c == '\r') c = 0;
+ if (c) {
+ double_newline = 0;
+ buf[pos++] = c;
+ } else if (!double_newline) {
+ if (sub->lines >= SUB_MAX_TEXT - 1) {
+ mp_msg(MSGT_VO, MSGL_WARN, "Too many subtitle lines\n");
+ break;
+ }
+ double_newline = 1;
+ buf[pos] = 0;
+ sub->lines++;
+ pos = 0;
+ buf = malloc(MAX_SUBLINE + 1);
+ sub->text[sub->lines] = buf;
+ sub->endpts[sub->lines] = endpts;
+ }
+ }
+ buf[pos] = 0;
+ if (sub->lines < SUB_MAX_TEXT &&
+ strlen(sub->text[sub->lines]))
+ sub->lines++;
+#ifdef CONFIG_FRIBIDI
+ sub = sub_fribidi(sub, sub_utf8, orig_lines);
+#endif
+}
+
+/**
+ * \brief remove outdated subtitle lines.
+ * \param sub subtitle struct to modify
+ * \param pts current pts. All lines with endpts <= this will be removed.
+ * Use MP_NOPTS_VALUE to remove all lines
+ * \return 1 if sub was modified, 0 otherwise.
+ */
+int sub_clear_text(subtitle *sub, double pts) {
+ int i = 0;
+ int changed = 0;
+ while (i < sub->lines) {
+ double endpts = sub->endpts[i];
+ if (pts == MP_NOPTS_VALUE || (endpts != MP_NOPTS_VALUE && pts >= endpts)) {
+ int j;
+ free(sub->text[i]);
+ for (j = i + 1; j < sub->lines; j++) {
+ sub->text[j - 1] = sub->text[j];
+ sub->endpts[j - 1] = sub->endpts[j];
+ }
+ sub->lines--;
+ changed = 1;
+ } else
+ i++;
+ }
+ return changed;
+}
diff --git a/sub/subreader.h b/sub/subreader.h
new file mode 100644
index 0000000000..9c7465d71a
--- /dev/null
+++ b/sub/subreader.h
@@ -0,0 +1,114 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_SUBREADER_H
+#define MPLAYER_SUBREADER_H
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "config.h"
+
+extern int suboverlap_enabled;
+extern int sub_no_text_pp; // disable text post-processing
+extern int sub_match_fuzziness;
+
+// subtitle formats
+#define SUB_INVALID -1
+#define SUB_MICRODVD 0
+#define SUB_SUBRIP 1
+#define SUB_SUBVIEWER 2
+#define SUB_SAMI 3
+#define SUB_VPLAYER 4
+#define SUB_RT 5
+#define SUB_SSA 6
+#define SUB_PJS 7
+#define SUB_MPSUB 8
+#define SUB_AQTITLE 9
+#define SUB_SUBVIEWER2 10
+#define SUB_SUBRIP09 11
+#define SUB_JACOSUB 12
+#define SUB_MPL2 13
+
+// One of the SUB_* constant above
+extern int sub_format;
+
+#define MAX_SUBTITLE_FILES 128
+
+#define SUB_MAX_TEXT 12
+#define SUB_ALIGNMENT_BOTTOMLEFT 1
+#define SUB_ALIGNMENT_BOTTOMCENTER 2
+#define SUB_ALIGNMENT_BOTTOMRIGHT 3
+#define SUB_ALIGNMENT_MIDDLELEFT 4
+#define SUB_ALIGNMENT_MIDDLECENTER 5
+#define SUB_ALIGNMENT_MIDDLERIGHT 6
+#define SUB_ALIGNMENT_TOPLEFT 7
+#define SUB_ALIGNMENT_TOPCENTER 8
+#define SUB_ALIGNMENT_TOPRIGHT 9
+
+typedef struct subtitle {
+
+ int lines;
+
+ unsigned long start;
+ unsigned long end;
+
+ char *text[SUB_MAX_TEXT];
+ double endpts[SUB_MAX_TEXT];
+ unsigned char alignment;
+} subtitle;
+
+typedef struct sub_data {
+ subtitle *subtitles;
+ char *filename;
+ int sub_uses_time;
+ int sub_num; // number of subtitle structs
+ int sub_errs;
+} sub_data;
+
+extern char *fribidi_charset;
+extern int flip_hebrew;
+extern int fribidi_flip_commas;
+
+struct MPOpts;
+sub_data* sub_read_file (char *filename, float pts, struct MPOpts *opts);
+subtitle* subcp_recode (subtitle *sub);
+// enca_fd is the file enca uses to determine the codepage.
+// setting to NULL disables enca.
+struct stream;
+void subcp_open (struct stream *st); /* for demux_ogg.c */
+void subcp_close (void); /* for demux_ogg.c */
+#ifdef CONFIG_ENCA
+const char* guess_buffer_cp(unsigned char* buffer, int buflen, const char *preferred_language, const char *fallback);
+const char* guess_cp(struct stream *st, const char *preferred_language, const char *fallback);
+#endif
+char ** sub_filenames(const char *path, char *fname);
+void list_sub_file(sub_data* subd);
+void dump_srt(sub_data* subd, float fps);
+void dump_mpsub(sub_data* subd, float fps);
+void dump_microdvd(sub_data* subd, float fps);
+void dump_jacosub(sub_data* subd, float fps);
+void dump_sami(sub_data* subd, float fps);
+void sub_free( sub_data * subd );
+struct MPContext;
+void find_sub(struct MPContext *mpctx, sub_data* subd,int key);
+void step_sub(sub_data *subd, float pts, int movement);
+void sub_add_text(subtitle *sub, const char *txt, int len, double endpts);
+int sub_clear_text(subtitle *sub, double pts);
+
+#endif /* MPLAYER_SUBREADER_H */
diff --git a/sub/unrar_exec.c b/sub/unrar_exec.c
new file mode 100644
index 0000000000..6de8c59bef
--- /dev/null
+++ b/sub/unrar_exec.c
@@ -0,0 +1,235 @@
+/*
+ * List files and extract file from rars by using external executable unrar.
+ *
+ * Copyright (C) 2005 Jindrich Makovicka <makovick gmail com>
+ * Copyright (C) 2007 Ulion <ulion2002 gmail com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+#include "unrar_exec.h"
+
+#include "mp_msg.h"
+
+#define UNRAR_LIST 1
+#define UNRAR_EXTRACT 2
+
+char* unrar_executable = NULL;
+
+static FILE* launch_pipe(pid_t *apid, const char *executable, int action,
+ const char *archive, const char *filename)
+{
+ if (!executable || access(executable, R_OK | X_OK)) return NULL;
+ if (access(archive, R_OK)) return NULL;
+ {
+ int mypipe[2];
+ pid_t pid;
+
+ if (pipe(mypipe)) {
+ mp_msg(MSGT_GLOBAL, MSGL_ERR, "UnRAR: Cannot create pipe.\n");
+ return NULL;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ /* This is the child process. Execute the unrar executable. */
+ close(mypipe[0]);
+ // Close MPlayer's stdin, stdout and stderr so the unrar binary
+ // can not mess them up.
+ // TODO: Close all other files except the pipe.
+ close(0); close(1); close(2);
+ // Assign new stdin, stdout and stderr and check they actually got the
+ // right descriptors.
+ if (open("/dev/null", O_RDONLY) != 0 || dup(mypipe[1]) != 1
+ || open("/dev/null", O_WRONLY) != 2)
+ _exit(EXIT_FAILURE);
+ if (action == UNRAR_LIST)
+ execl(executable, executable, "v", archive, NULL);
+ else if (action == UNRAR_EXTRACT)
+ execl(executable, executable, "p", "-inul", "-p-",
+ archive,filename,NULL);
+ mp_msg(MSGT_GLOBAL, MSGL_ERR, "UnRAR: Cannot execute %s\n", executable);
+ _exit(EXIT_FAILURE);
+ }
+ if (pid < 0) {
+ /* The fork failed. Report failure. */
+ mp_msg(MSGT_GLOBAL, MSGL_ERR, "UnRAR: Fork failed\n");
+ return NULL;
+ }
+ /* This is the parent process. Prepare the pipe stream. */
+ close(mypipe[1]);
+ *apid = pid;
+ if (action == UNRAR_LIST)
+ mp_msg(MSGT_GLOBAL, MSGL_V,
+ "UnRAR: call unrar with command line: %s v %s\n",
+ executable, archive);
+ else if (action == UNRAR_EXTRACT)
+ mp_msg(MSGT_GLOBAL, MSGL_V,
+ "UnRAR: call unrar with command line: %s p -inul -p- %s %s\n",
+ executable, archive, filename);
+ return fdopen(mypipe[0], "r");
+ }
+}
+
+#define ALLOC_INCR 1 * 1024 * 1024
+int unrar_exec_get(unsigned char **output, unsigned long *size,
+ const char *filename, const char *rarfile)
+{
+ int bufsize = ALLOC_INCR, bytesread;
+ pid_t pid;
+ int status = 0;
+ FILE *rar_pipe;
+
+ rar_pipe=launch_pipe(&pid,unrar_executable,UNRAR_EXTRACT,rarfile,filename);
+ if (!rar_pipe) return 0;
+
+ *size = 0;
+
+ *output = malloc(bufsize);
+
+ while (*output) {
+ bytesread=fread(*output+*size, 1, bufsize-*size, rar_pipe);
+ if (bytesread <= 0)
+ break;
+ *size += bytesread;
+ if (*size == bufsize) {
+ char *p;
+ bufsize += ALLOC_INCR;
+ p = realloc(*output, bufsize);
+ if (!p)
+ free(*output);
+ *output = p;
+ }
+ }
+ fclose(rar_pipe);
+ pid = waitpid(pid, &status, 0);
+ if (!*output || !*size || (pid == -1 && errno != ECHILD) ||
+ (pid > 0 && status)) {
+ free(*output);
+ *output = NULL;
+ *size = 0;
+ return 0;
+ }
+ if (bufsize > *size) {
+ char *p = realloc(*output, *size);
+ if (p)
+ *output = p;
+ }
+ mp_msg(MSGT_GLOBAL, MSGL_V, "UnRAR: got file %s len %lu\n", filename,*size);
+ return 1;
+}
+
+#define PARSE_NAME 0
+#define PARSE_PROPS 1
+
+int unrar_exec_list(const char *rarfile, ArchiveList_struct **list)
+{
+ char buf[1024], fname[1024];
+ char *p;
+ pid_t pid;
+ int status = 0, file_num = -1, ignore_next_line = 0, state = PARSE_NAME;
+ FILE *rar_pipe;
+ ArchiveList_struct *alist = NULL, *current = NULL, *new;
+
+ rar_pipe = launch_pipe(&pid, unrar_executable, UNRAR_LIST, rarfile, NULL);
+ if (!rar_pipe) return -1;
+ while (fgets(buf, sizeof(buf), rar_pipe)) {
+ int packsize, unpsize, ratio, day, month, year, hour, min;
+ int llen = strlen(buf);
+ // If read nothing, we got a file_num -1.
+ if (file_num == -1)
+ file_num = 0;
+ if (buf[llen-1] != '\n')
+ // The line is too long, ignore it.
+ ignore_next_line = 2;
+ if (ignore_next_line) {
+ --ignore_next_line;
+ state = PARSE_NAME;
+ continue;
+ }
+ // Trim the line.
+ while (llen > 0 && strchr(" \t\n\r\v\f", buf[llen-1]))
+ --llen;
+ buf[llen] = '\0';
+ p = buf;
+ while (*p && strchr(" \t\n\r\v\f", *p))
+ ++p;
+ if (!*p) {
+ state = PARSE_NAME;
+ continue;
+ }
+
+ if (state == PARSE_PROPS && sscanf(p, "%d %d %d%% %d-%d-%d %d:%d",
+ &unpsize, &packsize, &ratio, &day,
+ &month, &year, &hour, &min) == 8) {
+ new = calloc(1, sizeof(ArchiveList_struct));
+ if (!new) {
+ file_num = -1;
+ break;
+ }
+ if (!current)
+ alist = new;
+ else
+ current->next = new;
+ current = new;
+ current->item.Name = strdup(fname);
+ state = PARSE_NAME;
+ if (!current->item.Name) {
+ file_num = -1;
+ break;
+ }
+ current->item.PackSize = packsize;
+ current->item.UnpSize = unpsize;
+ ++file_num;
+ continue;
+ }
+ strcpy(fname, p);
+ state = PARSE_PROPS;
+ }
+ fclose(rar_pipe);
+ pid = waitpid(pid, &status, 0);
+ if (file_num < 0 || (pid == -1 && errno != ECHILD) ||
+ (pid > 0 && status)) {
+ unrar_exec_freelist(alist);
+ return -1;
+ }
+ if (!alist)
+ return -1;
+ *list = alist;
+ mp_msg(MSGT_GLOBAL, MSGL_V, "UnRAR: list got %d files\n", file_num);
+ return file_num;
+}
+
+void unrar_exec_freelist(ArchiveList_struct *list)
+{
+ ArchiveList_struct* tmp;
+
+ while (list) {
+ tmp = list->next;
+ free(list->item.Name);
+ free(list);
+ list = tmp;
+ }
+}
diff --git a/sub/unrar_exec.h b/sub/unrar_exec.h
new file mode 100644
index 0000000000..5754083fd7
--- /dev/null
+++ b/sub/unrar_exec.h
@@ -0,0 +1,54 @@
+/*
+ * List files and extract file from rars by using external executable unrar.
+ *
+ * Copyright (C) 2005 Jindrich Makovicka <makovick gmail com>
+ * Copyright (C) 2007 Ulion <ulion2002 gmail com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_UNRAR_EXEC_H
+#define MPLAYER_UNRAR_EXEC_H
+
+struct RAR_archive_entry
+{
+ char *Name;
+ unsigned long PackSize;
+ unsigned long UnpSize;
+ unsigned long FileCRC;
+ unsigned long FileTime;
+ unsigned char UnpVer;
+ unsigned char Method;
+ unsigned long FileAttr;
+};
+
+typedef struct archivelist
+{
+ struct RAR_archive_entry item;
+ struct archivelist *next;
+} ArchiveList_struct;
+
+extern char* unrar_executable;
+
+int unrar_exec_get(unsigned char **output, unsigned long *size,
+ const char *filename, const char *rarfile);
+
+int unrar_exec_list(const char *rarfile, ArchiveList_struct **list);
+
+void unrar_exec_freelist(ArchiveList_struct *list);
+
+#endif /* MPLAYER_UNRAR_EXEC_H */
diff --git a/sub/vobsub.c b/sub/vobsub.c
new file mode 100644
index 0000000000..08efa3acec
--- /dev/null
+++ b/sub/vobsub.c
@@ -0,0 +1,1443 @@
+/*
+ * Some code freely inspired from VobSub <URL:http://vobsub.edensrising.com>,
+ * with kind permission from Gabest <gabest@freemail.hu>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "config.h"
+#include "mpcommon.h"
+#include "vobsub.h"
+#include "spudec.h"
+#include "mp_msg.h"
+#include "path.h"
+#include "unrar_exec.h"
+#include "libavutil/common.h"
+
+extern int vobsub_id;
+// Record the original -vobsubid set by commandline, since vobsub_id will be
+// overridden if slang match any of vobsub streams.
+static int vobsubid = -2;
+
+/**********************************************************************
+ * RAR stream handling
+ * The RAR file must have the same basename as the file to open
+ **********************************************************************/
+#ifdef CONFIG_UNRAR_EXEC
+typedef struct {
+ FILE *file;
+ unsigned char *data;
+ unsigned long size;
+ unsigned long pos;
+} rar_stream_t;
+
+static rar_stream_t *rar_open(const char *const filename,
+ const char *const mode)
+{
+ rar_stream_t *stream;
+ /* unrar_exec can only read */
+ if (strcmp("r", mode) && strcmp("rb", mode)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ stream = malloc(sizeof(rar_stream_t));
+ if (stream == NULL)
+ return NULL;
+ /* first try normal access */
+ stream->file = fopen(filename, mode);
+ if (stream->file == NULL) {
+ char *rar_filename;
+ const char *p;
+ int rc;
+ /* Guess the RAR archive filename */
+ rar_filename = NULL;
+ p = strrchr(filename, '.');
+ if (p) {
+ ptrdiff_t l = p - filename;
+ rar_filename = malloc(l + 5);
+ if (rar_filename == NULL) {
+ free(stream);
+ return NULL;
+ }
+ strncpy(rar_filename, filename, l);
+ strcpy(rar_filename + l, ".rar");
+ } else {
+ rar_filename = malloc(strlen(filename) + 5);
+ if (rar_filename == NULL) {
+ free(stream);
+ return NULL;
+ }
+ strcpy(rar_filename, filename);
+ strcat(rar_filename, ".rar");
+ }
+ /* get rid of the path if there is any */
+ p = mp_basename(filename);
+ rc = unrar_exec_get(&stream->data, &stream->size, p, rar_filename);
+ if (!rc) {
+ /* There is no matching filename in the archive. However, sometimes
+ * the files we are looking for have been given arbitrary names in the archive.
+ * Let's look for a file with an exact match in the extension only. */
+ int i, num_files, name_len;
+ ArchiveList_struct *list, *lp;
+ num_files = unrar_exec_list(rar_filename, &list);
+ if (num_files > 0) {
+ char *demanded_ext;
+ demanded_ext = strrchr (p, '.');
+ if (demanded_ext) {
+ int demanded_ext_len = strlen (demanded_ext);
+ for (i = 0, lp = list; i < num_files; i++, lp = lp->next) {
+ name_len = strlen (lp->item.Name);
+ if (name_len >= demanded_ext_len && !strcasecmp (lp->item.Name + name_len - demanded_ext_len, demanded_ext)) {
+ rc = unrar_exec_get(&stream->data, &stream->size,
+ lp->item.Name, rar_filename);
+ if (rc)
+ break;
+ }
+ }
+ }
+ unrar_exec_freelist(list);
+ }
+ if (!rc) {
+ free(rar_filename);
+ free(stream);
+ return NULL;
+ }
+ }
+
+ free(rar_filename);
+ stream->pos = 0;
+ }
+ return stream;
+}
+
+static int rar_close(rar_stream_t *stream)
+{
+ if (stream->file)
+ return fclose(stream->file);
+ free(stream->data);
+ return 0;
+}
+
+static int rar_eof(rar_stream_t *stream)
+{
+ if (stream->file)
+ return feof(stream->file);
+ return stream->pos >= stream->size;
+}
+
+static long rar_tell(rar_stream_t *stream)
+{
+ if (stream->file)
+ return ftell(stream->file);
+ return stream->pos;
+}
+
+static int rar_seek(rar_stream_t *stream, long offset, int whence)
+{
+ if (stream->file)
+ return fseek(stream->file, offset, whence);
+ switch (whence) {
+ case SEEK_SET:
+ if (offset < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ stream->pos = offset;
+ break;
+ case SEEK_CUR:
+ if (offset < 0 && stream->pos < (unsigned long) -offset) {
+ errno = EINVAL;
+ return -1;
+ }
+ stream->pos += offset;
+ break;
+ case SEEK_END:
+ if (offset < 0 && stream->size < (unsigned long) -offset) {
+ errno = EINVAL;
+ return -1;
+ }
+ stream->pos = stream->size + offset;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+static int rar_getc(rar_stream_t *stream)
+{
+ if (stream->file)
+ return getc(stream->file);
+ if (rar_eof(stream))
+ return EOF;
+ return stream->data[stream->pos++];
+}
+
+static size_t rar_read(void *ptr, size_t size, size_t nmemb,
+ rar_stream_t *stream)
+{
+ size_t res;
+ unsigned long remain;
+ if (stream->file)
+ return fread(ptr, size, nmemb, stream->file);
+ if (rar_eof(stream))
+ return 0;
+ res = size * nmemb;
+ remain = stream->size - stream->pos;
+ if (res > remain)
+ res = remain / size * size;
+ memcpy(ptr, stream->data + stream->pos, res);
+ stream->pos += res;
+ res /= size;
+ return res;
+}
+
+#else
+typedef FILE rar_stream_t;
+#define rar_open fopen
+#define rar_close fclose
+#define rar_eof feof
+#define rar_tell ftell
+#define rar_seek fseek
+#define rar_getc getc
+#define rar_read fread
+#endif
+
+/**********************************************************************/
+
+static ssize_t vobsub_getline(char **lineptr, size_t *n, rar_stream_t *stream)
+{
+ size_t res = 0;
+ int c;
+ if (*lineptr == NULL) {
+ *lineptr = malloc(4096);
+ if (*lineptr)
+ *n = 4096;
+ } else if (*n == 0) {
+ char *tmp = realloc(*lineptr, 4096);
+ if (tmp) {
+ *lineptr = tmp;
+ *n = 4096;
+ }
+ }
+ if (*lineptr == NULL || *n == 0)
+ return -1;
+
+ for (c = rar_getc(stream); c != EOF; c = rar_getc(stream)) {
+ if (res + 1 >= *n) {
+ char *tmp = realloc(*lineptr, *n * 2);
+ if (tmp == NULL)
+ return -1;
+ *lineptr = tmp;
+ *n *= 2;
+ }
+ (*lineptr)[res++] = c;
+ if (c == '\n') {
+ (*lineptr)[res] = 0;
+ return res;
+ }
+ }
+ if (res == 0)
+ return -1;
+ (*lineptr)[res] = 0;
+ return res;
+}
+
+/**********************************************************************
+ * MPEG parsing
+ **********************************************************************/
+
+typedef struct {
+ rar_stream_t *stream;
+ unsigned int pts;
+ int aid;
+ unsigned char *packet;
+ unsigned int packet_reserve;
+ unsigned int packet_size;
+ int padding_was_here;
+ int merge;
+} mpeg_t;
+
+static mpeg_t *mpeg_open(const char *filename)
+{
+ mpeg_t *res = malloc(sizeof(mpeg_t));
+ int err = res == NULL;
+ if (!err) {
+ res->pts = 0;
+ res->aid = -1;
+ res->packet = NULL;
+ res->packet_size = 0;
+ res->packet_reserve = 0;
+ res->padding_was_here = 1;
+ res->merge = 0;
+ res->stream = rar_open(filename, "rb");
+ err = res->stream == NULL;
+ if (err)
+ perror("fopen Vobsub file failed");
+ if (err)
+ free(res);
+ }
+ return err ? NULL : res;
+}
+
+static void mpeg_free(mpeg_t *mpeg)
+{
+ free(mpeg->packet);
+ if (mpeg->stream)
+ rar_close(mpeg->stream);
+ free(mpeg);
+}
+
+static int mpeg_eof(mpeg_t *mpeg)
+{
+ return rar_eof(mpeg->stream);
+}
+
+static off_t mpeg_tell(mpeg_t *mpeg)
+{
+ return rar_tell(mpeg->stream);
+}
+
+static int mpeg_run(mpeg_t *mpeg)
+{
+ unsigned int len, idx, version;
+ int c;
+ /* Goto start of a packet, it starts with 0x000001?? */
+ const unsigned char wanted[] = { 0, 0, 1 };
+ unsigned char buf[5];
+
+ mpeg->aid = -1;
+ mpeg->packet_size = 0;
+ if (rar_read(buf, 4, 1, mpeg->stream) != 1)
+ return -1;
+ while (memcmp(buf, wanted, sizeof(wanted)) != 0) {
+ c = rar_getc(mpeg->stream);
+ if (c < 0)
+ return -1;
+ memmove(buf, buf + 1, 3);
+ buf[3] = c;
+ }
+ switch (buf[3]) {
+ case 0xb9: /* System End Code */
+ break;
+ case 0xba: /* Packet start code */
+ c = rar_getc(mpeg->stream);
+ if (c < 0)
+ return -1;
+ if ((c & 0xc0) == 0x40)
+ version = 4;
+ else if ((c & 0xf0) == 0x20)
+ version = 2;
+ else {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: Unsupported MPEG version: 0x%02x\n", c);
+ return -1;
+ }
+ if (version == 4) {
+ if (rar_seek(mpeg->stream, 9, SEEK_CUR))
+ return -1;
+ } else if (version == 2) {
+ if (rar_seek(mpeg->stream, 7, SEEK_CUR))
+ return -1;
+ } else
+ abort();
+ if (!mpeg->padding_was_here)
+ mpeg->merge = 1;
+ break;
+ case 0xbd: /* packet */
+ if (rar_read(buf, 2, 1, mpeg->stream) != 1)
+ return -1;
+ mpeg->padding_was_here = 0;
+ len = buf[0] << 8 | buf[1];
+ idx = mpeg_tell(mpeg);
+ c = rar_getc(mpeg->stream);
+ if (c < 0)
+ return -1;
+ if ((c & 0xC0) == 0x40) { /* skip STD scale & size */
+ if (rar_getc(mpeg->stream) < 0)
+ return -1;
+ c = rar_getc(mpeg->stream);
+ if (c < 0)
+ return -1;
+ }
+ if ((c & 0xf0) == 0x20) { /* System-1 stream timestamp */
+ /* Do we need this? */
+ abort();
+ } else if ((c & 0xf0) == 0x30) {
+ /* Do we need this? */
+ abort();
+ } else if ((c & 0xc0) == 0x80) { /* System-2 (.VOB) stream */
+ unsigned int pts_flags, hdrlen, dataidx;
+ c = rar_getc(mpeg->stream);
+ if (c < 0)
+ return -1;
+ pts_flags = c;
+ c = rar_getc(mpeg->stream);
+ if (c < 0)
+ return -1;
+ hdrlen = c;
+ dataidx = mpeg_tell(mpeg) + hdrlen;
+ if (dataidx > idx + len) {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "Invalid header length: %d (total length: %d, idx: %d, dataidx: %d)\n",
+ hdrlen, len, idx, dataidx);
+ return -1;
+ }
+ if ((pts_flags & 0xc0) == 0x80) {
+ if (rar_read(buf, 5, 1, mpeg->stream) != 1)
+ return -1;
+ if (!(((buf[0] & 0xf0) == 0x20) && (buf[0] & 1) && (buf[2] & 1) && (buf[4] & 1))) {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "vobsub PTS error: 0x%02x %02x%02x %02x%02x \n",
+ buf[0], buf[1], buf[2], buf[3], buf[4]);
+ mpeg->pts = 0;
+ } else
+ mpeg->pts = ((buf[0] & 0x0e) << 29 | buf[1] << 22 | (buf[2] & 0xfe) << 14
+ | buf[3] << 7 | (buf[4] >> 1));
+ } else /* if ((pts_flags & 0xc0) == 0xc0) */ {
+ /* what's this? */
+ /* abort(); */
+ }
+ rar_seek(mpeg->stream, dataidx, SEEK_SET);
+ mpeg->aid = rar_getc(mpeg->stream);
+ if (mpeg->aid < 0) {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "Bogus aid %d\n", mpeg->aid);
+ return -1;
+ }
+ mpeg->packet_size = len - ((unsigned int) mpeg_tell(mpeg) - idx);
+ if (mpeg->packet_reserve < mpeg->packet_size) {
+ free(mpeg->packet);
+ mpeg->packet = malloc(mpeg->packet_size);
+ if (mpeg->packet)
+ mpeg->packet_reserve = mpeg->packet_size;
+ }
+ if (mpeg->packet == NULL) {
+ mp_msg(MSGT_VOBSUB, MSGL_FATAL, "malloc failure");
+ mpeg->packet_reserve = 0;
+ mpeg->packet_size = 0;
+ return -1;
+ }
+ if (rar_read(mpeg->packet, mpeg->packet_size, 1, mpeg->stream) != 1) {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "fread failure");
+ mpeg->packet_size = 0;
+ return -1;
+ }
+ idx = len;
+ }
+ break;
+ case 0xbe: /* Padding */
+ if (rar_read(buf, 2, 1, mpeg->stream) != 1)
+ return -1;
+ len = buf[0] << 8 | buf[1];
+ if (len > 0 && rar_seek(mpeg->stream, len, SEEK_CUR))
+ return -1;
+ mpeg->padding_was_here = 1;
+ break;
+ default:
+ if (0xc0 <= buf[3] && buf[3] < 0xf0) {
+ /* MPEG audio or video */
+ if (rar_read(buf, 2, 1, mpeg->stream) != 1)
+ return -1;
+ len = buf[0] << 8 | buf[1];
+ if (len > 0 && rar_seek(mpeg->stream, len, SEEK_CUR))
+ return -1;
+ } else {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "unknown header 0x%02X%02X%02X%02X\n",
+ buf[0], buf[1], buf[2], buf[3]);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**********************************************************************
+ * Packet queue
+ **********************************************************************/
+
+typedef struct {
+ unsigned int pts100;
+ off_t filepos;
+ unsigned int size;
+ unsigned char *data;
+} packet_t;
+
+typedef struct {
+ char *id;
+ packet_t *packets;
+ unsigned int packets_reserve;
+ unsigned int packets_size;
+ unsigned int current_index;
+} packet_queue_t;
+
+static void packet_construct(packet_t *pkt)
+{
+ pkt->pts100 = 0;
+ pkt->filepos = 0;
+ pkt->size = 0;
+ pkt->data = NULL;
+}
+
+static void packet_destroy(packet_t *pkt)
+{
+ free(pkt->data);
+}
+
+static void packet_queue_construct(packet_queue_t *queue)
+{
+ queue->id = NULL;
+ queue->packets = NULL;
+ queue->packets_reserve = 0;
+ queue->packets_size = 0;
+ queue->current_index = 0;
+}
+
+static void packet_queue_destroy(packet_queue_t *queue)
+{
+ if (queue->packets) {
+ while (queue->packets_size--)
+ packet_destroy(queue->packets + queue->packets_size);
+ free(queue->packets);
+ }
+ return;
+}
+
+/* Make sure there is enough room for needed_size packets in the
+ packet queue. */
+static int packet_queue_ensure(packet_queue_t *queue, unsigned int needed_size)
+{
+ if (queue->packets_reserve < needed_size) {
+ if (queue->packets) {
+ packet_t *tmp = realloc(queue->packets, 2 * queue->packets_reserve * sizeof(packet_t));
+ if (tmp == NULL) {
+ mp_msg(MSGT_VOBSUB, MSGL_FATAL, "realloc failure");
+ return -1;
+ }
+ queue->packets = tmp;
+ queue->packets_reserve *= 2;
+ } else {
+ queue->packets = malloc(sizeof(packet_t));
+ if (queue->packets == NULL) {
+ mp_msg(MSGT_VOBSUB, MSGL_FATAL, "malloc failure");
+ return -1;
+ }
+ queue->packets_reserve = 1;
+ }
+ }
+ return 0;
+}
+
+/* add one more packet */
+static int packet_queue_grow(packet_queue_t *queue)
+{
+ if (packet_queue_ensure(queue, queue->packets_size + 1) < 0)
+ return -1;
+ packet_construct(queue->packets + queue->packets_size);
+ ++queue->packets_size;
+ return 0;
+}
+
+/* insert a new packet, duplicating pts from the current one */
+static int packet_queue_insert(packet_queue_t *queue)
+{
+ packet_t *pkts;
+ if (packet_queue_ensure(queue, queue->packets_size + 1) < 0)
+ return -1;
+ /* XXX packet_size does not reflect the real thing here, it will be updated a bit later */
+ memmove(queue->packets + queue->current_index + 2,
+ queue->packets + queue->current_index + 1,
+ sizeof(packet_t) * (queue->packets_size - queue->current_index - 1));
+ pkts = queue->packets + queue->current_index;
+ ++queue->packets_size;
+ ++queue->current_index;
+ packet_construct(pkts + 1);
+ pkts[1].pts100 = pkts[0].pts100;
+ pkts[1].filepos = pkts[0].filepos;
+ return 0;
+}
+
+/**********************************************************************
+ * Vobsub
+ **********************************************************************/
+
+typedef struct {
+ unsigned int palette[16];
+ int delay;
+ unsigned int have_palette;
+ unsigned int orig_frame_width, orig_frame_height;
+ unsigned int origin_x, origin_y;
+ /* index */
+ packet_queue_t *spu_streams;
+ unsigned int spu_streams_size;
+ unsigned int spu_streams_current;
+ unsigned int spu_valid_streams_size;
+} vobsub_t;
+
+/* Make sure that the spu stream idx exists. */
+static int vobsub_ensure_spu_stream(vobsub_t *vob, unsigned int index)
+{
+ if (index >= vob->spu_streams_size) {
+ /* This is a new stream */
+ if (vob->spu_streams) {
+ packet_queue_t *tmp = realloc(vob->spu_streams, (index + 1) * sizeof(packet_queue_t));
+ if (tmp == NULL) {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "vobsub_ensure_spu_stream: realloc failure");
+ return -1;
+ }
+ vob->spu_streams = tmp;
+ } else {
+ vob->spu_streams = malloc((index + 1) * sizeof(packet_queue_t));
+ if (vob->spu_streams == NULL) {
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "vobsub_ensure_spu_stream: malloc failure");
+ return -1;
+ }
+ }
+ while (vob->spu_streams_size <= index) {
+ packet_queue_construct(vob->spu_streams + vob->spu_streams_size);
+ ++vob->spu_streams_size;
+ }
+ }
+ return 0;
+}
+
+static int vobsub_add_id(vobsub_t *vob, const char *id, size_t idlen,
+ const unsigned int index)
+{
+ if (vobsub_ensure_spu_stream(vob, index) < 0)
+ return -1;
+ if (id && idlen) {
+ free(vob->spu_streams[index].id);
+ vob->spu_streams[index].id = malloc(idlen + 1);
+ if (vob->spu_streams[index].id == NULL) {
+ mp_msg(MSGT_VOBSUB, MSGL_FATAL, "vobsub_add_id: malloc failure");
+ return -1;
+ }
+ vob->spu_streams[index].id[idlen] = 0;
+ memcpy(vob->spu_streams[index].id, id, idlen);
+ }
+ vob->spu_streams_current = index;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VOBSUB_ID=%d\n", index);
+ if (id && idlen)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VSID_%d_LANG=%s\n", index, vob->spu_streams[index].id);
+ mp_msg(MSGT_VOBSUB, MSGL_V, "[vobsub] subtitle (vobsubid): %d language %s\n",
+ index, vob->spu_streams[index].id);
+ return 0;
+}
+
+static int vobsub_add_timestamp(vobsub_t *vob, off_t filepos, int ms)
+{
+ packet_queue_t *queue;
+ packet_t *pkt;
+ if (vob->spu_streams == 0) {
+ mp_msg(MSGT_VOBSUB, MSGL_WARN, "[vobsub] warning, binning some index entries. Check your index file\n");
+ return -1;
+ }
+ queue = vob->spu_streams + vob->spu_streams_current;
+ if (packet_queue_grow(queue) >= 0) {
+ pkt = queue->packets + (queue->packets_size - 1);
+ pkt->filepos = filepos;
+ pkt->pts100 = ms < 0 ? UINT_MAX : (unsigned int)ms * 90;
+ return 0;
+ }
+ return -1;
+}
+
+static int vobsub_parse_id(vobsub_t *vob, const char *line)
+{
+ // id: xx, index: n
+ size_t idlen;
+ const char *p, *q;
+ p = line;
+ while (isspace(*p))
+ ++p;
+ q = p;
+ while (isalpha(*q))
+ ++q;
+ idlen = q - p;
+ if (idlen == 0)
+ return -1;
+ ++q;
+ while (isspace(*q))
+ ++q;
+ if (strncmp("index:", q, 6))
+ return -1;
+ q += 6;
+ while (isspace(*q))
+ ++q;
+ if (!isdigit(*q))
+ return -1;
+ return vobsub_add_id(vob, p, idlen, atoi(q));
+}
+
+static int vobsub_parse_timestamp(vobsub_t *vob, const char *line)
+{
+ // timestamp: HH:MM:SS.mmm, filepos: 0nnnnnnnnn
+ const char *p;
+ int h, m, s, ms;
+ off_t filepos;
+ while (isspace(*line))
+ ++line;
+ p = line;
+ while (isdigit(*p))
+ ++p;
+ if (p - line != 2)
+ return -1;
+ h = atoi(line);
+ if (*p != ':')
+ return -1;
+ line = ++p;
+ while (isdigit(*p))
+ ++p;
+ if (p - line != 2)
+ return -1;
+ m = atoi(line);
+ if (*p != ':')
+ return -1;
+ line = ++p;
+ while (isdigit(*p))
+ ++p;
+ if (p - line != 2)
+ return -1;
+ s = atoi(line);
+ if (*p != ':')
+ return -1;
+ line = ++p;
+ while (isdigit(*p))
+ ++p;
+ if (p - line != 3)
+ return -1;
+ ms = atoi(line);
+ if (*p != ',')
+ return -1;
+ line = p + 1;
+ while (isspace(*line))
+ ++line;
+ if (strncmp("filepos:", line, 8))
+ return -1;
+ line += 8;
+ while (isspace(*line))
+ ++line;
+ if (! isxdigit(*line))
+ return -1;
+ filepos = strtol(line, NULL, 16);
+ return vobsub_add_timestamp(vob, filepos, vob->delay + ms + 1000 * (s + 60 * (m + 60 * h)));
+}
+
+static int vobsub_parse_origin(vobsub_t *vob, const char *line)
+{
+ // org: X,Y
+ char *p;
+ while (isspace(*line))
+ ++line;
+ if (!isdigit(*line))
+ return -1;
+ vob->origin_x = strtoul(line, &p, 10);
+ if (*p != ',')
+ return -1;
+ ++p;
+ vob->origin_y = strtoul(p, NULL, 10);
+ return 0;
+}
+
+unsigned int vobsub_palette_to_yuv(unsigned int pal)
+{
+ int r, g, b, y, u, v;
+ // Palette in idx file is not rgb value, it was calculated by wrong formula.
+ // Here's reversed formula of the one used to generate palette in idx file.
+ r = pal >> 16 & 0xff;
+ g = pal >> 8 & 0xff;
+ b = pal & 0xff;
+ y = av_clip_uint8( 0.1494 * r + 0.6061 * g + 0.2445 * b);
+ u = av_clip_uint8( 0.6066 * r - 0.4322 * g - 0.1744 * b + 128);
+ v = av_clip_uint8(-0.08435 * r - 0.3422 * g + 0.4266 * b + 128);
+ y = y * 219 / 255 + 16;
+ return y << 16 | u << 8 | v;
+}
+
+unsigned int vobsub_rgb_to_yuv(unsigned int rgb)
+{
+ int r, g, b, y, u, v;
+ r = rgb >> 16 & 0xff;
+ g = rgb >> 8 & 0xff;
+ b = rgb & 0xff;
+ y = ( 0.299 * r + 0.587 * g + 0.114 * b) * 219 / 255 + 16.5;
+ u = (-0.16874 * r - 0.33126 * g + 0.5 * b) * 224 / 255 + 128.5;
+ v = ( 0.5 * r - 0.41869 * g - 0.08131 * b) * 224 / 255 + 128.5;
+ return y << 16 | u << 8 | v;
+}
+
+static int vobsub_parse_delay(vobsub_t *vob, const char *line)
+{
+ int h, m, s, ms;
+ int forward = 1;
+ if (*(line + 7) == '+') {
+ forward = 1;
+ line++;
+ } else if (*(line + 7) == '-') {
+ forward = -1;
+ line++;
+ }
+ mp_msg(MSGT_SPUDEC, MSGL_V, "forward=%d", forward);
+ h = atoi(line + 7);
+ mp_msg(MSGT_VOBSUB, MSGL_V, "h=%d,", h);
+ m = atoi(line + 10);
+ mp_msg(MSGT_VOBSUB, MSGL_V, "m=%d,", m);
+ s = atoi(line + 13);
+ mp_msg(MSGT_VOBSUB, MSGL_V, "s=%d,", s);
+ ms = atoi(line + 16);
+ mp_msg(MSGT_VOBSUB, MSGL_V, "ms=%d", ms);
+ vob->delay = (ms + 1000 * (s + 60 * (m + 60 * h))) * forward;
+ return 0;
+}
+
+static int vobsub_set_lang(const char *line)
+{
+ if (vobsub_id == -1)
+ vobsub_id = atoi(line + 8);
+ return 0;
+}
+
+static int vobsub_parse_one_line(vobsub_t *vob, rar_stream_t *fd,
+ unsigned char **extradata,
+ unsigned int *extradata_len)
+{
+ ssize_t line_size;
+ int res = -1;
+ size_t line_reserve = 0;
+ char *line = NULL;
+ do {
+ line_size = vobsub_getline(&line, &line_reserve, fd);
+ if (line_size < 0 || line_size > 1000000 ||
+ *extradata_len+line_size > 10000000) {
+ break;
+ }
+
+ *extradata = realloc(*extradata, *extradata_len+line_size+1);
+ memcpy(*extradata+*extradata_len, line, line_size);
+ *extradata_len += line_size;
+ (*extradata)[*extradata_len] = 0;
+
+ if (*line == 0 || *line == '\r' || *line == '\n' || *line == '#')
+ continue;
+ else if (strncmp("langidx:", line, 8) == 0)
+ res = vobsub_set_lang(line);
+ else if (strncmp("delay:", line, 6) == 0)
+ res = vobsub_parse_delay(vob, line);
+ else if (strncmp("id:", line, 3) == 0)
+ res = vobsub_parse_id(vob, line + 3);
+ else if (strncmp("org:", line, 4) == 0)
+ res = vobsub_parse_origin(vob, line + 4);
+ else if (strncmp("timestamp:", line, 10) == 0)
+ res = vobsub_parse_timestamp(vob, line + 10);
+ else {
+ mp_msg(MSGT_VOBSUB, MSGL_V, "vobsub: ignoring %s", line);
+ continue;
+ }
+ if (res < 0)
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "ERROR in %s", line);
+ break;
+ } while (1);
+ free(line);
+ return res;
+}
+
+int vobsub_parse_ifo(void* this, const char *const name, unsigned int *palette,
+ unsigned int *width, unsigned int *height, int force,
+ int sid, char *langid)
+{
+ vobsub_t *vob = this;
+ int res = -1;
+ rar_stream_t *fd = rar_open(name, "rb");
+ if (fd == NULL) {
+ if (force)
+ mp_msg(MSGT_VOBSUB, MSGL_WARN, "VobSub: Can't open IFO file\n");
+ } else {
+ // parse IFO header
+ unsigned char block[0x800];
+ const char *const ifo_magic = "DVDVIDEO-VTS";
+ if (rar_read(block, sizeof(block), 1, fd) != 1) {
+ if (force)
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: Can't read IFO header\n");
+ } else if (memcmp(block, ifo_magic, strlen(ifo_magic) + 1))
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: Bad magic in IFO header\n");
+ else {
+ unsigned long pgci_sector = block[0xcc] << 24 | block[0xcd] << 16
+ | block[0xce] << 8 | block[0xcf];
+ int standard = (block[0x200] & 0x30) >> 4;
+ int resolution = (block[0x201] & 0x0c) >> 2;
+ *height = standard ? 576 : 480;
+ *width = 0;
+ switch (resolution) {
+ case 0x0:
+ *width = 720;
+ break;
+ case 0x1:
+ *width = 704;
+ break;
+ case 0x2:
+ *width = 352;
+ break;
+ case 0x3:
+ *width = 352;
+ *height /= 2;
+ break;
+ default:
+ mp_msg(MSGT_VOBSUB, MSGL_WARN, "Vobsub: Unknown resolution %d \n", resolution);
+ }
+ if (langid && 0 <= sid && sid < 32) {
+ unsigned char *tmp = block + 0x256 + sid * 6 + 2;
+ langid[0] = tmp[0];
+ langid[1] = tmp[1];
+ langid[2] = 0;
+ }
+ if (rar_seek(fd, pgci_sector * sizeof(block), SEEK_SET)
+ || rar_read(block, sizeof(block), 1, fd) != 1)
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: Can't read IFO PGCI\n");
+ else {
+ unsigned long idx;
+ unsigned long pgc_offset = block[0xc] << 24 | block[0xd] << 16
+ | block[0xe] << 8 | block[0xf];
+ for (idx = 0; idx < 16; ++idx) {
+ unsigned char *p = block + pgc_offset + 0xa4 + 4 * idx;
+ palette[idx] = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+ }
+ if (vob)
+ vob->have_palette = 1;
+ res = 0;
+ }
+ }
+ rar_close(fd);
+ }
+ return res;
+}
+
+void *vobsub_open(const char *const name, const char *const ifo,
+ const int force, void** spu)
+{
+ unsigned char *extradata = NULL;
+ unsigned int extradata_len = 0;
+ vobsub_t *vob = calloc(1, sizeof(vobsub_t));
+ if (spu)
+ *spu = NULL;
+ if (vobsubid == -2)
+ vobsubid = vobsub_id;
+ if (vob) {
+ char *buf;
+ buf = malloc(strlen(name) + 5);
+ if (buf) {
+ rar_stream_t *fd;
+ mpeg_t *mpg;
+ /* read in the info file */
+ if (!ifo) {
+ strcpy(buf, name);
+ strcat(buf, ".ifo");
+ vobsub_parse_ifo(vob, buf, vob->palette, &vob->orig_frame_width, &vob->orig_frame_height, force, -1, NULL);
+ } else
+ vobsub_parse_ifo(vob, ifo, vob->palette, &vob->orig_frame_width, &vob->orig_frame_height, force, -1, NULL);
+ /* read in the index */
+ strcpy(buf, name);
+ strcat(buf, ".idx");
+ fd = rar_open(buf, "rb");
+ if (fd == NULL) {
+ if (force)
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: Can't open IDX file\n");
+ else {
+ free(buf);
+ free(vob);
+ return NULL;
+ }
+ } else {
+ while (vobsub_parse_one_line(vob, fd, &extradata, &extradata_len) >= 0)
+ /* NOOP */ ;
+ rar_close(fd);
+ }
+ if (spu)
+ *spu = spudec_new_scaled(vob->palette, vob->orig_frame_width, vob->orig_frame_height, extradata, extradata_len);
+ free(extradata);
+
+ /* read the indexed mpeg_stream */
+ strcpy(buf, name);
+ strcat(buf, ".sub");
+ mpg = mpeg_open(buf);
+ if (mpg == NULL) {
+ if (force)
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: Can't open SUB file\n");
+ else {
+ free(buf);
+ free(vob);
+ return NULL;
+ }
+ } else {
+ long last_pts_diff = 0;
+ while (!mpeg_eof(mpg)) {
+ off_t pos = mpeg_tell(mpg);
+ if (mpeg_run(mpg) < 0) {
+ if (!mpeg_eof(mpg))
+ mp_msg(MSGT_VOBSUB, MSGL_ERR, "VobSub: mpeg_run error\n");
+ break;
+ }
+ if (mpg->packet_size) {
+ if ((mpg->aid & 0xe0) == 0x20) {
+ unsigned int sid = mpg->aid & 0x1f;
+ if (vobsub_ensure_spu_stream(vob, sid) >= 0) {
+ packet_queue_t *queue = vob->spu_streams + sid;
+ /* get the packet to fill */
+ if (queue->packets_size == 0 && packet_queue_grow(queue) < 0)
+ abort();
+ while (queue->current_index + 1 < queue->packets_size
+ && queue->packets[queue->current_index + 1].filepos <= pos)
+ ++queue->current_index;
+ if (queue->current_index < queue->packets_size) {
+ packet_t *pkt;
+ if (queue->packets[queue->current_index].data) {
+ /* insert a new packet and fix the PTS ! */
+ packet_queue_insert(queue);
+ queue->packets[queue->current_index].pts100 =
+ mpg->pts + last_pts_diff;
+ }
+ pkt = queue->packets + queue->current_index;
+ if (pkt->pts100 != UINT_MAX) {
+ if (queue->packets_size > 1)
+ last_pts_diff = pkt->pts100 - mpg->pts;
+ else
+ pkt->pts100 = mpg->pts;
+ if (mpg->merge && queue->current_index > 0) {
+ packet_t *last = &queue->packets[queue->current_index - 1];
+ pkt->pts100 = last->pts100;
+ }
+ mpg->merge = 0;
+ /* FIXME: should not use mpg_sub internal informations, make a copy */
+ pkt->data = mpg->packet;
+ pkt->size = mpg->packet_size;
+ mpg->packet = NULL;
+ mpg->packet_reserve = 0;
+ mpg->packet_size = 0;
+ }
+ }
+ } else
+ mp_msg(MSGT_VOBSUB, MSGL_WARN, "don't know what to do with subtitle #%u\n", sid);
+ }
+ }
+ }
+ vob->spu_streams_current = vob->spu_streams_size;
+ while (vob->spu_streams_current-- > 0) {
+ vob->spu_streams[vob->spu_streams_current].current_index = 0;
+ if (vobsubid == vob->spu_streams_current ||
+ vob->spu_streams[vob->spu_streams_current].packets_size > 0)
+ ++vob->spu_valid_streams_size;
+ }
+ mpeg_free(mpg);
+ }
+ free(buf);
+ }
+ }
+ return vob;
+}
+
+void vobsub_close(void *this)
+{
+ vobsub_t *vob = this;
+ if (vob->spu_streams) {
+ while (vob->spu_streams_size--)
+ packet_queue_destroy(vob->spu_streams + vob->spu_streams_size);
+ free(vob->spu_streams);
+ }
+ free(vob);
+}
+
+unsigned int vobsub_get_indexes_count(void *vobhandle)
+{
+ vobsub_t *vob = vobhandle;
+ return vob->spu_valid_streams_size;
+}
+
+char *vobsub_get_id(void *vobhandle, unsigned int index)
+{
+ vobsub_t *vob = vobhandle;
+ return (index < vob->spu_streams_size) ? vob->spu_streams[index].id : NULL;
+}
+
+int vobsub_get_id_by_index(void *vobhandle, unsigned int index)
+{
+ vobsub_t *vob = vobhandle;
+ int i, j;
+ if (vob == NULL)
+ return -1;
+ for (i = 0, j = 0; i < vob->spu_streams_size; ++i)
+ if (i == vobsubid || vob->spu_streams[i].packets_size > 0) {
+ if (j == index)
+ return i;
+ ++j;
+ }
+ return -1;
+}
+
+int vobsub_get_index_by_id(void *vobhandle, int id)
+{
+ vobsub_t *vob = vobhandle;
+ int i, j;
+ if (vob == NULL || id < 0 || id >= vob->spu_streams_size)
+ return -1;
+ if (id != vobsubid && !vob->spu_streams[id].packets_size)
+ return -1;
+ for (i = 0, j = 0; i < id; ++i)
+ if (i == vobsubid || vob->spu_streams[i].packets_size > 0)
+ ++j;
+ return j;
+}
+
+int vobsub_set_from_lang(void *vobhandle, unsigned char * lang)
+{
+ int i;
+ vobsub_t *vob= vobhandle;
+ while (lang && strlen(lang) >= 2) {
+ for (i = 0; i < vob->spu_streams_size; i++)
+ if (vob->spu_streams[i].id)
+ if ((strncmp(vob->spu_streams[i].id, lang, 2) == 0)) {
+ vobsub_id = i;
+ mp_msg(MSGT_VOBSUB, MSGL_INFO, "Selected VOBSUB language: %d language: %s\n", i, vob->spu_streams[i].id);
+ return 0;
+ }
+ lang+=2;while (lang[0]==',' || lang[0]==' ') ++lang;
+ }
+ mp_msg(MSGT_VOBSUB, MSGL_WARN, "No matching VOBSUB language found!\n");
+ return -1;
+}
+
+/// make sure we seek to the first packet of packets having same pts values.
+static void vobsub_queue_reseek(packet_queue_t *queue, unsigned int pts100)
+{
+ int reseek_count = 0;
+ unsigned int lastpts = 0;
+
+ if (queue->current_index > 0
+ && (queue->packets[queue->current_index].pts100 == UINT_MAX
+ || queue->packets[queue->current_index].pts100 > pts100)) {
+ // possible pts seek previous, try to check it.
+ int i = 1;
+ while (queue->current_index >= i
+ && queue->packets[queue->current_index-i].pts100 == UINT_MAX)
+ ++i;
+ if (queue->current_index >= i
+ && queue->packets[queue->current_index-i].pts100 > pts100)
+ // pts seek previous confirmed, reseek from beginning
+ queue->current_index = 0;
+ }
+ while (queue->current_index < queue->packets_size
+ && queue->packets[queue->current_index].pts100 <= pts100) {
+ lastpts = queue->packets[queue->current_index].pts100;
+ ++queue->current_index;
+ ++reseek_count;
+ }
+ while (reseek_count-- && --queue->current_index) {
+ if (queue->packets[queue->current_index-1].pts100 != UINT_MAX &&
+ queue->packets[queue->current_index-1].pts100 != lastpts)
+ break;
+ }
+}
+
+int vobsub_get_packet(void *vobhandle, float pts, void** data, int* timestamp)
+{
+ vobsub_t *vob = vobhandle;
+ unsigned int pts100 = 90000 * pts;
+ if (vob->spu_streams && 0 <= vobsub_id && (unsigned) vobsub_id < vob->spu_streams_size) {
+ packet_queue_t *queue = vob->spu_streams + vobsub_id;
+
+ vobsub_queue_reseek(queue, pts100);
+
+ while (queue->current_index < queue->packets_size) {
+ packet_t *pkt = queue->packets + queue->current_index;
+ if (pkt->pts100 != UINT_MAX)
+ if (pkt->pts100 <= pts100) {
+ ++queue->current_index;
+ *data = pkt->data;
+ *timestamp = pkt->pts100;
+ return pkt->size;
+ } else
+ break;
+ else
+ ++queue->current_index;
+ }
+ }
+ return -1;
+}
+
+int vobsub_get_next_packet(void *vobhandle, void** data, int* timestamp)
+{
+ vobsub_t *vob = vobhandle;
+ if (vob->spu_streams && 0 <= vobsub_id && (unsigned) vobsub_id < vob->spu_streams_size) {
+ packet_queue_t *queue = vob->spu_streams + vobsub_id;
+ if (queue->current_index < queue->packets_size) {
+ packet_t *pkt = queue->packets + queue->current_index;
+ ++queue->current_index;
+ *data = pkt->data;
+ *timestamp = pkt->pts100;
+ return pkt->size;
+ }
+ }
+ return -1;
+}
+
+void vobsub_seek(void * vobhandle, float pts)
+{
+ vobsub_t * vob = vobhandle;
+ packet_queue_t * queue;
+ int seek_pts100 = pts * 90000;
+
+ if (vob->spu_streams && 0 <= vobsub_id && (unsigned) vobsub_id < vob->spu_streams_size) {
+ /* do not seek if we don't know the id */
+ if (vobsub_get_id(vob, vobsub_id) == NULL)
+ return;
+ queue = vob->spu_streams + vobsub_id;
+ queue->current_index = 0;
+ vobsub_queue_reseek(queue, seek_pts100);
+ }
+}
+
+void vobsub_reset(void *vobhandle)
+{
+ vobsub_t *vob = vobhandle;
+ if (vob->spu_streams) {
+ unsigned int n = vob->spu_streams_size;
+ while (n-- > 0)
+ vob->spu_streams[n].current_index = 0;
+ }
+}
+
+/**********************************************************************
+ * Vobsub output
+ **********************************************************************/
+
+typedef struct {
+ FILE *fsub;
+ FILE *fidx;
+ unsigned int aid;
+} vobsub_out_t;
+
+static void create_idx(vobsub_out_t *me, const unsigned int *palette,
+ unsigned int orig_width, unsigned int orig_height)
+{
+ int i;
+ fprintf(me->fidx,
+ "# VobSub index file, v7 (do not modify this line!)\n"
+ "#\n"
+ "# Generated by %s\n"
+ "# See <URL:http://www.mplayerhq.hu/> for more information about MPlayer\n"
+ "# See <URL:http://wiki.multimedia.cx/index.php?title=VOBsub> for more information about Vobsub\n"
+ "#\n"
+ "size: %ux%u\n",
+ mplayer_version, orig_width, orig_height);
+ if (palette) {
+ fputs("palette:", me->fidx);
+ for (i = 0; i < 16; ++i) {
+ const double y = palette[i] >> 16 & 0xff,
+ u = (palette[i] >> 8 & 0xff) - 128.0,
+ v = (palette[i] & 0xff) - 128.0;
+ if (i)
+ putc(',', me->fidx);
+ fprintf(me->fidx, " %02x%02x%02x",
+ av_clip_uint8(y + 1.4022 * u),
+ av_clip_uint8(y - 0.3456 * u - 0.7145 * v),
+ av_clip_uint8(y + 1.7710 * v));
+ }
+ putc('\n', me->fidx);
+ }
+
+ fprintf(me->fidx, "# ON: displays only forced subtitles, OFF: shows everything\n"
+ "forced subs: OFF\n");
+}
+
+void *vobsub_out_open(const char *basename, const unsigned int *palette,
+ unsigned int orig_width, unsigned int orig_height,
+ const char *id, unsigned int index)
+{
+ vobsub_out_t *result = NULL;
+ char *filename;
+ filename = malloc(strlen(basename) + 5);
+ if (filename) {
+ result = malloc(sizeof(vobsub_out_t));
+ if (result) {
+ result->aid = index;
+ strcpy(filename, basename);
+ strcat(filename, ".sub");
+ result->fsub = fopen(filename, "ab");
+ if (result->fsub == NULL)
+ perror("Error: vobsub_out_open subtitle file open failed");
+ strcpy(filename, basename);
+ strcat(filename, ".idx");
+ result->fidx = fopen(filename, "ab");
+ if (result->fidx) {
+ if (ftell(result->fidx) == 0) {
+ create_idx(result, palette, orig_width, orig_height);
+ /* Make the selected language the default language */
+ fprintf(result->fidx, "\n# Language index in use\nlangidx: %u\n", index);
+ }
+ fprintf(result->fidx, "\nid: %s, index: %u\n", id ? id : "xx", index);
+ /* So that we can check the file now */
+ fflush(result->fidx);
+ } else
+ perror("Error: vobsub_out_open index file open failed");
+ free(filename);
+ }
+ }
+ return result;
+}
+
+void vobsub_out_close(void *me)
+{
+ vobsub_out_t *vob = me;
+ if (vob->fidx)
+ fclose(vob->fidx);
+ if (vob->fsub)
+ fclose(vob->fsub);
+ free(vob);
+}
+
+void vobsub_out_output(void *me, const unsigned char *packet,
+ int len, double pts)
+{
+ static double last_pts;
+ static int last_pts_set = 0;
+ vobsub_out_t *vob = me;
+ if (vob->fsub) {
+ /* Windows' Vobsub require that every packet is exactly 2kB long */
+ unsigned char buffer[2048];
+ unsigned char *p;
+ int remain = 2048;
+ /* Do not output twice a line with the same timestamp, this
+ breaks Windows' Vobsub */
+ if (vob->fidx && (!last_pts_set || last_pts != pts)) {
+ static unsigned int last_h = 9999, last_m = 9999, last_s = 9999, last_ms = 9999;
+ unsigned int h, m, ms;
+ double s;
+ s = pts;
+ h = s / 3600;
+ s -= h * 3600;
+ m = s / 60;
+ s -= m * 60;
+ ms = (s - (unsigned int) s) * 1000;
+ if (ms >= 1000) /* prevent overfolws or bad float stuff */
+ ms = 0;
+ if (h != last_h || m != last_m || (unsigned int) s != last_s || ms != last_ms) {
+ fprintf(vob->fidx, "timestamp: %02u:%02u:%02u:%03u, filepos: %09lx\n",
+ h, m, (unsigned int) s, ms, ftell(vob->fsub));
+ last_h = h;
+ last_m = m;
+ last_s = (unsigned int) s;
+ last_ms = ms;
+ }
+ }
+ last_pts = pts;
+ last_pts_set = 1;
+
+ /* Packet start code: Windows' Vobsub needs this */
+ p = buffer;
+ *p++ = 0; /* 0x00 */
+ *p++ = 0;
+ *p++ = 1;
+ *p++ = 0xba;
+ *p++ = 0x40;
+ memset(p, 0, 9);
+ p += 9;
+ { /* Packet */
+ static unsigned char last_pts[5] = { 0, 0, 0, 0, 0};
+ unsigned char now_pts[5];
+ int pts_len, pad_len, datalen = len;
+ pts *= 90000;
+ now_pts[0] = 0x21 | (((unsigned long)pts >> 29) & 0x0e);
+ now_pts[1] = ((unsigned long)pts >> 22) & 0xff;
+ now_pts[2] = 0x01 | (((unsigned long)pts >> 14) & 0xfe);
+ now_pts[3] = ((unsigned long)pts >> 7) & 0xff;
+ now_pts[4] = 0x01 | (((unsigned long)pts << 1) & 0xfe);
+ pts_len = memcmp(last_pts, now_pts, sizeof(now_pts)) ? sizeof(now_pts) : 0;
+ memcpy(last_pts, now_pts, sizeof(now_pts));
+
+ datalen += 3; /* Version, PTS_flags, pts_len */
+ datalen += pts_len;
+ datalen += 1; /* AID */
+ pad_len = 2048 - (p - buffer) - 4 /* MPEG ID */ - 2 /* payload len */ - datalen;
+ /* XXX - Go figure what should go here! In any case the
+ packet has to be completly filled. If I can fill it
+ with padding (0x000001be) latter I'll do that. But if
+ there is only room for 6 bytes then I can not write a
+ padding packet. So I add some padding in the PTS
+ field. This looks like a dirty kludge. Oh well... */
+ if (pad_len < 0) {
+ /* Packet is too big. Let's try ommiting the PTS field */
+ datalen -= pts_len;
+ pts_len = 0;
+ pad_len = 0;
+ } else if (pad_len > 6)
+ pad_len = 0;
+ datalen += pad_len;
+
+ *p++ = 0; /* 0x0e */
+ *p++ = 0;
+ *p++ = 1;
+ *p++ = 0xbd;
+
+ *p++ = (datalen >> 8) & 0xff; /* length of payload */
+ *p++ = datalen & 0xff;
+ *p++ = 0x80; /* System-2 (.VOB) stream */
+ *p++ = pts_len ? 0x80 : 0x00; /* pts_flags */
+ *p++ = pts_len + pad_len;
+ memcpy(p, now_pts, pts_len);
+ p += pts_len;
+ memset(p, 0, pad_len);
+ p += pad_len;
+ }
+ *p++ = 0x20 | vob->aid; /* aid */
+ if (fwrite(buffer, p - buffer, 1, vob->fsub) != 1
+ || fwrite(packet, len, 1, vob->fsub) != 1)
+ perror("ERROR: vobsub write failed");
+ else
+ remain -= p - buffer + len;
+
+ /* Padding */
+ if (remain >= 6) {
+ p = buffer;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x01;
+ *p++ = 0xbe;
+ *p++ = (remain - 6) >> 8;
+ *p++ = (remain - 6) & 0xff;
+ /* for better compression, blank this */
+ memset(buffer + 6, 0, remain - (p - buffer));
+ if (fwrite(buffer, remain, 1, vob->fsub) != 1)
+ perror("ERROR: vobsub padding write failed");
+ } else if (remain > 0) {
+ /* I don't know what to output. But anyway the block
+ needs to be 2KB big */
+ memset(buffer, 0, remain);
+ if (fwrite(buffer, remain, 1, vob->fsub) != 1)
+ perror("ERROR: vobsub blank padding write failed");
+ } else if (remain < 0)
+ fprintf(stderr,
+ "\nERROR: wrong thing happenned...\n"
+ " I wrote a %i data bytes spu packet and that's too long\n", len);
+ }
+}
diff --git a/sub/vobsub.h b/sub/vobsub.h
new file mode 100644
index 0000000000..b076e4b6bc
--- /dev/null
+++ b/sub/vobsub.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_VOBSUB_H
+#define MPLAYER_VOBSUB_H
+
+void *vobsub_open(const char *subname, const char *const ifo, const int force, void** spu);
+void vobsub_reset(void *vob);
+int vobsub_parse_ifo(void* this, const char *const name, unsigned int *palette, unsigned int *width, unsigned int *height, int force, int sid, char *langid);
+int vobsub_get_packet(void *vobhandle, float pts,void** data, int* timestamp);
+int vobsub_get_next_packet(void *vobhandle, void** data, int* timestamp);
+void vobsub_close(void *this);
+unsigned int vobsub_get_indexes_count(void * /* vobhandle */);
+char *vobsub_get_id(void * /* vobhandle */, unsigned int /* index */);
+
+/// Get vobsub id by its index in the valid streams.
+int vobsub_get_id_by_index(void *vobhandle, unsigned int index);
+/// Get index in the valid streams by vobsub id.
+int vobsub_get_index_by_id(void *vobhandle, int id);
+
+/// Convert palette value in idx file to yuv.
+unsigned int vobsub_palette_to_yuv(unsigned int pal);
+/// Convert rgb value to yuv.
+unsigned int vobsub_rgb_to_yuv(unsigned int rgb);
+
+void *vobsub_out_open(const char *basename, const unsigned int *palette, unsigned int orig_width, unsigned int orig_height, const char *id, unsigned int index);
+void vobsub_out_output(void *me, const unsigned char *packet, int len, double pts);
+void vobsub_out_close(void *me);
+int vobsub_set_from_lang(void *vobhandle, unsigned char * lang);
+void vobsub_seek(void * vobhandle, float pts);
+
+#endif /* MPLAYER_VOBSUB_H */