Fix bad parsing of ASS lines embedded into FFmpeg files and containing commas.
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index 5e2cb8638804b328e36e1c89cd127e3e449d2535..0746458e58fd5be806a75566b398772ad33ff618 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -78,7 +78,7 @@ using boost::optional;
 using boost::dynamic_pointer_cast;
 using dcp::Size;
 
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log, bool fast)
        : FFmpeg (c)
        , _log (log)
        , _have_current_subtitle (false)
@@ -94,11 +94,12 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
        }
 
        if (c->audio) {
-               audio.reset (new AudioDecoder (this, c->audio, log));
+               audio.reset (new AudioDecoder (this, c->audio, log, fast));
        }
 
        if (c->subtitle) {
-               subtitle.reset (new SubtitleDecoder (this, c->subtitle, log));
+               /* XXX: this time here should be the time of the first subtitle, not 0 */
+               subtitle.reset (new SubtitleDecoder (this, c->subtitle, log, ContentTime()));
        }
 
        _next_time.resize (_format_context->nb_streams);
@@ -128,7 +129,7 @@ FFmpegDecoder::flush ()
        if (video) {
                double const vfr = _ffmpeg_content->video_frame_rate().get();
                Frame const f = full_length.frames_round (vfr);
-               Frame v = video->position().frames_round (vfr);
+               Frame v = video->position().frames_round (vfr) + 1;
                while (v < f) {
                        video->emit (shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)), v);
                        ++v;
@@ -137,12 +138,18 @@ FFmpegDecoder::flush ()
 
        BOOST_FOREACH (shared_ptr<FFmpegAudioStream> i, _ffmpeg_content->ffmpeg_audio_streams ()) {
                ContentTime a = audio->stream_position(i);
-               while (a < full_length) {
-                       ContentTime to_do = min (full_length - a, ContentTime::from_seconds (0.1));
-                       shared_ptr<AudioBuffers> silence (new AudioBuffers (i->channels(), to_do.frames_ceil (i->frame_rate())));
-                       silence->make_silent ();
-                       audio->emit (i, silence, a);
-                       a += to_do;
+               /* Unfortunately if a is 0 that really means that we don't know the stream position since
+                  there has been no data on it since the last seek.  In this case we'll just do nothing
+                  here.  I'm not sure if that's the right idea.
+               */
+               if (a > ContentTime()) {
+                       while (a < full_length) {
+                               ContentTime to_do = min (full_length - a, ContentTime::from_seconds (0.1));
+                               shared_ptr<AudioBuffers> silence (new AudioBuffers (i->channels(), to_do.frames_ceil (i->frame_rate())));
+                               silence->make_silent ();
+                               audio->emit (i, silence, a);
+                               a += to_do;
+                       }
                }
        }
 
@@ -501,7 +508,8 @@ FFmpegDecoder::decode_video_packet ()
        }
 
        if (i == _filter_graphs.end ()) {
-               graph.reset (new VideoFilterGraph (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+               dcp::Fraction vfr (lrint(_ffmpeg_content->video_frame_rate().get() * 1000), 1000);
+               graph.reset (new VideoFilterGraph (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format, vfr));
                graph->setup (_ffmpeg_content->filters ());
                _filter_graphs.push_back (graph);
                LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format);
@@ -559,9 +567,11 @@ FFmpegDecoder::decode_subtitle_packet ()
        FFmpegSubtitlePeriod sub_period = subtitle_period (sub);
        ContentTime from;
        from = sub_period.from + _pts_offset;
-       _have_current_subtitle = true;
        if (sub_period.to) {
                _current_subtitle_to = *sub_period.to + _pts_offset;
+       } else {
+               _current_subtitle_to = optional<ContentTime>();
+               _have_current_subtitle = true;
        }
 
        for (unsigned int i = 0; i < sub.num_rects; ++i) {
@@ -582,16 +592,20 @@ FFmpegDecoder::decode_subtitle_packet ()
                }
        }
 
+       if (_current_subtitle_to) {
+               subtitle->emit_stop (*_current_subtitle_to);
+       }
+
        avsubtitle_free (&sub);
 }
 
 void
 FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime from)
 {
-       /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
-          G, third B, fourth A.
+       /* Note BGRA is expressed little-endian, so the first byte in the word is B, second
+          G, third R, fourth A.
        */
-       shared_ptr<Image> image (new Image (AV_PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
+       shared_ptr<Image> image (new Image (AV_PIX_FMT_BGRA, dcp::Size (rect->w, rect->h), true));
 
 #ifdef DCPOMATIC_HAVE_AVSUBTITLERECT_PICT
        /* Start of the first line in the subtitle */
@@ -635,8 +649,8 @@ FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime
                uint32_t* out_line_p = out_p;
                for (int x = 0; x < rect->w; ++x) {
                        RGBA const p = mapped_palette[*sub_line_p++];
-                       /* XXX: this seems to be wrong to me (isn't the output image RGBA?) but it looks right on screen */
-                       *out_line_p++ = (p.a << 24) | (p.r << 16) | (p.g << 8) | p.b;
+                       /* XXX: this seems to be wrong to me (isn't the output image BGRA?) but it looks right on screen */
+                       *out_line_p++ = (p.a << 24) | (p.b << 16) | (p.g << 8) | p.r;
                }
 #ifdef DCPOMATIC_HAVE_AVSUBTITLERECT_PICT
                sub_p += rect->pict.linesize[0];
@@ -665,16 +679,24 @@ FFmpegDecoder::decode_ass_subtitle (string ass, ContentTime from)
           produces a single format of Dialogue: lines...
        */
 
-       vector<string> bits;
-       split (bits, ass, is_any_of (","));
-       if (bits.size() < 10) {
+       int commas = 0;
+       string text;
+       for (size_t i = 0; i < ass.length(); ++i) {
+               if (commas < 9 && ass[i] == ',') {
+                       ++commas;
+               } else if (commas == 9) {
+                       text += ass[i];
+               }
+       }
+
+       if (text.empty ()) {
                return;
        }
 
        sub::RawSubtitle base;
        list<sub::RawSubtitle> raw = sub::SSAReader::parse_line (
                base,
-               bits[9],
+               text,
                _ffmpeg_content->video->size().width,
                _ffmpeg_content->video->size().height
                );