Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 4 Feb 2014 09:59:38 +0000 (09:59 +0000)
committerCarl Hetherington <cth@carlh.net>
Tue, 4 Feb 2014 09:59:38 +0000 (09:59 +0000)
96 files changed:
doc/design/resampling.tex
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_merger.h
src/lib/content.cc
src/lib/content.h
src/lib/content_factory.cc
src/lib/decoded.h [new file with mode: 0644]
src/lib/decoder.cc
src/lib/decoder.h
src/lib/encoder.cc
src/lib/exceptions.cc
src/lib/exceptions.h
src/lib/ffmpeg.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/film.cc
src/lib/film.h
src/lib/filter_graph.cc
src/lib/image.cc
src/lib/image_content.cc
src/lib/image_content.h
src/lib/image_decoder.cc
src/lib/image_decoder.h
src/lib/image_examiner.h
src/lib/job.cc
src/lib/player.cc
src/lib/player.h
src/lib/playlist.cc
src/lib/playlist.h
src/lib/render_subtitles.cc [new file with mode: 0644]
src/lib/render_subtitles.h [new file with mode: 0644]
src/lib/resampler.cc
src/lib/resampler.h
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/subrip.cc [new file with mode: 0644]
src/lib/subrip.h [new file with mode: 0644]
src/lib/subrip_content.cc [new file with mode: 0644]
src/lib/subrip_content.h [new file with mode: 0644]
src/lib/subrip_decoder.cc [new file with mode: 0644]
src/lib/subrip_decoder.h [new file with mode: 0644]
src/lib/subrip_subtitle.h [new file with mode: 0644]
src/lib/subtitle_decoder.cc
src/lib/subtitle_decoder.h
src/lib/transcode_job.cc
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/types.h
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_examiner.h
src/lib/wscript
src/tools/server_test.cc
src/wx/audio_plot.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/timecode.cc
src/wx/timecode.h
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timeline_dialog.cc
src/wx/timeline_dialog.h
src/wx/video_panel.cc
test/ffmpeg_audio_test.cc
test/ffmpeg_pts_offset.cc
test/ffmpeg_seek_test.cc [new file with mode: 0644]
test/frame_rate_test.cc
test/long_ffmpeg_seek_test.cc [new file with mode: 0644]
test/play_test.cc
test/repeat_frame_test.cc [new file with mode: 0644]
test/resampler_test.cc
test/seek_zero_test.cc [new file with mode: 0644]
test/skip_frame_test.cc [new file with mode: 0644]
test/subrip_test.cc [new file with mode: 0644]
test/test.cc
test/test.h
test/util_test.cc
test/wscript
wscript

index 44aeee9b1b4c8abcdea917a7aeeebe66620c7815..cf9cfb1edc220bcdfa6f242b4aeee94427c2a0d5 100644 (file)
@@ -1,4 +1,5 @@
 \documentclass{article}
+\usepackage{amsmath}
 \begin{document}
 
 Here is what resampling we need to do.  Content video is at $C_V$ fps, audio at $C_A$.  
@@ -18,6 +19,7 @@ $C_V$ is a DCI rate, $C_A$ is not.  e.g.\ if $C_V = 24$, $C_A = 44.1\times{}10^3
 \textbf{Resample $C_A$ to the DCI rate.}
 
 \section{Hard case 1}
+\label{sec:hard1}
 
 $C_V$ is not a DCI rate, $C_A$ is, e.g.\ if $C_V = 25$, $C_A =
 48\times{}10^3$.  We will run the video at a nearby DCI rate $F_V$,
@@ -31,5 +33,24 @@ resample audio to $25 * 48\times{}10^3 / 24 = 50\times{}10^3$.
 \medskip
 \textbf{Resample $C_A$ to $C_V C_A / F_V$}
 
+\section{Hard case 2}
+
+Neither $C_V$ nor $C_A$ is not a DCI rate, e.g.\ if $C_V = 25$, $C_A =
+44.1\times{}10^3$.  We will run the video at a nearby DCI rate $F_V$,
+meaning that it will run faster or slower than it should.  We first
+resample the audio to a DCI rate $F_A$, then perform as with
+Section~\ref{sec:hard1} above.
+
+\medskip
+\textbf{Resample $C_A$ to $C_V F_A / F_V$}
+
+
+\section{The general case}
+
+Given a DCP running at $F_V$ and $F_A$ and a piece of content at $C_V$
+and $C_A$, resample the audio to $R_A$ where
+\begin{align*}
+R_A &= \frac{C_V F_A}{F_V}
+\end{align*}
 
 \end{document}
index 8186f9de49b9d89baa81c04d7f454f876b8de963..872947b55d3d56cac4a957ca561bc5944a81d34c 100644 (file)
@@ -69,7 +69,7 @@ AnalyseAudioJob::run ()
        _analysis.reset (new AudioAnalysis (_film->audio_channels ()));
 
        _done = 0;
-       OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
+       AudioFrame const len = _film->time_to_audio_frames (_film->length ());
        while (!player->pass ()) {
                set_progress (double (_done) / len);
        }
@@ -81,7 +81,7 @@ AnalyseAudioJob::run ()
 }
 
 void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, DCPTime)
 {
        for (int i = 0; i < b->frames(); ++i) {
                for (int j = 0; j < b->channels(); ++j) {
index 3d4881983b5234572ce40a47455c5b849a620328..6ed236d85ca57854a7a4f4691f4082bf4c1c059e 100644 (file)
@@ -33,10 +33,10 @@ public:
        void run ();
 
 private:
-       void audio (boost::shared_ptr<const AudioBuffers>, Time);
+       void audio (boost::shared_ptr<const AudioBuffers>, DCPTime);
 
        boost::weak_ptr<AudioContent> _content;
-       OutputAudioFrame _done;
+       AudioFrame _done;
        int64_t _samples_per_point;
        std::vector<AudioPoint> _current;
 
index b4c4f34b6f064aaa71e822869728e266c488bdf0..3c0d13ba93c1b98b5bbd1760f33ecc16c38786db 100644 (file)
@@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
 int const AudioContentProperty::AUDIO_DELAY = 204;
 int const AudioContentProperty::AUDIO_MAPPING = 205;
 
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
        : Content (f, s)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
@@ -149,3 +149,27 @@ AudioContent::technical_summary () const
 {
        return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
 }
+
+/** Note: this is not particularly fast, as the FrameRateChange lookup
+ *  is not very intelligent.
+ *
+ *  @param t Some duration to convert.
+ *  @param at The time within the DCP to get the active frame rate change from; i.e. a point at which
+ *  the `controlling' video content is active.
+ */
+AudioFrame
+AudioContent::time_to_content_audio_frames (DCPTime t, DCPTime at) const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       /* Consider the case where we're running a 25fps video at 24fps (i.e. slow)
+          Our audio is at 44.1kHz.  We will resample it to 48000 * 25 / 24 and then
+          run it at 48kHz (i.e. slow, to match).
+
+          After 1 second, we'll have run the equivalent of 44.1kHz * 24 / 25 samples
+          in the source.
+       */
+       
+       return rint (t * content_audio_frame_rate() * film->active_frame_rate_change(at).speed_up / TIME_HZ);
+}
index ca4a1f2348fea78b788632a38a6da5a058c091f1..0b2ee2e461fe6b001d98509b6481efb61a214657 100644 (file)
@@ -43,7 +43,7 @@ class AudioContent : public virtual Content
 public:
        typedef int64_t Frame;
        
-       AudioContent (boost::shared_ptr<const Film>, Time);
+       AudioContent (boost::shared_ptr<const Film>, DCPTime);
        AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -52,7 +52,7 @@ public:
        std::string technical_summary () const;
 
        virtual int audio_channels () const = 0;
-       virtual AudioContent::Frame audio_length () const = 0;
+       virtual AudioFrame audio_length () const = 0;
        virtual int content_audio_frame_rate () const = 0;
        virtual int output_audio_frame_rate () const = 0;
        virtual AudioMapping audio_mapping () const = 0;
@@ -74,6 +74,8 @@ public:
                return _audio_delay;
        }
 
+       Frame time_to_content_audio_frames (DCPTime, DCPTime) const;
+       
 private:
        /** Gain to apply to audio in dB */
        float _audio_gain;
index c0ef02f65d5ab5518dcb7e53aa145b1ff3f9a598..8d3b0e1288ea5b53627a9cfb82619b0ccc125c7e 100644 (file)
@@ -22,6 +22,8 @@
 #include "exceptions.h"
 #include "log.h"
 #include "resampler.h"
+#include "util.h"
+#include "film.h"
 
 #include "i18n.h"
 
@@ -35,24 +37,53 @@ using boost::shared_ptr;
 AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
        : Decoder (film)
        , _audio_content (content)
-       , _audio_position (0)
 {
+       if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) {
+               _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ()));
+       }
+}
+
+/** Audio timestamping is made hard by many factors, but the final nail in the coffin is resampling.
+ *  We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ *  data out.  Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ *  The time is passed in here so that after a seek we can set up our _audio_position.  The
+ *  time is ignored once this has been done.
+ */
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+       if (_resampler) {
+               data = _resampler->run (data);
+       }
 
+       if (!_audio_position) {
+               shared_ptr<const Film> film = _film.lock ();
+               assert (film);
+               FrameRateChange frc = film->active_frame_rate_change (_audio_content->position ());
+               _audio_position = (double (time) / frc.speed_up) * film->audio_frame_rate() / TIME_HZ;
+       }
+
+       _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, _audio_position.get ())));
+       _audio_position = _audio_position.get() + data->frames ();
 }
 
 void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::flush ()
 {
-       Audio (data, frame);
-       _audio_position = frame + data->frames ();
+       if (!_resampler) {
+               return;
+       }
+
+       shared_ptr<const AudioBuffers> b = _resampler->flush ();
+       if (b) {
+               _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (b, _audio_position.get ())));
+               _audio_position = _audio_position.get() + b->frames ();
+       }
 }
 
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- *  The player needs to know that there is no audio otherwise it will keep trying to
- *  pass() the decoder to get it to emit audio.
- */
-bool
-AudioDecoder::has_audio () const
+void
+AudioDecoder::seek (ContentTime, bool)
 {
-       return _audio_content->audio_channels () > 0;
+       _audio_position.reset ();
 }
index ab6c4b8a931e12cfe892c8cb74d86ae7162d1a10..bb3aafccd6a8982824f890fa05907ea8def66b96 100644 (file)
 #include "decoder.h"
 #include "content.h"
 #include "audio_content.h"
+#include "decoded.h"
 
 class AudioBuffers;
+class Resampler;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
@@ -37,17 +39,21 @@ class AudioDecoder : public virtual Decoder
 {
 public:
        AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
+       
+       boost::shared_ptr<const AudioContent> audio_content () const {
+               return _audio_content;
+       }
 
-       bool has_audio () const;
-
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
-
+       void seek (ContentTime time, bool accurate);
+       
 protected:
 
-       void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+       void flush ();
+
        boost::shared_ptr<const AudioContent> _audio_content;
-       AudioContent::Frame _audio_position;
+       boost::shared_ptr<Resampler> _resampler;
+       boost::optional<AudioFrame> _audio_position;
 };
 
 #endif
index 226601e0ec61dffc92726bd8f98bbd976f21e9ad..f068b504e8dcbff0c0215a146de00550480bea97 100644 (file)
@@ -37,6 +37,8 @@ public:
        TimedAudioBuffers<T>
        pull (T time)
        {
+               assert (time >= _last_pull);
+               
                TimedAudioBuffers<T> out;
                
                F const to_return = _t_to_f (time - _last_pull);
@@ -97,9 +99,16 @@ public:
                if (_buffers->frames() == 0) {
                        return TimedAudioBuffers<T> ();
                }
-               
+
                return TimedAudioBuffers<T> (_buffers, _last_pull);
        }
+
+       void
+       clear (DCPTime t)
+       {
+               _last_pull = t;
+               _buffers.reset (new AudioBuffers (_buffers->channels(), 0));
+       }
        
 private:
        boost::shared_ptr<AudioBuffers> _buffers;
index ccca46bc02fbedd91e79b6665f6d7858736cb788..ea1c19acdc1cc620cffeee582e85a35bd4d10acc 100644 (file)
@@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f)
 
 }
 
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
        : _film (f)
        , _position (p)
        , _trim_start (0)
@@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
                _paths.push_back ((*i)->content ());
        }
        _digest = node->string_child ("Digest");
-       _position = node->number_child<Time> ("Position");
-       _trim_start = node->number_child<Time> ("TrimStart");
-       _trim_end = node->number_child<Time> ("TrimEnd");
+       _position = node->number_child<DCPTime> ("Position");
+       _trim_start = node->number_child<DCPTime> ("TrimStart");
+       _trim_end = node->number_child<DCPTime> ("TrimEnd");
 }
 
 Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -146,7 +146,7 @@ Content::signal_changed (int p)
 }
 
 void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -161,7 +161,7 @@ Content::set_position (Time p)
 }
 
 void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -172,7 +172,7 @@ Content::set_trim_start (Time t)
 }
 
 void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -204,21 +204,12 @@ Content::technical_summary () const
        return String::compose ("%1 %2 %3", path_summary(), digest(), position());
 }
 
-Time
+DCPTime
 Content::length_after_trim () const
 {
        return full_length() - trim_start() - trim_end();
 }
 
-/** @param t A time relative to the start of this content (not the position).
- *  @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
-       return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
 /** @return string which includes everything about how this content affects
  *  its playlist.
  */
index 4ee7c267f354c5cf65da2f1c948f3b8d93b79424..3172b9c8d1c77b3ad19a3d4b034b88f744181311 100644 (file)
@@ -49,7 +49,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no
 {
 public:
        Content (boost::shared_ptr<const Film>);
-       Content (boost::shared_ptr<const Film>, Time);
+       Content (boost::shared_ptr<const Film>, DCPTime);
        Content (boost::shared_ptr<const Film>, boost::filesystem::path);
        Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -60,7 +60,7 @@ public:
        virtual std::string technical_summary () const;
        virtual std::string information () const = 0;
        virtual void as_xml (xmlpp::Node *) const;
-       virtual Time full_length () const = 0;
+       virtual DCPTime full_length () const = 0;
        virtual std::string identifier () const;
 
        boost::shared_ptr<Content> clone () const;
@@ -92,42 +92,40 @@ public:
                return _digest;
        }
 
-       void set_position (Time);
+       void set_position (DCPTime);
 
-       /** Time that this content starts; i.e. the time that the first
+       /** DCPTime that this content starts; i.e. the time that the first
         *  bit of the content (trimmed or not) will happen.
         */
-       Time position () const {
+       DCPTime position () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _position;
        }
 
-       void set_trim_start (Time);
+       void set_trim_start (DCPTime);
 
-       Time trim_start () const {
+       DCPTime trim_start () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _trim_start;
        }
 
-       void set_trim_end (Time);
+       void set_trim_end (DCPTime);
        
-       Time trim_end () const {
+       DCPTime trim_end () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _trim_end;
        }
        
-       Time end () const {
+       DCPTime end () const {
                return position() + length_after_trim() - 1;
        }
 
-       Time length_after_trim () const;
+       DCPTime length_after_trim () const;
        
        void set_change_signals_frequent (bool f) {
                _change_signals_frequent = f;
        }
 
-       bool trimmed (Time) const;
-
        boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
 
 protected:
@@ -145,9 +143,9 @@ protected:
        
 private:
        std::string _digest;
-       Time _position;
-       Time _trim_start;
-       Time _trim_end;
+       DCPTime _position;
+       DCPTime _trim_start;
+       DCPTime _trim_end;
        bool _change_signals_frequent;
 };
 
index bab22b8eb19e14b6179fd27696710c6b630df70c..825c8049854ba3032519b7d27430560a18d9f8b8 100644 (file)
@@ -21,6 +21,7 @@
 #include "ffmpeg_content.h"
 #include "image_content.h"
 #include "sndfile_content.h"
+#include "subrip_content.h"
 #include "util.h"
 
 using std::string;
@@ -39,6 +40,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version)
                content.reset (new ImageContent (film, node, version));
        } else if (type == "Sndfile") {
                content.reset (new SndfileContent (film, node, version));
+       } else if (type == "SubRip") {
+               content.reset (new SubRipContent (film, node, version));
        }
 
        return content;
@@ -48,11 +51,16 @@ shared_ptr<Content>
 content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
 {
        shared_ptr<Content> content;
+
+       string ext = path.extension().string ();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
                
        if (valid_image_file (path)) {
                content.reset (new ImageContent (film, path));
        } else if (SndfileContent::valid_file (path)) {
                content.reset (new SndfileContent (film, path));
+       } else if (ext == ".srt") {
+               content.reset (new SubRipContent (film, path));
        } else {
                content.reset (new FFmpegContent (film, path));
        }
diff --git a/src/lib/decoded.h b/src/lib/decoded.h
new file mode 100644 (file)
index 0000000..f4ebe0d
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_LIB_DECODED_H
+#define DCPOMATIC_LIB_DECODED_H
+
+#include <libdcp/subtitle_asset.h>
+#include "types.h"
+#include "rect.h"
+#include "util.h"
+
+class Image;
+
+class Decoded
+{
+public:
+       Decoded ()
+               : dcp_time (0)
+       {}
+
+       virtual ~Decoded () {}
+
+       virtual void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange, DCPTime) = 0;
+
+       DCPTime dcp_time;
+};
+
+/** One frame of video from a VideoDecoder */
+class DecodedVideo : public Decoded
+{
+public:
+       DecodedVideo ()
+               : eyes (EYES_BOTH)
+               , same (false)
+               , frame (0)
+       {}
+
+       DecodedVideo (boost::shared_ptr<const Image> im, Eyes e, bool s, VideoFrame f)
+               : image (im)
+               , eyes (e)
+               , same (s)
+               , frame (f)
+       {}
+
+       void set_dcp_times (VideoFrame video_frame_rate, AudioFrame, FrameRateChange frc, DCPTime offset)
+       {
+               dcp_time = frame * TIME_HZ * frc.factor() / video_frame_rate + offset;
+       }
+       
+       boost::shared_ptr<const Image> image;
+       Eyes eyes;
+       bool same;
+       VideoFrame frame;
+};
+
+class DecodedAudio : public Decoded
+{
+public:
+       DecodedAudio (boost::shared_ptr<const AudioBuffers> d, AudioFrame f)
+               : data (d)
+               , frame (f)
+       {}
+
+       void set_dcp_times (VideoFrame, AudioFrame audio_frame_rate, FrameRateChange, DCPTime offset)
+       {
+               dcp_time = frame * TIME_HZ / audio_frame_rate + offset;
+       }
+       
+       boost::shared_ptr<const AudioBuffers> data;
+       AudioFrame frame;
+};
+
+class DecodedImageSubtitle : public Decoded
+{
+public:
+       DecodedImageSubtitle ()
+               : content_time (0)
+               , content_time_to (0)
+               , dcp_time_to (0)
+       {}
+
+       DecodedImageSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t)
+               : image (im)
+               , rect (r)
+               , content_time (f)
+               , content_time_to (t)
+               , dcp_time_to (0)
+       {}
+
+       void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+       {
+               dcp_time = rint (content_time / frc.speed_up) + offset;
+               dcp_time_to = rint (content_time_to / frc.speed_up) + offset;
+       }
+
+       boost::shared_ptr<Image> image;
+       dcpomatic::Rect<double> rect;
+       ContentTime content_time;
+       ContentTime content_time_to;
+       DCPTime dcp_time_to;
+};
+
+class DecodedTextSubtitle : public Decoded
+{
+public:
+       DecodedTextSubtitle ()
+               : dcp_time_to (0)
+       {}
+
+       DecodedTextSubtitle (std::list<libdcp::Subtitle> s)
+               : subs (s)
+       {}
+
+       void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+       {
+               if (subs.empty ()) {
+                       return;
+               }
+
+               /* Assuming that all subs are at the same time */
+               dcp_time = rint (subs.front().in().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+               dcp_time_to = rint (subs.front().out().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+       }
+
+       std::list<libdcp::Subtitle> subs;
+       DCPTime dcp_time_to;
+};
+
+#endif
index 3f4cda6eb5a4345595410fe475314f3d24881bd7..53a0c31e140d6ceab0a3d2f222fe419bda88237b 100644 (file)
 
 #include "film.h"
 #include "decoder.h"
+#include "decoded.h"
 
 #include "i18n.h"
 
+using std::cout;
 using boost::shared_ptr;
 
 /** @param f Film.
@@ -33,6 +35,45 @@ using boost::shared_ptr;
  */
 Decoder::Decoder (shared_ptr<const Film> f)
        : _film (f)
+       , _done (false)
 {
 
 }
+
+struct DecodedSorter
+{
+       bool operator() (shared_ptr<Decoded> a, shared_ptr<Decoded> b)
+       {
+               return a->dcp_time < b->dcp_time;
+       }
+};
+
+shared_ptr<Decoded>
+Decoder::peek ()
+{
+       while (!_done && _pending.empty ()) {
+               _done = pass ();
+       }
+
+       if (_done && _pending.empty ()) {
+               return shared_ptr<Decoded> ();
+       }
+
+       _pending.sort (DecodedSorter ());
+       return _pending.front ();
+}
+
+void
+Decoder::consume ()
+{
+       if (!_pending.empty ()) {
+               _pending.pop_front ();
+       }
+}
+
+void
+Decoder::seek (ContentTime, bool)
+{
+       _pending.clear ();
+       _done = false;
+}
index d67592ed812544c644b8766bcb1b1be1c03e84de..6646b0e76acc66b14edb78d698bd9c48412e07c4 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
 #include <boost/utility.hpp>
+#include "types.h"
 
 class Film;
+class Decoded;
 
 /** @class Decoder.
  *  @brief Parent class for decoders of content.
@@ -39,18 +41,32 @@ public:
        Decoder (boost::shared_ptr<const Film>);
        virtual ~Decoder () {}
 
-       /** Perform one decode pass of the content, which may or may not
-        *  cause the object to emit some data.
+       /** Seek so that the next get_*() will yield the next thing
+        *  (video/sound frame, subtitle etc.) at or after the requested
+        *  time.  Pass accurate = true to try harder to get close to
+        *  the request.
         */
-       virtual void pass () = 0;
-       virtual bool done () const = 0;
+       virtual void seek (ContentTime time, bool accurate);
+       
+       boost::shared_ptr<Decoded> peek ();
+       void consume ();
 
 protected:
 
+       /** Perform one decode pass of the content, which may or may not
+        *  result in a complete quantum (Decoded object) of decoded stuff
+        *  being made ready.
+        *  @return true if the decoder is done (i.e. no more data will be
+        *  produced by any future calls to pass() without a seek() first).
+        */
+       virtual bool pass () = 0;
        virtual void flush () {};
        
        /** The Film that we are decoding in */
        boost::weak_ptr<const Film> _film;
+
+       std::list<boost::shared_ptr<Decoded> > _pending;
+       bool _done;
 };
 
 #endif
index 92b4763be5768f5ca04c5f40621191d6ef52cb4b..f1c3e7e6182e2d4dfb9b7145388680738bed1d43 100644 (file)
@@ -217,7 +217,7 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
                TIMING ("adding to queue of %1", _queue.size ());
                _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image->image(), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
+                                                 image->image(PIX_FMT_RGB24, false), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
                                                  _film->j2k_bandwidth(), _film->resolution(), _film->log()
                                                  )
                                          ));
index 8144f41b999690f526db309a4102547f4e69a05c..e05ac4ff04b3fdebb5617129f5c9a0eb6c9dc10e 100644 (file)
@@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s)
 
 }
 
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
        : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
 {
 
 }
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+       : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f)
+{
+
+}
index 3423a5754e340e3909b6b59ef617b5785d1a2809..213be6186d523a713a0a3f167f77127e0047164e 100644 (file)
@@ -230,6 +230,13 @@ public:
        PixelFormatError (std::string o, AVPixelFormat f);
 };
 
+/** An error that occurs while parsing a SubRip file */
+class SubRipError : public FileError
+{
+public:
+       SubRipError (std::string, std::string, boost::filesystem::path);
+};
+
 /** A parent class for classes which have a need to catch and
  *  re-throw exceptions.  This is intended for classes
  *  which run their own thread; they should do something like
index d3653e311fdfadeabe97d9a3898d5fe0c835fb36..4bf9415234e3d54b844e8881e56a75dd30558492 100644 (file)
@@ -191,6 +191,10 @@ FFmpeg::video_codec_context () const
 AVCodecContext *
 FFmpeg::audio_codec_context () const
 {
+       if (!_ffmpeg_content->audio_stream ()) {
+               return 0;
+       }
+       
        return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
 }
 
index 2c5fcf70e1e09915245711103a1f76081a395680..3bee49146a5304024d083c6138b00e8332012390 100644 (file)
@@ -163,7 +163,7 @@ FFmpegContent::examine (shared_ptr<Job> job)
 
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
 
-       VideoContent::Frame video_length = 0;
+       VideoFrame video_length = 0;
        video_length = examiner->video_length ();
        film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
 
@@ -262,12 +262,12 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
 }
 
-AudioContent::Frame
+AudioFrame
 FFmpegContent::audio_length () const
 {
        int const cafr = content_audio_frame_rate ();
        int const vfr  = video_frame_rate ();
-       VideoContent::Frame const vl = video_length ();
+       VideoFrame const vl = video_length ();
 
        boost::mutex::scoped_lock lm (_mutex);
        if (!_audio_stream) {
@@ -310,16 +310,15 @@ FFmpegContent::output_audio_frame_rate () const
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_frame_rate (content_audio_frame_rate ());
 
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
 
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
           look different in the DCP compared to the source (slower or faster).
-          skip/repeat doesn't come into effect here.
        */
 
        if (frc.change_speed) {
-               t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+               t /= frc.speed_up;
        }
 
        return rint (t);
@@ -446,13 +445,13 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
        FFmpegStream::as_xml (root);
 }
 
-Time
+DCPTime
 FFmpegContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
-       FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+       FrameRateChange frc (video_frame_rate (), film->video_frame_rate ());
        return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
 }
 
index 7ff159b852b4cb69df4004b1f6a813ad2131e182..d9037b0d8ca2f70ee38ee3edc56e9c703c4dd874 100644 (file)
@@ -134,13 +134,13 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        std::string identifier () const;
        
        /* AudioContent */
        int audio_channels () const;
-       AudioContent::Frame audio_length () const;
+       AudioFrame audio_length () const;
        int content_audio_frame_rate () const;
        int output_audio_frame_rate () const;
        AudioMapping audio_mapping () const;
index c3709166e6c8c1bb7c33d4a2c902ff436db0c15f..55f4eb5c69b63cc97c0c5ce6ba1bb44db619b0ff 100644 (file)
@@ -69,7 +69,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
        , _decode_video (video)
        , _decode_audio (audio)
        , _pts_offset (0)
-       , _just_sought (false)
 {
        setup_subtitle ();
 
@@ -82,13 +81,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
 
-          We will do:
-            audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
-            video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+          We will do pts_to_use = pts_from_ffmpeg + pts_offset;
        */
 
        bool const have_video = video && c->first_video();
-       bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+       bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio;
 
        /* First, make one of them start at 0 */
 
@@ -141,12 +138,10 @@ FFmpegDecoder::flush ()
                decode_audio_packet ();
        }
 
-       /* Stop us being asked for any more data */
-       _video_position = _ffmpeg_content->video_length ();
-       _audio_position = _ffmpeg_content->audio_length ();
+       AudioDecoder::flush ();
 }
 
-void
+bool
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
@@ -162,7 +157,7 @@ FFmpegDecoder::pass ()
                }
 
                flush ();
-               return;
+               return true;
        }
 
        shared_ptr<const Film> film = _film.lock ();
@@ -179,6 +174,7 @@ FFmpegDecoder::pass ()
        }
 
        av_free_packet (&_packet);
+       return false;
 }
 
 /** @param data pointer to array of pointers to buffers.
@@ -293,77 +289,135 @@ FFmpegDecoder::bytes_per_audio_sample () const
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
 {
-       double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+       int frames_read = 0;
+       optional<ContentTime> last_video;
+       optional<ContentTime> last_audio;
 
-       /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
-          a number plucked from the air) earlier than we want to end up.  The loop below
-          will hopefully then step through to where we want to be.
-       */
-       int initial = frame;
+       while (!finished (last_video, last_audio, frames_read)) {
+               int r = av_read_frame (_format_context, &_packet);
+               if (r < 0) {
+                       /* We should flush our decoders here, possibly yielding a few more frames,
+                          but the consequence of having to do that is too hideous to contemplate.
+                          Instead we give up and say that you can't seek too close to the end
+                          of a file.
+                       */
+                       return frames_read;
+               }
 
-       if (accurate) {
-               initial -= 5;
+               ++frames_read;
+
+               double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+               if (_packet.stream_index == _video_stream) {
+
+                       avcodec_get_frame_defaults (_frame);
+                       
+                       int finished = 0;
+                       r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+                       if (r >= 0 && finished) {
+                               last_video = rint (
+                                       (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * TIME_HZ
+                                       );
+                       }
+
+               } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
+                       AVPacket copy_packet = _packet;
+                       while (copy_packet.size > 0) {
+
+                               int finished;
+                               r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
+                               if (r >= 0 && finished) {
+                                       last_audio = rint (
+                                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * TIME_HZ
+                                               );
+                               }
+                                       
+                               copy_packet.data += r;
+                               copy_packet.size -= r;
+                       }
+               }
+               
+               av_free_packet (&_packet);
        }
 
-       if (initial < 0) {
-               initial = 0;
+       return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+       return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+       return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+       int64_t s = ((double (t) / TIME_HZ) - _pts_offset) /
+               av_q2d (_format_context->streams[_video_stream]->time_base);
+
+       if (_ffmpeg_content->audio_stream ()) {
+               s = min (
+                       s, int64_t (
+                               ((double (t) / TIME_HZ) - _pts_offset) /
+                               av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)
+                               )
+                       );
        }
 
-       /* Initial seek time in the stream's timebase */
-       int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+       /* Ridiculous empirical hack */
+       s--;
 
-       av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+       av_seek_frame (_format_context, _video_stream, s, AVSEEK_FLAG_BACKWARD);
 
        avcodec_flush_buffers (video_codec_context());
+       if (audio_codec_context ()) {
+               avcodec_flush_buffers (audio_codec_context ());
+       }
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
+}
 
-       /* This !accurate is piling hack upon hack; setting _just_sought to true
-          even with accurate == true defeats our attempt to align the start
-          of the video and audio.  Here we disable that defeat when accurate == true
-          i.e. when we are making a DCP rather than just previewing one.
-          Ewww.  This should be gone in 2.0.
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+       Decoder::seek (time, accurate);
+       AudioDecoder::seek (time, accurate);
+       
+       /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+          a number plucked from the air) earlier than we want to end up.  The loop below
+          will hopefully then step through to where we want to be.
        */
-       if (!accurate) {
-               _just_sought = true;
+
+       ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0;
+       ContentTime initial_seek = time - pre_roll;
+       if (initial_seek < 0) {
+               initial_seek = 0;
        }
-       
-       _video_position = frame;
-       
-       if (frame == 0 || !accurate) {
-               /* We're already there, or we're as close as we need to be */
+
+       /* Initial seek time in the video stream's timebase */
+
+       seek_and_flush (initial_seek);
+
+       if (!accurate) {
+               /* That'll do */
                return;
        }
 
-       while (1) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       return;
-               }
-
-               if (_packet.stream_index != _video_stream) {
-                       av_free_packet (&_packet);
-                       continue;
-               }
-               
-               int finished = 0;
-               r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
-               if (r >= 0 && finished) {
-                       _video_position = rint (
-                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate()
-                               );
+       int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
 
-                       if (_video_position >= (frame - 1)) {
-                               av_free_packet (&_packet);
-                               break;
-                       }
-               }
-               
-               av_free_packet (&_packet);
+       seek_and_flush (initial_seek);
+       if (N > 0) {
+               minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
        }
 }
 
@@ -380,6 +434,7 @@ FFmpegDecoder::decode_audio_packet ()
 
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
                if (decode_result < 0) {
                        shared_ptr<const Film> film = _film.lock ();
                        assert (film);
@@ -388,31 +443,17 @@ FFmpegDecoder::decode_audio_packet ()
                }
 
                if (frame_finished) {
-                       
-                       if (_audio_position == 0) {
-                               /* Where we are in the source, in seconds */
-                               double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                                       * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
-                               if (pts > 0) {
-                                       /* Emit some silence */
-                                       shared_ptr<AudioBuffers> silence (
-                                               new AudioBuffers (
-                                                       _ffmpeg_content->audio_channels(),
-                                                       pts * _ffmpeg_content->content_audio_frame_rate()
-                                                       )
-                                               );
-                                       
-                                       silence->make_silent ();
-                                       audio (silence, _audio_position);
-                               }
-                       }
+                       ContentTime const ct = (
+                               av_frame_get_best_effort_timestamp (_frame) *
+                               av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base)
+                               + _pts_offset
+                               ) * TIME_HZ;
                        
                        int const data_size = av_samples_get_buffer_size (
                                0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                );
-                       
-                       audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+
+                       audio (deinterleave_audio (_frame->data, data_size), ct);
                }
                        
                copy_packet.data += decode_result;
@@ -461,45 +502,9 @@ FFmpegDecoder::decode_video_packet ()
                }
                
                if (i->second != AV_NOPTS_VALUE) {
-
                        double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
-                       if (_just_sought) {
-                               /* We just did a seek, so disable any attempts to correct for where we
-                                  are / should be.
-                               */
-                               _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
-                               _just_sought = false;
-                       }
-
-                       double const next = _video_position / _ffmpeg_content->video_frame_rate();
-                       double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
-                       double delta = pts - next;
-
-                       while (delta > one_frame) {
-                               /* This PTS is more than one frame forward in time of where we think we should be; emit
-                                  a black frame.
-                               */
-
-                               /* XXX: I think this should be a copy of the last frame... */
-                               boost::shared_ptr<Image> black (
-                                       new Image (
-                                               static_cast<AVPixelFormat> (_frame->format),
-                                               libdcp::Size (video_codec_context()->width, video_codec_context()->height),
-                                               true
-                                               )
-                                       );
-                               
-                               black->make_black ();
-                               video (image, false, _video_position);
-                               delta -= one_frame;
-                       }
-
-                       if (delta > -one_frame) {
-                               /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
-                               video (image, false, _video_position);
-                       }
-                               
+                       VideoFrame const f = rint (pts * _ffmpeg_content->video_frame_rate ());
+                       video (image, false, f);
                } else {
                        shared_ptr<const Film> film = _film.lock ();
                        assert (film);
@@ -532,14 +537,6 @@ FFmpegDecoder::setup_subtitle ()
        }
 }
 
-bool
-FFmpegDecoder::done () const
-{
-       bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
-       bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
-       return vd && ad;
-}
-       
 void
 FFmpegDecoder::decode_subtitle_packet ()
 {
@@ -553,7 +550,7 @@ FFmpegDecoder::decode_subtitle_packet ()
           indicate that the previous subtitle should stop.
        */
        if (sub.num_rects <= 0) {
-               subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+               image_subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
@@ -562,11 +559,11 @@ FFmpegDecoder::decode_subtitle_packet ()
        /* Subtitle PTS in seconds (within the source, not taking into account any of the
           source that we may have chopped off for the DCP)
        */
-       double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
-
+       double const packet_time = (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
+       
        /* hence start time for this sub */
-       Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
-       Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+       ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+       ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
 
        AVSubtitleRect const * rect = sub.rects[0];
 
@@ -601,7 +598,7 @@ FFmpegDecoder::decode_subtitle_packet ()
 
        libdcp::Size const vs = _ffmpeg_content->video_size ();
 
-       subtitle (
+       image_subtitle (
                image,
                dcpomatic::Rect<double> (
                        static_cast<double> (rect->x) / vs.width,
index 63a8f0c717be592a59930501a14cc4c67c54728d..ee725b20c9c46cb066d9e98bbfd6d7331bee45c9 100644 (file)
@@ -51,15 +51,12 @@ public:
        FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
        ~FFmpegDecoder ();
 
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
+       void seek (ContentTime time, bool);
 
 private:
        friend class ::ffmpeg_pts_offset_test;
 
-       static double compute_pts_offset (double, double, float);
-
+       bool pass ();
        void flush ();
 
        void setup_subtitle ();
@@ -74,6 +71,11 @@ private:
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
 
+       bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+       bool seek_final_finished (int, int) const;
+       int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
+       void seek_and_flush (int64_t);
+
        AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
        AVCodec* _subtitle_codec;                ///< may be 0 if there is no subtitle
        
@@ -84,5 +86,4 @@ private:
        bool _decode_audio;
 
        double _pts_offset;
-       bool _just_sought;
 };
index a63090d12e72a5bbfc90185a7df2b758012af839..38dd678bbfa91da1f80562f3dac0d7fcf78c0829 100644 (file)
@@ -134,10 +134,10 @@ FFmpegExaminer::video_size () const
 }
 
 /** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+VideoFrame
 FFmpegExaminer::video_length () const
 {
-       VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
+       VideoFrame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
        return max (1, length);
 }
 
index 4de475d2a3f78d753985f6b165e56bf3b74c7290..2dd8b2e34baabc95594f4b21c79b92fae7eb679f 100644 (file)
@@ -31,7 +31,7 @@ public:
        
        float video_frame_rate () const;
        libdcp::Size video_size () const;
-       VideoContent::Frame video_length () const;
+       VideoFrame video_length () const;
 
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
                return _subtitle_streams;
index 8690d3ee0b97eb22e1080337ff4d7e1b27c88746..67f795a6c739af5e60b8947bfde2e4657ba9f275 100644 (file)
@@ -855,7 +855,7 @@ Film::move_content_later (shared_ptr<Content> c)
        _playlist->move_later (c);
 }
 
-Time
+DCPTime
 Film::length () const
 {
        return _playlist->length ();
@@ -867,12 +867,18 @@ Film::has_subtitles () const
        return _playlist->has_subtitles ();
 }
 
-OutputVideoFrame
+VideoFrame
 Film::best_video_frame_rate () const
 {
        return _playlist->best_dcp_frame_rate ();
 }
 
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+       return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
 void
 Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
@@ -891,31 +897,31 @@ Film::playlist_changed ()
        signal_changed (CONTENT);
 }      
 
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
+AudioFrame
+Film::time_to_audio_frames (DCPTime t) const
 {
        return t * audio_frame_rate () / TIME_HZ;
 }
 
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
+VideoFrame
+Film::time_to_video_frames (DCPTime t) const
 {
        return t * video_frame_rate () / TIME_HZ;
 }
 
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
+DCPTime
+Film::audio_frames_to_time (AudioFrame f) const
 {
        return f * TIME_HZ / audio_frame_rate ();
 }
 
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
+DCPTime
+Film::video_frames_to_time (VideoFrame f) const
 {
        return f * TIME_HZ / video_frame_rate ();
 }
 
-OutputAudioFrame
+AudioFrame
 Film::audio_frame_rate () const
 {
        /* XXX */
index 0a747193eae4debd6fdab184328fbe2156ddcda5..5d83ec6edaa484c19192f1874448810711588741 100644 (file)
@@ -101,12 +101,12 @@ public:
        boost::shared_ptr<Player> make_player () const;
        boost::shared_ptr<Playlist> playlist () const;
 
-       OutputAudioFrame audio_frame_rate () const;
+       AudioFrame audio_frame_rate () const;
 
-       OutputAudioFrame time_to_audio_frames (Time) const;
-       OutputVideoFrame time_to_video_frames (Time) const;
-       Time video_frames_to_time (OutputVideoFrame) const;
-       Time audio_frames_to_time (OutputAudioFrame) const;
+       AudioFrame time_to_audio_frames (DCPTime) const;
+       VideoFrame time_to_video_frames (DCPTime) const;
+       DCPTime video_frames_to_time (VideoFrame) const;
+       DCPTime audio_frames_to_time (AudioFrame) const;
 
        uint64_t required_disk_space () const;
        bool should_be_enough_disk_space (double &, double &) const;
@@ -114,9 +114,10 @@ public:
        /* Proxies for some Playlist methods */
 
        ContentList content () const;
-       Time length () const;
+       DCPTime length () const;
        bool has_subtitles () const;
-       OutputVideoFrame best_video_frame_rate () const;
+       VideoFrame best_video_frame_rate () const;
+       FrameRateChange active_frame_rate_change (DCPTime) const;
 
        libdcp::KDM
        make_kdm (
index cd5d198079683f588c2a12c68a1648e4a5136e29..7c006aa58834eaf5eb22f1eb80c61d52b37ea5dd 100644 (file)
@@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size
                throw DecodeError (N_("could not configure filter graph."));
        }
 
-       /* XXX: leaking `inputs' / `outputs' ? */
+       avfilter_inout_free (&inputs);
+       avfilter_inout_free (&outputs);
 }
 
 FilterGraph::~FilterGraph ()
index 95bf2b04d171085615e786418600d23d858f2909..e5f626c24762bbb2e57be8e1bd06a1cada3b9efc 100644 (file)
@@ -31,6 +31,7 @@ extern "C" {
 #include "image.h"
 #include "exceptions.h"
 #include "scaler.h"
+#include "timer.h"
 
 using std::string;
 using std::min;
@@ -94,9 +95,9 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
        libdcp::Size cropped_size = crop.apply (size ());
 
        struct SwsContext* scale_context = sws_getContext (
-               cropped_size.width, cropped_size.height, pixel_format(),
-               inter_size.width, inter_size.height, out_format,
-               scaler->ffmpeg_id (), 0, 0, 0
+                       cropped_size.width, cropped_size.height, pixel_format(),
+                       inter_size.width, inter_size.height, out_format,
+                       scaler->ffmpeg_id (), 0, 0, 0
                );
 
        uint8_t* scale_in_data[components()];
@@ -375,8 +376,18 @@ Image::make_black ()
 void
 Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
 {
-       /* Only implemented for RGBA onto RGB24 so far */
-       assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+       int this_bpp = 0;
+       int other_bpp = 0;
+
+       if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+               this_bpp = 4;
+               other_bpp = 4;
+       } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+               this_bpp = 3;
+               other_bpp = 4;
+       } else {
+               assert (false);
+       }
 
        int start_tx = position.x;
        int start_ox = 0;
@@ -395,15 +406,15 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
 
        for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
-               uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+               uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
                uint8_t* op = other->data()[0] + oy * other->stride()[0];
                for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
                        float const alpha = float (op[3]) / 255;
                        tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
                        tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
                        tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
-                       tp += 3;
-                       op += 4;
+                       tp += this_bpp;
+                       op += other_bpp;
                }
        }
 }
index 2d29df0c4bf2fa5a25b32e7e3aa7d200c6c3ca4c..a7f951beaca4e98f5213d397408c19f9a79e24ec 100644 (file)
@@ -110,7 +110,7 @@ ImageContent::examine (shared_ptr<Job> job)
 }
 
 void
-ImageContent::set_video_length (VideoContent::Frame len)
+ImageContent::set_video_length (VideoFrame len)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -120,13 +120,13 @@ ImageContent::set_video_length (VideoContent::Frame len)
        signal_changed (ContentProperty::LENGTH);
 }
 
-Time
+DCPTime
 ImageContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate ());
        return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
 }
 
index e5a0311d97947f816f62dbfc5aefd67a8a9ef282..f929e2b6f15b72105a6a97880e6bef647eb98175 100644 (file)
@@ -41,11 +41,11 @@ public:
        std::string summary () const;
        std::string technical_summary () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        std::string identifier () const;
        
-       void set_video_length (VideoContent::Frame);
+       void set_video_length (VideoFrame);
        bool still () const;
        void set_video_frame_rate (float);
 };
index a7999c02a86b5270b09dd50fd118c00280ef5761..204849ecf8bd1ddc3ca415d5152eb4085863816e 100644 (file)
@@ -36,20 +36,22 @@ ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageCont
        : Decoder (f)
        , VideoDecoder (f, c)
        , _image_content (c)
+       , _video_position (0)
 {
 
 }
 
-void
+bool
 ImageDecoder::pass ()
 {
        if (_video_position >= _image_content->video_length ()) {
-               return;
+               return true;
        }
 
        if (_image && _image_content->still ()) {
                video (_image, true, _video_position);
-               return;
+               ++_video_position;
+               return false;
        }
 
        Magick::Image* magick_image = 0;
@@ -81,16 +83,15 @@ ImageDecoder::pass ()
        delete magick_image;
 
        video (_image, false, _video_position);
-}
+       ++_video_position;
 
-void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
-       _video_position = frame;
+       return false;
 }
 
-bool
-ImageDecoder::done () const
+void
+ImageDecoder::seek (ContentTime time, bool accurate)
 {
-       return _video_position >= _image_content->video_length ();
+       Decoder::seek (time, accurate);
+       
+       _video_position = rint (time * _video_content->video_frame_rate() / TIME_HZ);
 }
index c7500243e08091ec3d6669f3c14273dfb93454e4..63b4c58e344f42ece958d7a58e75441d00da216c 100644 (file)
@@ -34,14 +34,13 @@ public:
                return _image_content;
        }
 
-       /* Decoder */
-
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
+       void seek (ContentTime, bool);
 
 private:
+       bool pass ();
+       
        boost::shared_ptr<const ImageContent> _image_content;
        boost::shared_ptr<Image> _image;
+       VideoFrame _video_position;
 };
 
index 8887f0d3d15a2587f56faff713a62835b3af8a7b..f5d5150749971c6f874880802e1da1ff09b96555 100644 (file)
@@ -32,7 +32,7 @@ public:
 
        float video_frame_rate () const;
        libdcp::Size video_size () const;
-       VideoContent::Frame video_length () const {
+       VideoFrame video_length () const {
                return _video_length;
        }
 
@@ -40,5 +40,5 @@ private:
        boost::weak_ptr<const Film> _film;
        boost::shared_ptr<const ImageContent> _image_content;
        boost::optional<libdcp::Size> _video_size;
-       VideoContent::Frame _video_length;
+       VideoFrame _video_length;
 };
index 9981934ec93767d1286ff459acbe3845de9af821..05a90524c9818615112b77464da5a56bd154ec69 100644 (file)
@@ -198,7 +198,7 @@ Job::set_state (State s)
        }
 }
 
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
 int
 Job::elapsed_time () const
 {
index e661a7b3693930eac8b0348fd250de185a098706..3e6a1598d17e76559c64b7ca0cde2837ec905b64 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include <stdint.h>
+#include <algorithm>
 #include "player.h"
 #include "film.h"
 #include "ffmpeg_decoder.h"
 #include "sndfile_decoder.h"
 #include "sndfile_content.h"
 #include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
 #include "playlist.h"
 #include "job.h"
 #include "image.h"
 #include "ratio.h"
-#include "resampler.h"
 #include "log.h"
 #include "scaler.h"
+#include "render_subtitles.h"
 
 using std::list;
 using std::cout;
@@ -45,69 +48,20 @@ using std::map;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
+using boost::optional;
 
 class Piece
 {
 public:
-       Piece (shared_ptr<Content> c)
-               : content (c)
-               , video_position (c->position ())
-               , audio_position (c->position ())
-               , repeat_to_do (0)
-               , repeat_done (0)
-       {}
-       
-       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
                : content (c)
                , decoder (d)
-               , video_position (c->position ())
-               , audio_position (c->position ())
+               , frc (f)
        {}
 
-       /** Set this piece to repeat a video frame a given number of times */
-       void set_repeat (IncomingVideo video, int num)
-       {
-               repeat_video = video;
-               repeat_to_do = num;
-               repeat_done = 0;
-       }
-
-       void reset_repeat ()
-       {
-               repeat_video.image.reset ();
-               repeat_to_do = 0;
-               repeat_done = 0;
-       }
-
-       bool repeating () const
-       {
-               return repeat_done != repeat_to_do;
-       }
-
-       void repeat (Player* player)
-       {
-               player->process_video (
-                       repeat_video.weak_piece,
-                       repeat_video.image,
-                       repeat_video.eyes,
-                       repeat_done > 0,
-                       repeat_video.frame,
-                       (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
-                       );
-
-               ++repeat_done;
-       }
-       
        shared_ptr<Content> content;
        shared_ptr<Decoder> decoder;
-       /** Time of the last video we emitted relative to the start of the DCP */
-       Time video_position;
-       /** Time of the last audio we emitted relative to the start of the DCP */
-       Time audio_position;
-
-       IncomingVideo repeat_video;
-       int repeat_to_do;
-       int repeat_done;
+       FrameRateChange frc;
 };
 
 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
@@ -120,6 +74,8 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        , _audio_position (0)
        , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
        , _last_emit_was_black (false)
+       , _just_did_inaccurate_seek (false)
+       , _approximate_size (false)
 {
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
@@ -146,111 +102,162 @@ Player::pass ()
                setup_pieces ();
        }
 
-       Time earliest_t = TIME_MAX;
-       shared_ptr<Piece> earliest;
-       enum {
-               VIDEO,
-               AUDIO
-       } type = VIDEO;
+       /* Interrogate all our pieces to find the one with the earliest decoded data */
+
+       shared_ptr<Piece> earliest_piece;
+       shared_ptr<Decoded> earliest_decoded;
+       DCPTime earliest_time = TIME_MAX;
+       DCPTime earliest_audio = TIME_MAX;
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               if ((*i)->decoder->done ()) {
-                       continue;
-               }
 
-               shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
-               shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+               DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
+               
+               bool done = false;
+               shared_ptr<Decoded> dec;
+               while (!done) {
+                       dec = (*i)->decoder->peek ();
+                       if (!dec) {
+                               /* Decoder has nothing else to give us */
+                               break;
+                       }
 
-               if (_video && vd) {
-                       if ((*i)->video_position < earliest_t) {
-                               earliest_t = (*i)->video_position;
-                               earliest = *i;
-                               type = VIDEO;
+                       dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
+                       DCPTime const t = dec->dcp_time - offset;
+                       if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
+                               /* In the end-trimmed part; decoder has nothing else to give us */
+                               dec.reset ();
+                               done = true;
+                       } else if (t >= (*i)->content->trim_start ()) {
+                               /* Within the un-trimmed part; everything's ok */
+                               done = true;
+                       } else {
+                               /* Within the start-trimmed part; get something else */
+                               (*i)->decoder->consume ();
                        }
                }
 
-               if (_audio && ad && ad->has_audio ()) {
-                       if ((*i)->audio_position < earliest_t) {
-                               earliest_t = (*i)->audio_position;
-                               earliest = *i;
-                               type = AUDIO;
-                       }
+               if (!dec) {
+                       continue;
                }
-       }
 
-       if (!earliest) {
+               if (dec->dcp_time < earliest_time) {
+                       earliest_piece = *i;
+                       earliest_decoded = dec;
+                       earliest_time = dec->dcp_time;
+               }
+
+               if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
+                       earliest_audio = dec->dcp_time;
+               }
+       }
+               
+       if (!earliest_piece) {
                flush ();
                return true;
        }
 
-       switch (type) {
-       case VIDEO:
-               if (earliest_t > _video_position) {
-                       emit_black ();
-               } else {
-                       if (earliest->repeating ()) {
-                               earliest->repeat (this);
+       if (earliest_audio != TIME_MAX) {
+               TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
+               Audio (tb.audio, tb.time);
+               /* This assumes that the audio_frames_to_time conversion is exact
+                  so that there are no accumulated errors caused by rounding.
+               */
+               _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+       }
+
+       /* Emit the earliest thing */
+
+       shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+       shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+       shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
+       shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
+
+       /* Will be set to false if we shouldn't consume the peeked DecodedThing */
+       bool consume = true;
+
+       if (dv && _video) {
+
+               if (_just_did_inaccurate_seek) {
+
+                       /* Just emit; no subtlety */
+                       emit_video (earliest_piece, dv);
+                       step_video_position (dv);
+                       
+               } else if (dv->dcp_time > _video_position) {
+
+                       /* Too far ahead */
+
+                       list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+                       while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+                               ++i;
+                       }
+
+                       if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+                               /* We're outside all video content */
+                               emit_black ();
+                               _statistics.video.black++;
                        } else {
-                               earliest->decoder->pass ();
+                               /* We're inside some video; repeat the frame */
+                               _last_incoming_video.video->dcp_time = _video_position;
+                               emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
+                               step_video_position (_last_incoming_video.video);
+                               _statistics.video.repeat++;
                        }
-               }
-               break;
 
-       case AUDIO:
-               if (earliest_t > _audio_position) {
-                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+                       consume = false;
+
+               } else if (dv->dcp_time == _video_position) {
+                       /* We're ok */
+                       emit_video (earliest_piece, dv);
+                       step_video_position (dv);
+                       _statistics.video.good++;
                } else {
-                       earliest->decoder->pass ();
-
-                       if (earliest->decoder->done()) {
-                               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
-                               assert (ac);
-                               shared_ptr<Resampler> re = resampler (ac, false);
-                               if (re) {
-                                       shared_ptr<const AudioBuffers> b = re->flush ();
-                                       if (b->frames ()) {
-                                               process_audio (earliest, b, ac->audio_length ());
-                                       }
-                               }
-                       }
+                       /* Too far behind: skip */
+                       _statistics.video.skip++;
                }
-               break;
-       }
 
-       if (_audio) {
-               boost::optional<Time> audio_done_up_to;
-               for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-                       if ((*i)->decoder->done ()) {
-                               continue;
-                       }
+               _just_did_inaccurate_seek = false;
 
-                       shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
-                       if (ad && ad->has_audio ()) {
-                               audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
-                       }
-               }
+       } else if (da && _audio) {
 
-               if (audio_done_up_to) {
-                       TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
-                       Audio (tb.audio, tb.time);
-                       _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+               if (da->dcp_time > _audio_position) {
+                       /* Too far ahead */
+                       emit_silence (da->dcp_time - _audio_position);
+                       consume = false;
+                       _statistics.audio.silence += (da->dcp_time - _audio_position);
+               } else if (da->dcp_time == _audio_position) {
+                       /* We're ok */
+                       emit_audio (earliest_piece, da);
+                       _statistics.audio.good += da->data->frames();
+               } else {
+                       /* Too far behind: skip */
+                       _statistics.audio.skip += da->data->frames();
                }
-       }
                
+       } else if (dis && _video) {
+               _image_subtitle.piece = earliest_piece;
+               _image_subtitle.subtitle = dis;
+               update_subtitle_from_image ();
+       } else if (dts && _video) {
+               _text_subtitle.piece = earliest_piece;
+               _text_subtitle.subtitle = dts;
+               update_subtitle_from_text ();
+       }
+
+       if (consume) {
+               earliest_piece->decoder->consume ();
+       }                       
+       
        return false;
 }
 
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
 void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
 {
        /* Keep a note of what came in so that we can repeat it if required */
        _last_incoming_video.weak_piece = weak_piece;
-       _last_incoming_video.image = image;
-       _last_incoming_video.eyes = eyes;
-       _last_incoming_video.same = same;
-       _last_incoming_video.frame = frame;
-       _last_incoming_video.extra = extra;
+       _last_incoming_video.video = video;
        
        shared_ptr<Piece> piece = weak_piece.lock ();
        if (!piece) {
@@ -260,23 +267,18 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
        shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
        assert (content);
 
-       FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
-       if (frc.skip && (frame % 2) == 1) {
-               return;
-       }
+       FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
 
-       Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
-       if (content->trimmed (relative_time)) {
-               return;
-       }
-
-       Time const time = content->position() + relative_time + extra - content->trim_start ();
        float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
-       libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
+       libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
+       if (_approximate_size) {
+               image_size.width &= ~3;
+               image_size.height &= ~3;
+       }
 
        shared_ptr<PlayerImage> pi (
                new PlayerImage (
-                       image,
+                       video->image,
                        content->crop(),
                        image_size,
                        _video_container_size,
@@ -284,11 +286,15 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
                        )
                );
        
-       if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+       if (
+               _film->with_subtitles () &&
+               _out_subtitle.image &&
+               video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
+               ) {
 
                Position<int> const container_offset (
                        (_video_container_size.width - image_size.width) / 2,
-                       (_video_container_size.height - image_size.width) / 2
+                       (_video_container_size.height - image_size.height) / 2
                        );
 
                pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
@@ -299,18 +305,25 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
        _last_video = piece->content;
 #endif
 
-       Video (pi, eyes, content->colour_conversion(), same, time);
-
+       Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
+       
        _last_emit_was_black = false;
-       _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+}
 
-       if (frc.repeat > 1 && !piece->repeating ()) {
-               piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+void
+Player::step_video_position (shared_ptr<DecodedVideo> video)
+{
+       /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
+       if (video->eyes != EYES_LEFT) {
+               /* This assumes that the video_frames_to_time conversion is exact
+                  so that there are no accumulated errors caused by rounding.
+               */
+               _video_position += _film->video_frames_to_time (1);
        }
 }
 
 void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
 {
        shared_ptr<Piece> piece = weak_piece.lock ();
        if (!piece) {
@@ -322,37 +335,20 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
 
        /* Gain */
        if (content->audio_gain() != 0) {
-               shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+               shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
                gain->apply_gain (content->audio_gain ());
-               audio = gain;
-       }
-
-       /* Resample */
-       if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
-               shared_ptr<Resampler> r = resampler (content, true);
-               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
-               audio = ro.first;
-               frame = ro.second;
+               audio->data = gain;
        }
-       
-       Time const relative_time = _film->audio_frames_to_time (frame);
 
-       if (content->trimmed (relative_time)) {
-               return;
-       }
-
-       Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-       
        /* Remap channels */
-       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
        dcp_mapped->make_silent ();
-
        AudioMapping map = content->audio_mapping ();
        for (int i = 0; i < map.content_channels(); ++i) {
                for (int j = 0; j < _film->audio_channels(); ++j) {
                        if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
                                dcp_mapped->accumulate_channel (
-                                       audio.get(),
+                                       audio->data.get(),
                                        i,
                                        static_cast<libdcp::Channel> (j),
                                        map.get (i, static_cast<libdcp::Channel> (j))
@@ -361,30 +357,30 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
                }
        }
 
-       audio = dcp_mapped;
+       audio->data = dcp_mapped;
 
-       /* We must cut off anything that comes before the start of all time */
-       if (time < 0) {
-               int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
-               if (frames >= audio->frames ()) {
+       /* Delay */
+       audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+       if (audio->dcp_time < 0) {
+               int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+               if (frames >= audio->data->frames ()) {
                        return;
                }
 
-               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
-               trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+               trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
 
-               audio = trimmed;
-               time = 0;
+               audio->data = trimmed;
+               audio->dcp_time = 0;
        }
 
-       _audio_merger.push (audio, time);
-       piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+       _audio_merger.push (audio->data, audio->dcp_time);
 }
 
 void
 Player::flush ()
 {
-       TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+       TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
        if (_audio && tb.audio) {
                Audio (tb.audio, tb.time);
                _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
@@ -395,7 +391,7 @@ Player::flush ()
        }
 
        while (_audio && _audio_position < _video_position) {
-               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+               emit_silence (_video_position - _audio_position);
        }
        
 }
@@ -405,7 +401,7 @@ Player::flush ()
  *  @return true on error
  */
 void
-Player::seek (Time t, bool accurate)
+Player::seek (DCPTime t, bool accurate)
 {
        if (!_have_valid_pieces) {
                setup_pieces ();
@@ -416,92 +412,122 @@ Player::seek (Time t, bool accurate)
        }
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
-               if (!vc) {
-                       continue;
-               }
-
                /* s is the offset of t from the start position of this content */
-               Time s = t - vc->position ();
-               s = max (static_cast<Time> (0), s);
-               s = min (vc->length_after_trim(), s);
+               DCPTime s = t - (*i)->content->position ();
+               s = max (static_cast<DCPTime> (0), s);
+               s = min ((*i)->content->length_after_trim(), s);
 
-               /* Hence set the piece positions to the `global' time */
-               (*i)->video_position = (*i)->audio_position = vc->position() + s;
+               /* Convert this to the content time */
+               ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
 
                /* And seek the decoder */
-               dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
-                       vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
-                       );
-
-               (*i)->reset_repeat ();
+               (*i)->decoder->seek (ct, accurate);
        }
 
-       _video_position = _audio_position = t;
+       _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
+       _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
+
+       _audio_merger.clear (_audio_position);
 
-       /* XXX: don't seek audio because we don't need to... */
+       if (!accurate) {
+               /* We just did an inaccurate seek, so it's likely that the next thing seen
+                  out of pass() will be a fair distance from _{video,audio}_position.  Setting
+                  this flag stops pass() from trying to fix that: we assume that if it
+                  was an inaccurate seek then the caller does not care too much about
+                  inserting black/silence to keep the time tidy.
+               */
+               _just_did_inaccurate_seek = true;
+       }
 }
 
 void
 Player::setup_pieces ()
 {
        list<shared_ptr<Piece> > old_pieces = _pieces;
-
        _pieces.clear ();
 
        ContentList content = _playlist->content ();
-       sort (content.begin(), content.end(), ContentSorter ());
 
        for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 
-               shared_ptr<Piece> piece (new Piece (*i));
+               shared_ptr<Decoder> decoder;
+               optional<FrameRateChange> frc;
 
-               /* XXX: into content? */
+               /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+               DCPTime best_overlap_t = 0;
+               shared_ptr<VideoContent> best_overlap;
+               for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+                       if (!vc) {
+                               continue;
+                       }
+                       
+                       DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+                       if (overlap > best_overlap_t) {
+                               best_overlap = vc;
+                               best_overlap_t = overlap;
+                       }
+               }
 
+               optional<FrameRateChange> best_overlap_frc;
+               if (best_overlap) {
+                       best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+               } else {
+                       /* No video overlap; e.g. if the DCP is just audio */
+                       best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+               }
+
+               /* FFmpeg */
                shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
                if (fc) {
-                       shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-                       
-                       fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                       fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
-                       fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
-                       fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
-                       piece->decoder = fd;
+                       decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
+                       frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
                }
-               
+
+               /* ImageContent */
                shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
                if (ic) {
-                       bool reusing = false;
-                       
                        /* See if we can re-use an old ImageDecoder */
                        for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
                                shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
                                if (imd && imd->content() == ic) {
-                                       piece = *j;
-                                       reusing = true;
+                                       decoder = imd;
                                }
                        }
 
-                       if (!reusing) {
-                               shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
-                               id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                               piece->decoder = id;
+                       if (!decoder) {
+                               decoder.reset (new ImageDecoder (_film, ic));
                        }
+
+                       frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
                }
 
+               /* SndfileContent */
                shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
                if (sc) {
-                       shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
-                       sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+                       decoder.reset (new SndfileDecoder (_film, sc));
+                       frc = best_overlap_frc;
+               }
 
-                       piece->decoder = sd;
+               /* SubRipContent */
+               shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+               if (rc) {
+                       decoder.reset (new SubRipDecoder (_film, rc));
+                       frc = best_overlap_frc;
                }
 
-               _pieces.push_back (piece);
+               ContentTime st = (*i)->trim_start() * frc->speed_up;
+               decoder->seek (st, true);
+               
+               _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
        }
 
        _have_valid_pieces = true;
+
+       /* The Piece for the _last_incoming_video will no longer be valid */
+       _last_incoming_video.video.reset ();
+
+       _video_position = _audio_position = 0;
 }
 
 void
@@ -527,7 +553,8 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
                property == SubtitleContentProperty::SUBTITLE_SCALE
                ) {
 
-               update_subtitle ();
+               update_subtitle_from_image ();
+               update_subtitle_from_text ();
                Changed (frequent);
 
        } else if (
@@ -569,29 +596,6 @@ Player::set_video_container_size (libdcp::Size s)
                );
 }
 
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
-       map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
-       if (i != _resamplers.end ()) {
-               return i->second;
-       }
-
-       if (!create) {
-               return shared_ptr<Resampler> ();
-       }
-
-       _film->log()->log (
-               String::compose (
-                       "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
-                       )
-               );
-       
-       shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
-       _resamplers[c] = r;
-       return r;
-}
-
 void
 Player::emit_black ()
 {
@@ -605,17 +609,18 @@ Player::emit_black ()
 }
 
 void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
 {
        if (most == 0) {
                return;
        }
        
-       OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
-       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+       DCPTime t = min (most, TIME_HZ / 2);
+       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
        silence->make_silent ();
        Audio (silence, _audio_position);
-       _audio_position += _film->audio_frames_to_time (N);
+       
+       _audio_position += t;
 }
 
 void
@@ -632,26 +637,14 @@ Player::film_changed (Film::Property p)
 }
 
 void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+Player::update_subtitle_from_image ()
 {
-       _in_subtitle.piece = weak_piece;
-       _in_subtitle.image = image;
-       _in_subtitle.rect = rect;
-       _in_subtitle.from = from;
-       _in_subtitle.to = to;
-
-       update_subtitle ();
-}
-
-void
-Player::update_subtitle ()
-{
-       shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+       shared_ptr<Piece> piece = _image_subtitle.piece.lock ();
        if (!piece) {
                return;
        }
 
-       if (!_in_subtitle.image) {
+       if (!_image_subtitle.subtitle->image) {
                _out_subtitle.image.reset ();
                return;
        }
@@ -659,7 +652,7 @@ Player::update_subtitle ()
        shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
        assert (sc);
 
-       dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
+       dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect;
        libdcp::Size scaled_size;
 
        in_rect.x += sc->subtitle_x_offset ();
@@ -684,24 +677,15 @@ Player::update_subtitle ()
        _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
        _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
        
-       _out_subtitle.image = _in_subtitle.image->scale (
+       _out_subtitle.image = _image_subtitle.subtitle->image->scale (
                scaled_size,
                Scaler::from_id ("bicubic"),
-               _in_subtitle.image->pixel_format (),
+               _image_subtitle.subtitle->image->pixel_format (),
                true
                );
-
-       /* XXX: hack */
-       Time from = _in_subtitle.from;
-       Time to = _in_subtitle.to;
-       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
-       if (vc) {
-               from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
-               to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
-       }
        
-       _out_subtitle.from = from + piece->content->position ();
-       _out_subtitle.to = to + piece->content->position ();
+       _out_subtitle.from = _image_subtitle.subtitle->dcp_time + piece->content->position ();
+       _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to + piece->content->position ();
 }
 
 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -710,22 +694,35 @@ Player::update_subtitle ()
 bool
 Player::repeat_last_video ()
 {
-       if (!_last_incoming_video.image || !_have_valid_pieces) {
+       if (!_last_incoming_video.video || !_have_valid_pieces) {
                return false;
        }
 
-       process_video (
+       emit_video (
                _last_incoming_video.weak_piece,
-               _last_incoming_video.image,
-               _last_incoming_video.eyes,
-               _last_incoming_video.same,
-               _last_incoming_video.frame,
-               _last_incoming_video.extra
+               _last_incoming_video.video
                );
 
        return true;
 }
 
+void
+Player::update_subtitle_from_text ()
+{
+       if (_text_subtitle.subtitle->subs.empty ()) {
+               _out_subtitle.image.reset ();
+               return;
+       }
+
+       render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position);
+}
+
+void
+Player::set_approximate_size ()
+{
+       _approximate_size = true;
+}
+                             
 PlayerImage::PlayerImage (
        shared_ptr<const Image> in,
        Crop crop,
@@ -750,10 +747,10 @@ PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
 }
 
 shared_ptr<Image>
-PlayerImage::image ()
+PlayerImage::image (AVPixelFormat format, bool aligned)
 {
-       shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
-
+       shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+       
        Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
 
        if (_subtitle_image) {
@@ -762,3 +759,16 @@ PlayerImage::image ()
 
        return out;
 }
+
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+       log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+       log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
+}
+
+PlayerStatistics const &
+Player::statistics () const
+{
+       return _statistics;
+}
index 11cc99e7793005630a5e9ce1014e5827bdf55ed4..377e8bd18e5ae3634d4faa0848f19fbcee088082 100644 (file)
 #include <list>
 #include <boost/shared_ptr.hpp>
 #include <boost/enable_shared_from_this.hpp>
+#include <libdcp/subtitle_asset.h>
 #include "playlist.h"
 #include "content.h"
 #include "film.h"
 #include "rect.h"
 #include "audio_merger.h"
 #include "audio_content.h"
+#include "decoded.h"
 
 class Job;
 class Film;
@@ -36,24 +38,9 @@ class Playlist;
 class AudioContent;
 class Piece;
 class Image;
-class Resampler;
 
-/** @class Player
- *  @brief A class which can `play' a Playlist; emitting its audio and video.
- */
-
-struct IncomingVideo
-{
-public:
-       boost::weak_ptr<Piece> weak_piece;
-       boost::shared_ptr<const Image> image;
-       Eyes eyes;
-       bool same;
-       VideoContent::Frame frame;
-       Time extra;
-};
-
-/** A wrapper for an Image which contains some pending operations; these may
+/** @class PlayerImage
+ *  @brief A wrapper for an Image which contains some pending operations; these may
  *  not be necessary if the receiver of the PlayerImage throws it away.
  */
 class PlayerImage
@@ -63,7 +50,7 @@ public:
 
        void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
        
-       boost::shared_ptr<Image> image ();
+       boost::shared_ptr<Image> image (AVPixelFormat, bool);
 
 private:
        boost::shared_ptr<const Image> _in;
@@ -74,7 +61,43 @@ private:
        boost::shared_ptr<const Image> _subtitle_image;
        Position<int> _subtitle_position;
 };
+
+class PlayerStatistics
+{
+public:
+       struct Video {
+               Video ()
+                       : black (0)
+                       , repeat (0)
+                       , good (0)
+                       , skip (0)
+               {}
+               
+               int black;
+               int repeat;
+               int good;
+               int skip;
+       } video;
+
+       struct Audio {
+               Audio ()
+                       : silence (0)
+                       , good (0)
+                       , skip (0)
+               {}
+               
+               int64_t silence;
+               int64_t good;
+               int64_t skip;
+       } audio;
+
+       void dump (boost::shared_ptr<Log>) const;
+};
  
+/** @class Player
+ *  @brief A class which can `play' a Playlist; emitting its audio and video.
+ */
+
 class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
 {
 public:
@@ -84,16 +107,19 @@ public:
        void disable_audio ();
 
        bool pass ();
-       void seek (Time, bool);
+       void seek (DCPTime, bool);
 
-       Time video_position () const {
+       DCPTime video_position () const {
                return _video_position;
        }
 
        void set_video_container_size (libdcp::Size);
+       void set_approximate_size ();
 
        bool repeat_last_video ();
 
+       PlayerStatistics const & statistics () const;
+       
        /** Emitted when a video frame is ready.
         *  First parameter is the video image.
         *  Second parameter is the eye(s) that should see this image.
@@ -101,10 +127,10 @@ public:
         *  Fourth parameter is true if the image is the same as the last one that was emitted.
         *  Fifth parameter is the time.
         */
-       boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video;
+       boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime)> Video;
        
        /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, DCPTime)> Audio;
 
        /** Emitted when something has changed such that if we went back and emitted
         *  the last frame again it would look different.  This is not emitted after
@@ -118,19 +144,19 @@ private:
        friend class PlayerWrapper;
        friend class Piece;
 
-       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time);
-       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
-       void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
        void setup_pieces ();
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int, bool);
-       void do_seek (Time, bool);
+       void do_seek (DCPTime, bool);
        void flush ();
        void emit_black ();
-       void emit_silence (OutputAudioFrame);
-       boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
+       void emit_silence (AudioFrame);
        void film_changed (Film::Property);
-       void update_subtitle ();
+       void update_subtitle_from_image ();
+       void update_subtitle_from_text ();
+       void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>);
+       void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>);
+       void step_video_position (boost::shared_ptr<DecodedVideo>);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -143,29 +169,30 @@ private:
        std::list<boost::shared_ptr<Piece> > _pieces;
 
        /** The time after the last video that we emitted */
-       Time _video_position;
+       DCPTime _video_position;
        /** The time after the last audio that we emitted */
-       Time _audio_position;
+       DCPTime _audio_position;
 
-       AudioMerger<Time, AudioContent::Frame> _audio_merger;
+       AudioMerger<DCPTime, AudioFrame> _audio_merger;
 
        libdcp::Size _video_container_size;
        boost::shared_ptr<PlayerImage> _black_frame;
-       std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
 
        struct {
                boost::weak_ptr<Piece> piece;
-               boost::shared_ptr<Image> image;
-               dcpomatic::Rect<double> rect;
-               Time from;
-               Time to;
-       } _in_subtitle;
+               boost::shared_ptr<DecodedImageSubtitle> subtitle;
+       } _image_subtitle;
 
        struct {
-               boost::shared_ptr<Image> image;
+               boost::weak_ptr<Piece> piece;
+               boost::shared_ptr<DecodedTextSubtitle> subtitle;
+       } _text_subtitle;
+       
+       struct {
                Position<int> position;
-               Time from;
-               Time to;
+               boost::shared_ptr<Image> image;
+               DCPTime from;
+               DCPTime to;
        } _out_subtitle;
 
 #ifdef DCPOMATIC_DEBUG
@@ -174,7 +201,15 @@ private:
 
        bool _last_emit_was_black;
 
-       IncomingVideo _last_incoming_video;
+       struct {
+               boost::weak_ptr<Piece> weak_piece;
+               boost::shared_ptr<DecodedVideo> video;
+       } _last_incoming_video;
+
+       bool _just_did_inaccurate_seek;
+       bool _approximate_size;
+
+       PlayerStatistics _statistics;
 
        boost::signals2::scoped_connection _playlist_changed_connection;
        boost::signals2::scoped_connection _playlist_content_changed_connection;
index daa82cb94a56e305dc3420424f15e2d7a4f1cf41..4175de4c9ce89d4f4baf8b403e92dac1ffcb05d0 100644 (file)
@@ -81,7 +81,7 @@ Playlist::maybe_sequence_video ()
        _sequencing_video = true;
        
        ContentList cl = _content;
-       Time next = 0;
+       DCPTime next = 0;
        for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
                if (!dynamic_pointer_cast<VideoContent> (*i)) {
                        continue;
@@ -254,10 +254,10 @@ Playlist::best_dcp_frame_rate () const
        return best->dcp;
 }
 
-Time
+DCPTime
 Playlist::length () const
 {
-       Time len = 0;
+       DCPTime len = 0;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
                len = max (len, (*i)->end() + 1);
        }
@@ -279,10 +279,10 @@ Playlist::reconnect ()
        }
 }
 
-Time
+DCPTime
 Playlist::video_end () const
 {
-       Time end = 0;
+       DCPTime end = 0;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
                if (dynamic_pointer_cast<const VideoContent> (*i)) {
                        end = max (end, (*i)->end ());
@@ -292,6 +292,23 @@ Playlist::video_end () const
        return end;
 }
 
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+               if (!vc) {
+                       break;
+               }
+
+               if (vc->position() >= t && t < vc->end()) {
+                       return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+               }
+       }
+
+       return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
 void
 Playlist::set_sequence_video (bool s)
 {
@@ -314,7 +331,7 @@ Playlist::content () const
 void
 Playlist::repeat (ContentList c, int n)
 {
-       pair<Time, Time> range (TIME_MAX, 0);
+       pair<DCPTime, DCPTime> range (TIME_MAX, 0);
        for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                range.first = min (range.first, (*i)->position ());
                range.second = max (range.second, (*i)->position ());
@@ -322,7 +339,7 @@ Playlist::repeat (ContentList c, int n)
                range.second = max (range.second, (*i)->end ());
        }
 
-       Time pos = range.second;
+       DCPTime pos = range.second;
        for (int i = 0; i < n; ++i) {
                for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                        shared_ptr<Content> copy = (*i)->clone ();
@@ -355,7 +372,7 @@ Playlist::move_earlier (shared_ptr<Content> c)
                return;
        }
        
-       Time const p = (*previous)->position ();
+       DCPTime const p = (*previous)->position ();
        (*previous)->set_position (p + c->length_after_trim ());
        c->set_position (p);
        sort (_content.begin(), _content.end(), ContentSorter ());
@@ -382,7 +399,7 @@ Playlist::move_later (shared_ptr<Content> c)
                return;
        }
 
-       Time const p = (*next)->position ();
+       DCPTime const p = (*next)->position ();
        (*next)->set_position (c->position ());
        c->set_position (p + c->length_after_trim ());
        sort (_content.begin(), _content.end(), ContentSorter ());
index 1915e3d045a01cabe7058c0fb4af089599548842..35709f109d2bf4bb4cbcbd725f61bbce7f7bf3ae 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/enable_shared_from_this.hpp>
 #include "ffmpeg_content.h"
 #include "audio_mapping.h"
+#include "util.h"
 
 class Content;
 class FFmpegContent;
@@ -70,10 +71,11 @@ public:
 
        std::string video_identifier () const;
 
-       Time length () const;
+       DCPTime length () const;
        
        int best_dcp_frame_rate () const;
-       Time video_end () const;
+       DCPTime video_end () const;
+       FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
 
        void set_sequence_video (bool);
        void maybe_sequence_video ();
diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc
new file mode 100644 (file)
index 0000000..6306817
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (libdcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+       switch (v_align) {
+       case libdcp::TOP:
+               return (v_position / 100) * target_height - offset;
+       case libdcp::CENTER:
+               return (0.5 + v_position / 100) * target_height - offset;
+       case libdcp::BOTTOM:
+               return (1.0 - v_position / 100) * target_height - offset;
+       }
+
+       return 0;
+}
+
+void
+render_subtitles (list<libdcp::Subtitle> subtitles, libdcp::Size target, shared_ptr<Image>& image, Position<int>& position)
+{
+       if (subtitles.empty ()) {
+               image.reset ();
+               return;
+       }
+
+       /* Estimate height that the subtitle image needs to be */
+       optional<int> top;
+       optional<int> bottom;
+       for (list<libdcp::Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+               int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+               int const t = b - i->size() * target.height / (11 * 72);
+
+               top = min (top.get_value_or (t), t);
+               bottom = max (bottom.get_value_or (b), b);
+       }
+
+       top = top.get() - 32;
+       bottom = bottom.get() + 32;
+
+       image.reset (new Image (PIX_FMT_RGBA, libdcp::Size (target.width, bottom.get() - top.get ()), false));
+       image->make_black ();
+
+       Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+               image->data()[0],
+               Cairo::FORMAT_ARGB32,
+               image->size().width,
+               image->size().height,
+               Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+               );
+       
+       Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+       Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+       layout->set_width (image->size().width * PANGO_SCALE);
+       layout->set_alignment (Pango::ALIGN_CENTER);
+
+       context->set_line_width (1);
+
+       for (list<libdcp::Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+               string f = i->font ();
+               if (f.empty ()) {
+                       f = "Arial";
+               }
+               Pango::FontDescription font (f);
+               font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+               if (i->italic ()) {
+                       font.set_style (Pango::STYLE_ITALIC);
+               }
+               layout->set_font_description (font);
+               layout->set_text (i->text ());
+
+               /* Compute fade factor */
+               /* XXX */
+               float fade_factor = 1;
+#if 0          
+               libdcp::Time now (time * 1000 / (4 * TIME_HZ));
+               libdcp::Time end_fade_up = i->in() + i->fade_up_time ();
+               libdcp::Time start_fade_down = i->out() - i->fade_down_time ();
+               if (now < end_fade_up) {
+                       fade_factor = (now - i->in()) / i->fade_up_time();
+               } else if (now > start_fade_down) {
+                       fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+               }
+#endif         
+
+               layout->update_from_cairo_context (context);
+               
+               /* Work out position */
+
+               int const x = 0;
+               int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+               if (i->effect() == libdcp::SHADOW) {
+                       /* Drop-shadow effect */
+                       libdcp::Color const ec = i->effect_color ();
+                       context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+                       context->move_to (x + 4, y + 4);
+                       layout->add_to_cairo_context (context);
+                       context->fill ();
+               }
+
+               /* The actual subtitle */
+               context->move_to (x, y);
+               libdcp::Color const c = i->color ();
+               context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+               layout->add_to_cairo_context (context);
+               context->fill ();
+
+               if (i->effect() == libdcp::BORDER) {
+                       /* Border effect */
+                       context->move_to (x, y);
+                       libdcp::Color ec = i->effect_color ();
+                       context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+                       layout->add_to_cairo_context (context);
+                       context->stroke ();
+               }
+       }
+}
+
diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h
new file mode 100644 (file)
index 0000000..2264838
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libdcp/subtitle_asset.h>
+#include <libdcp/util.h>
+#include "position.h"
+
+class Image;
+
+void
+render_subtitles (std::list<libdcp::Subtitle>, libdcp::Size, boost::shared_ptr<Image> &, Position<int> &);
index d897bf562dc4a97dcf5c314a8e7242e050f93e2f..00121384dce7617ab59f0ee85ea0373a7ab0c440 100644 (file)
@@ -64,11 +64,9 @@ Resampler::~Resampler ()
        swr_free (&_swr_context);
 }
 
-pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
-Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
 {
-       AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
-               
        /* Compute the resampled frames count and add 32 for luck */
        int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
        shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
@@ -84,7 +82,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
        }
        
        resampled->set_frames (resampled_frames);
-       return make_pair (resampled, resamp_time);
+       return resampled;
 }      
 
 shared_ptr<const AudioBuffers>
index 69ec83ba93047f3493cd23005487b7845580944e..4ee11a7f0954958e69a99bf09be75196394838c3 100644 (file)
@@ -33,7 +33,7 @@ public:
        Resampler (int, int, int);
        ~Resampler ();
 
-       std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
        boost::shared_ptr<const AudioBuffers> flush ();
 
 private:       
index 796229777f86374a05b60e0eb11f1bb0c9c34ba5..d3acc7d2e3662a4261d56e6d077e888da4a50ede 100644 (file)
@@ -49,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml:
        , _audio_mapping (node->node_child ("AudioMapping"), version)
 {
        _audio_channels = node->number_child<int> ("AudioChannels");
-       _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+       _audio_length = node->number_child<AudioFrame> ("AudioLength");
        _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
 }
 
@@ -141,13 +141,13 @@ SndfileContent::as_xml (xmlpp::Node* node) const
        _audio_mapping.as_xml (node->add_child("AudioMapping"));
 }
 
-Time
+DCPTime
 SndfileContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
 
-       OutputAudioFrame const len = audio_length() * output_audio_frame_rate() / content_audio_frame_rate ();
+       AudioFrame const len = audio_length() * output_audio_frame_rate() / content_audio_frame_rate ();
        
        /* XXX: this depends on whether, alongside this audio, we are running video slower or faster than
           it should be.  The calculation above works out the output audio frames assuming that we are just
index 701ff16b24bd0dbbdf2d75e5708e2be3c7ce9e45..94f46fea34f5650f4fd9f8832ac76027505ae5d7 100644 (file)
@@ -44,7 +44,7 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        /* AudioContent */
        int audio_channels () const {
@@ -52,7 +52,7 @@ public:
                return _audio_channels;
        }
        
-       AudioContent::Frame audio_length () const {
+       AudioFrame audio_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_length;
        }
@@ -75,7 +75,7 @@ public:
 
 private:
        int _audio_channels;
-       AudioContent::Frame _audio_length;
+       AudioFrame _audio_length;
        int _audio_frame_rate;
        AudioMapping _audio_mapping;
 };
index e10f4f568430d08dd86d1d5db38d2116606fbbbb..d6537843e8970ebde3ae6872bb99f04fd66e5de3 100644 (file)
@@ -55,9 +55,13 @@ SndfileDecoder::~SndfileDecoder ()
        delete[] _deinterleave_buffer;
 }
 
-void
+bool
 SndfileDecoder::pass ()
 {
+       if (_remaining == 0) {
+               return true;
+       }
+       
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
@@ -90,9 +94,11 @@ SndfileDecoder::pass ()
        }
                
        data->set_frames (this_time);
-       audio (data, _done);
+       audio (data, _done * TIME_HZ / audio_frame_rate ());
        _done += this_time;
        _remaining -= this_time;
+
+       return _remaining == 0;
 }
 
 int
@@ -101,7 +107,7 @@ SndfileDecoder::audio_channels () const
        return _info.channels;
 }
 
-AudioContent::Frame
+AudioFrame
 SndfileDecoder::audio_length () const
 {
        return _info.frames;
@@ -113,8 +119,12 @@ SndfileDecoder::audio_frame_rate () const
        return _info.samplerate;
 }
 
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
 {
-       return _audio_position >= _sndfile_content->audio_length ();
+       Decoder::seek (t, accurate);
+       AudioDecoder::seek (t, accurate);
+
+       _done = t * audio_frame_rate() / TIME_HZ;
+       _remaining = _info.frames - _done;
 }
index 77fa6d17734da4096757dae504a759c9925e0e8b..46d9c5e5cf24762b5c2733e4f8c0be6c035a4d97 100644 (file)
@@ -29,18 +29,19 @@ public:
        SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
        ~SndfileDecoder ();
 
-       void pass ();
-       bool done () const;
+       void seek (ContentTime, bool);
 
        int audio_channels () const;
-       AudioContent::Frame audio_length () const;
+       AudioFrame audio_length () const;
        int audio_frame_rate () const;
 
 private:
+       bool pass ();
+       
        boost::shared_ptr<const SndfileContent> _sndfile_content;
        SNDFILE* _sndfile;
        SF_INFO _info;
-       AudioContent::Frame _done;
-       AudioContent::Frame _remaining;
+       AudioFrame _done;
+       AudioFrame _remaining;
        float* _deinterleave_buffer;
 };
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644 (file)
index 0000000..380a2ce
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/algorithm/string.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+       FILE* f = fopen_boost (content->path (0), "r");
+       if (!f) {
+               throw OpenFileError (content->path (0));
+       }
+
+       enum {
+               COUNTER,
+               METADATA,
+               CONTENT
+       } state = COUNTER;
+
+       char buffer[256];
+       int next_count = 1;
+
+       boost::optional<SubRipSubtitle> current;
+       list<string> lines;
+       
+       while (!feof (f)) {
+               fgets (buffer, sizeof (buffer), f);
+               if (feof (f)) {
+                       break;
+               }
+               
+               string line (buffer);
+               trim_right_if (line, boost::is_any_of ("\n\r"));
+               
+               switch (state) {
+               case COUNTER:
+               {
+                       int x = 0;
+                       try {
+                               x = lexical_cast<int> (line);
+                       } catch (...) {
+
+                       }
+                       
+                       if (x == next_count) {
+                               state = METADATA;
+                               ++next_count;
+                               current = SubRipSubtitle ();
+                       } else {
+                               throw SubRipError (line, _("a subtitle count"), content->path (0));
+                       }
+               }
+               break;
+               case METADATA:
+               {
+                       vector<string> p;
+                       boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+                       if (p.size() != 3 && p.size() != 7) {
+                               throw SubRipError (line, _("a time/position line"), content->path (0));
+                       }
+
+                       current->from = convert_time (p[0]);
+                       current->to = convert_time (p[2]);
+
+                       if (p.size() > 3) {
+                               current->x1 = convert_coordinate (p[3]);
+                               current->x2 = convert_coordinate (p[4]);
+                               current->y1 = convert_coordinate (p[5]);
+                               current->y2 = convert_coordinate (p[6]);
+                       }
+                       state = CONTENT;
+                       break;
+               }
+               case CONTENT:
+                       if (line.empty ()) {
+                               state = COUNTER;
+                               current->pieces = convert_content (lines);
+                               _subtitles.push_back (current.get ());
+                               current.reset ();
+                               lines.clear ();
+                       } else {
+                               lines.push_back (line);
+                       }
+                       break;
+               }
+       }
+
+       if (state == CONTENT) {
+               current->pieces = convert_content (lines);
+               _subtitles.push_back (current.get ());
+       }
+
+       fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+       ContentTime r = 0;
+
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 3);
+       r += lexical_cast<int> (a[0]) * 60 * 60 * TIME_HZ;
+       r += lexical_cast<int> (a[1]) * 60 * TIME_HZ;
+
+       vector<string> b;
+       boost::algorithm::split (b, a[2], boost::is_any_of (","));
+       r += lexical_cast<int> (b[0]) * TIME_HZ;
+       r += lexical_cast<int> (b[1]) * TIME_HZ / 1000;
+
+       return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 2);
+       return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+       if (!p.text.empty ()) {
+               pieces.push_back (p);
+               p.text.clear ();
+       }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+       list<SubRipSubtitlePiece> pieces;
+       
+       SubRipSubtitlePiece p;
+
+       enum {
+               TEXT,
+               TAG
+       } state = TEXT;
+
+       string tag;
+
+       /* XXX: missing <font> support */
+       /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+          not work, I think.
+       */
+
+       for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+               for (size_t j = 0; j < i->size(); ++j) {
+                       switch (state) {
+                       case TEXT:
+                               if ((*i)[j] == '<' || (*i)[j] == '{') {
+                                       state = TAG;
+                               } else {
+                                       p.text += (*i)[j];
+                               }
+                               break;
+                       case TAG:
+                               if ((*i)[j] == '>' || (*i)[j] == '}') {
+                                       if (tag == "b") {
+                                               maybe_content (pieces, p);
+                                               p.bold = true;
+                                       } else if (tag == "/b") {
+                                               maybe_content (pieces, p);
+                                               p.bold = false;
+                                       } else if (tag == "i") {
+                                               maybe_content (pieces, p);
+                                               p.italic = true;
+                                       } else if (tag == "/i") {
+                                               maybe_content (pieces, p);
+                                               p.italic = false;
+                                       } else if (tag == "u") {
+                                               maybe_content (pieces, p);
+                                               p.underline = true;
+                                       } else if (tag == "/u") {
+                                               maybe_content (pieces, p);
+                                               p.underline = false;
+                                       }
+                                       tag.clear ();
+                                       state = TEXT;
+                               } else {
+                                       tag += (*i)[j];
+                               }
+                               break;
+                       }
+               }
+       }
+
+       maybe_content (pieces, p);
+
+       return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+       if (_subtitles.empty ()) {
+               return 0;
+       }
+
+       return _subtitles.back().to;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644 (file)
index 0000000..e7d2167
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+       SubRip (boost::shared_ptr<const SubRipContent>);
+
+       ContentTime length () const;
+
+protected:
+       std::vector<SubRipSubtitle> _subtitles;
+       
+private:
+       friend class subrip_time_test;
+       friend class subrip_coordinate_test;
+       friend class subrip_content_test;
+       friend class subrip_parse_test;
+       
+       static ContentTime convert_time (std::string);
+       static int convert_coordinate (std::string);
+       static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+       static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644 (file)
index 0000000..73499a5
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subrip_content.h"
+#include "util.h"
+#include "subrip.h"
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using boost::shared_ptr;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+       : Content (film, path)
+       , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
+       : Content (film, node)
+       , SubtitleContent (film, node, version)
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job> job)
+{
+       Content::examine (job);
+       SubRip s (shared_from_this ());
+       boost::mutex::scoped_lock lm (_mutex);
+       _length = s.length ();
+}
+
+string
+SubRipContent::summary () const
+{
+       return path_summary() + " " + _("[subtitles]");
+}
+
+string
+SubRipContent::technical_summary () const
+{
+       return Content::technical_summary() + " - " + _("SubRip subtitles");
+}
+
+string
+SubRipContent::information () const
+{
+       
+}
+       
+void
+SubRipContent::as_xml (xmlpp::Node* node)
+{
+       node->add_child("Type")->add_child_text ("SubRip");
+       Content::as_xml (node);
+       SubtitleContent::as_xml (node);
+}
+
+DCPTime
+SubRipContent::full_length () const
+{
+       /* XXX: this assumes that the timing of the SubRip file is appropriate
+          for the DCP's frame rate.
+       */
+       return _length;
+}
+
+string
+SubRipContent::identifier () const
+{
+       LocaleGuard lg;
+
+       stringstream s;
+       s << Content::identifier()
+         << "_" << subtitle_scale()
+         << "_" << subtitle_x_offset()
+         << "_" << subtitle_y_offset();
+
+       return s.str ();
+}
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
new file mode 100644 (file)
index 0000000..6138c04
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+       SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+       boost::shared_ptr<SubRipContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ());
+       }
+       
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string technical_summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *);
+       DCPTime full_length () const;
+       std::string identifier () const;
+
+private:
+       DCPTime _length;
+};
diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc
new file mode 100644 (file)
index 0000000..aecee4e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subrip_decoder.h"
+
+using std::list;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const Film> film, shared_ptr<const SubRipContent> content)
+       : Decoder (film)
+       , SubtitleDecoder (film)
+       , SubRip (content)
+       , _next (0)
+{
+
+}
+
+bool
+SubRipDecoder::pass ()
+{
+       if (_next >= _subtitles.size ()) {
+               return true;
+       }
+       
+       list<libdcp::Subtitle> out;
+       for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+               out.push_back (
+                       libdcp::Subtitle (
+                               "Arial",
+                               i->italic,
+                               libdcp::Color (255, 255, 255),
+                               72,
+                               _subtitles[_next].from,
+                               _subtitles[_next].to,
+                               0.9,
+                               libdcp::BOTTOM,
+                               i->text,
+                               libdcp::NONE,
+                               libdcp::Color (255, 255, 255),
+                               0,
+                               0
+                               )
+                       );
+       }
+
+       text_subtitle (out);
+       _next++;
+       return false;
+}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
new file mode 100644 (file)
index 0000000..26d5d50
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_DECODER_H
+#define DCPOMATIC_SUBRIP_DECODER_H
+
+#include "subtitle_decoder.h"
+#include "subrip.h"
+
+class SubRipContent;
+
+class SubRipDecoder : public SubtitleDecoder, public SubRip
+{
+public:
+       SubRipDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SubRipContent>);
+       
+       bool pass ();
+
+private:
+       size_t _next;
+};
+
+#endif
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
new file mode 100644 (file)
index 0000000..f730ee4
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <libdcp/types.h>
+#include "types.h"
+
+struct SubRipSubtitlePiece
+{
+       SubRipSubtitlePiece ()
+               : bold (false)
+               , italic (false)
+               , underline (false)
+       {}
+       
+       std::string text;
+       bool bold;
+       bool italic;
+       bool underline;
+       libdcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+       SubRipSubtitle ()
+               : from (0)
+               , to (0)
+       {}
+       
+       ContentTime from;
+       ContentTime to;
+       boost::optional<int> x1;
+       boost::optional<int> x2;
+       boost::optional<int> y1;
+       boost::optional<int> y2;
+       std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
index c06f3d718a812e9403266e3ccf91f5c494fa69b8..e5cadb7b48b4e513c3b11a7ad47babc10ecda689 100644 (file)
@@ -20,7 +20,9 @@
 #include <boost/shared_ptr.hpp>
 #include "subtitle_decoder.h"
 
+using std::list;
 using boost::shared_ptr;
+using boost::optional;
 
 SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
        : Decoder (f)
@@ -29,11 +31,17 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
 }
 
 
-/** Called by subclasses when a subtitle is ready.
+/** Called by subclasses when an image subtitle is ready.
  *  Image may be 0 to say that there is no current subtitle.
  */
 void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::image_subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, ContentTime from, ContentTime to)
 {
-       Subtitle (image, rect, from, to);
+       _pending.push_back (shared_ptr<DecodedImageSubtitle> (new DecodedImageSubtitle (image, rect, from, to)));
+}
+
+void
+SubtitleDecoder::text_subtitle (list<libdcp::Subtitle> s)
+{
+       _pending.push_back (shared_ptr<DecodedTextSubtitle> (new DecodedTextSubtitle (s)));
 }
index eeeadbd3f65fdda2f9ee46b90c9745f2ce713f8f..82662d192fbd1295d8574d7001b44d413984c353 100644 (file)
 
 */
 
+#ifndef DCPOMATIC_SUBTITLE_DECODER_H
+#define DCPOMATIC_SUBTITLE_DECODER_H
+
 #include <boost/signals2.hpp>
+#include <libdcp/subtitle_asset.h>
 #include "decoder.h"
 #include "rect.h"
 #include "types.h"
+#include "decoded.h"
 
 class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
 class Image;
 
 class SubtitleDecoder : public virtual Decoder
@@ -31,8 +36,9 @@ class SubtitleDecoder : public virtual Decoder
 public:
        SubtitleDecoder (boost::shared_ptr<const Film>);
 
-       boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
-
 protected:
-       void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+       void image_subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, ContentTime, ContentTime);
+       void text_subtitle (std::list<libdcp::Subtitle>);
 };
+
+#endif
index f0e22da64483d2166bf0adfe08e37d7946df393e..289259369d028652aa2fc684f7aabc4997c30971 100644 (file)
@@ -111,6 +111,6 @@ TranscodeJob::remaining_time () const
        }
 
        /* Compute approximate proposed length here, as it's only here that we need it */
-       OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
+       VideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
        return left / fps;
 }
index 1c8f7e3eb0b021d03f6c62a0076ce4cd41f56188..ba4d3b040baa432812cfe85e972ea9f6c9d052c1 100644 (file)
@@ -62,7 +62,8 @@ audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
  *  @param e Encoder to use.
  */
 Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
-       : _player (f->make_player ())
+       : _film (f)
+       , _player (f->make_player ())
        , _encoder (new Encoder (f, j))
        , _finishing (false)
 {
@@ -78,6 +79,8 @@ Transcoder::go ()
 
        _finishing = true;
        _encoder->process_end ();
+
+       _player->statistics().dump (_film->log ());
 }
 
 float
index d7736d4e8e56dd3a1cdf7440e6a5872bb7a59db1..25b2ef90841c68a23dd077eb12d41d0c4a282969 100644 (file)
@@ -42,6 +42,7 @@ public:
        }
 
 private:
+       boost::shared_ptr<const Film> _film;
        boost::shared_ptr<Player> _player;
        boost::shared_ptr<Encoder> _encoder;
        bool _finishing;
index 96b993a8e15866b4674324735e05cdadd968f2f7..924279c25eafb1885ad1e9fe504287565f1ebb67 100644 (file)
@@ -38,11 +38,12 @@ class AudioBuffers;
  */
 #define SERVER_LINK_VERSION 1
 
-typedef int64_t Time;
+typedef int64_t DCPTime;
 #define TIME_MAX INT64_MAX
-#define TIME_HZ         ((Time) 96000)
-typedef int64_t OutputAudioFrame;
-typedef int    OutputVideoFrame;
+#define TIME_HZ         ((DCPTime) 96000)
+typedef int64_t ContentTime;
+typedef int64_t AudioFrame;
+typedef int    VideoFrame;
 typedef std::vector<boost::shared_ptr<Content> > ContentList;
 typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
 typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
index edb202df260ea0f6dd333a8a455e627deeb07d22..ef203c2bdc82fae52119f0acef4b4d4c2ba27764 100644 (file)
@@ -47,6 +47,7 @@
 #include <openssl/md5.h>
 #include <magick/MagickCore.h>
 #include <magick/version.h>
+#include <pangomm/init.h>
 #include <libdcp/version.h>
 #include <libdcp/util.h>
 #include <libdcp/signer_chain.h>
@@ -309,6 +310,7 @@ dcpomatic_setup ()
        setenv ("LTDL_LIBRARY_PATH", lib.c_str (), 1);
 #endif 
 
+       Pango::init ();
        libdcp::init ();
        
        Ratio::setup_ratios ();
@@ -749,7 +751,7 @@ ensure_ui_thread ()
  *  @return Equivalent number of audio frames for `v'.
  */
 int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second)
 {
        return ((int64_t) v * audio_sample_rate / frames_per_second);
 }
@@ -774,7 +776,7 @@ audio_channel_name (int c)
        return channels[c];
 }
 
-FrameRateConversion::FrameRateConversion (float source, int dcp)
+FrameRateChange::FrameRateChange (float source, int dcp)
        : skip (false)
        , repeat (1)
        , change_speed (false)
@@ -792,7 +794,8 @@ FrameRateConversion::FrameRateConversion (float source, int dcp)
                repeat = round (dcp / source);
        }
 
-       change_speed = !about_equal (source * factor(), dcp);
+       speed_up = dcp / (source * factor());
+       change_speed = !about_equal (speed_up, 1.0);
 
        if (!skip && repeat == 1 && !change_speed) {
                description = _("Content and DCP have the same rate.\n");
@@ -916,3 +919,10 @@ fit_ratio_within (float ratio, libdcp::Size full_frame)
        
        return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
 }
+
+DCPTime
+time_round_up (DCPTime t, DCPTime nearest)
+{
+       DCPTime const a = t + nearest - 1;
+       return a - (a % nearest);
+}
index 7dcd920b7cac3d72974332eda264466faf0cc42a..a84e7e4cf815522ae65ffa6cf52bebbf46849210 100644 (file)
@@ -79,9 +79,9 @@ extern std::string tidy_for_filename (std::string);
 extern boost::shared_ptr<const libdcp::Signer> make_signer ();
 extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
 
-struct FrameRateConversion
+struct FrameRateChange
 {
-       FrameRateConversion (float, int);
+       FrameRateChange (float, int);
 
        /** @return factor by which to multiply a source frame rate
            to get the effective rate after any skip or repeat has happened.
@@ -109,11 +109,17 @@ struct FrameRateConversion
         */
        bool change_speed;
 
+       /** Amount by which the video is being sped-up in the DCP; e.g. for a
+        *  24fps source in a 25fps DCP this would be 25/24.
+        */
+       float speed_up;
+
        std::string description;
 };
 
 extern int dcp_audio_frame_rate (int);
 extern int stride_round_up (int, int const *, int);
+extern DCPTime time_round_up (DCPTime, DCPTime);
 extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
 extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
 extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
@@ -160,7 +166,7 @@ private:
        int _timeout;
 };
 
-extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+extern int64_t video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second);
 
 class LocaleGuard
 {
index cc075a34ced0534d0e3298affe5ce840d0a8926a..bf13e1c764f61df1c8c578474c47e5bce5788276 100644 (file)
@@ -59,7 +59,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
        setup_default_colour_conversion ();
 }
 
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, VideoFrame len)
        : Content (f, s)
        , _video_length (len)
        , _video_frame_rate (0)
@@ -83,7 +83,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod
        : Content (f, node)
        , _ratio (0)
 {
-       _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+       _video_length = node->number_child<VideoFrame> ("VideoLength");
        _video_size.width = node->number_child<int> ("VideoWidth");
        _video_size.height = node->number_child<int> ("VideoHeight");
        _video_frame_rate = node->number_child<float> ("VideoFrameRate");
@@ -353,19 +353,21 @@ VideoContent::video_size_after_crop () const
 }
 
 /** @param t A time offset from the start of this piece of content.
- *  @return Corresponding frame index.
+ *  @return Corresponding frame index, rounded up so that the frame index
+ *  is that of the next complete frame which starts after `t'.
  */
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+VideoFrame
+VideoContent::time_to_content_video_frames (DCPTime t) const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
        /* Here we are converting from time (in the DCP) to a frame number in the content.
           Hence we need to use the DCP's frame rate and the double/skip correction, not
-          the source's rate.
+          the source's rate; source rate will be equal to DCP rate if we ignore
+          double/skip.  There's no need to call Film::active_frame_rate_change() here
+          as we know that we are it (since we're video).
        */
-       return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
+       return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ));
 }
index 141525e01b20b237184c32cf21bc49d7a21791d9..8d901cbcdfa2d8b0a06745da2e0eabb4ccbf0943 100644 (file)
@@ -43,7 +43,7 @@ public:
        typedef int Frame;
 
        VideoContent (boost::shared_ptr<const Film>);
-       VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+       VideoContent (boost::shared_ptr<const Film>, DCPTime, VideoFrame);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -53,7 +53,7 @@ public:
        virtual std::string information () const;
        virtual std::string identifier () const;
 
-       VideoContent::Frame video_length () const {
+       VideoFrame video_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_length;
        }
@@ -123,12 +123,12 @@ public:
        libdcp::Size video_size_after_3d_split () const;
        libdcp::Size video_size_after_crop () const;
 
-       VideoContent::Frame time_to_content_video_frames (Time) const;
+       VideoFrame time_to_content_video_frames (DCPTime) const;
 
 protected:
        void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
 
-       VideoContent::Frame _video_length;
+       VideoFrame _video_length;
        float _video_frame_rate;
 
 private:
index e7ddec5e6cd19df910966a66fcb9e969b5c5b6ee..a3b45716c532887473d76c17f50957bf80dba5ac 100644 (file)
 
 using std::cout;
 using boost::shared_ptr;
+using boost::optional;
 
 VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
        : Decoder (f)
        , _video_content (c)
-       , _video_position (0)
 {
 
 }
 
+/** Called by subclasses when they have a video frame ready */
 void
-VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoFrame frame)
 {
        switch (_video_content->video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
-               Video (image, EYES_BOTH, same, frame);
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image, EYES_BOTH, same, frame)));
                break;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
        {
                int const half = image->size().width / 2;
-               Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
-               Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame)));
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame)));
                break;
        }
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
        {
                int const half = image->size().height / 2;
-               Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
-               Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame)));
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame)));
                break;
        }
+       default:
+               assert (false);
        }
-       
-       _video_position = frame + 1;
 }
-
index 142320a049c8e7a23bf7aff2d618149d6259941d..8947c27089b2b718c819ce42b39fdfa794aee425 100644 (file)
@@ -25,6 +25,7 @@
 #include "decoder.h"
 #include "video_content.h"
 #include "util.h"
+#include "decoded.h"
 
 class VideoContent;
 class Image;
@@ -34,24 +35,14 @@ class VideoDecoder : public virtual Decoder
 public:
        VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
 
-       /** Seek so that the next pass() will yield (approximately) the requested frame.
-        *  Pass accurate = true to try harder to get close to the request.
-        */
-       virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is the eye(s) which should see this image.
-        *  Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
-        *  Fourth parameter is the frame within our source.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
-       
+       boost::shared_ptr<const VideoContent> video_content () const {
+               return _video_content;
+       }
+
 protected:
 
-       void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       void video (boost::shared_ptr<const Image>, bool, VideoFrame);
        boost::shared_ptr<const VideoContent> _video_content;
-       VideoContent::Frame _video_position;
 };
 
 #endif
index 039c494b52fe98f630798729207b65b26b4d46f3..98c0cdff1433d21e0ad0106c994bf06762f6ffd8 100644 (file)
@@ -27,5 +27,5 @@ public:
        virtual ~VideoExaminer () {}
        virtual float video_frame_rate () const = 0;
        virtual libdcp::Size video_size () const = 0;
-       virtual VideoContent::Frame video_length () const = 0;
+       virtual VideoFrame video_length () const = 0;
 };
index 81a55a160bc42ab200931e9bec862d712ab7d965..8a20618c0641c91e30b82bdce72047926478ba3c 100644 (file)
@@ -41,6 +41,7 @@ sources = """
           player.cc
           playlist.cc
           ratio.cc
+          render_subtitles.cc
           resampler.cc
           scp_dcp_job.cc
           scaler.cc
@@ -50,6 +51,9 @@ sources = """
           sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
+          subrip.cc
+          subrip_content.cc
+          subrip_decoder.cc
           subtitle_content.cc
           subtitle_decoder.cc
           timer.cc
@@ -76,7 +80,7 @@ def build(bld):
                  AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                  BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
                  SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
-                 CURL ZIP QUICKMAIL
+                 CURL ZIP QUICKMAIL PANGOMM CAIROMM
                  """
 
     obj.source = sources + ' version.cc'
index b2c5e784ba8caf2346c08b903c5a81dec8d8e296..38e4704b751e8882437b8d605e5fbb1415151f21 100644 (file)
@@ -47,10 +47,15 @@ static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log"));
 static int frame = 0;
 
 void
-process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, Time)
+process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, DCPTime)
 {
-       shared_ptr<DCPVideoFrame> local  (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
-       shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
+       shared_ptr<DCPVideoFrame> local  (
+               new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)
+               );
+       
+       shared_ptr<DCPVideoFrame> remote (
+               new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)
+               );
 
        cout << "Frame " << frame << ": ";
        cout.flush ();
index f78885772ce033e682a1e9d178d85c93441b2b45..96de34d406744960db60c4aa072a0673699c9c2d 100644 (file)
@@ -145,7 +145,7 @@ AudioPlot::paint ()
        gc->SetPen (*wxLIGHT_GREY_PEN);
        gc->StrokePath (grid);
 
-       gc->DrawText (_("Time"), data_width, _height - _y_origin + db_label_height / 2);
+       gc->DrawText (_("DCPTime"), data_width, _height - _y_origin + db_label_height / 2);
        
        if (_type_visible[AudioPoint::PEAK]) {
                for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
index 061305436a3003563fe93d8f82d46f3b9ff576e6..831a57a0286c6678fc048dcfbb9987f56828a2e4 100644 (file)
@@ -841,7 +841,7 @@ FilmEditor::setup_content_sensitivity ()
 
        _video_panel->Enable    (video_selection.size() > 0 && _generally_sensitive);
        _audio_panel->Enable    (audio_selection.size() > 0 && _generally_sensitive);
-       _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
+       _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive);
        _timing_panel->Enable   (selection.size() == 1 && _generally_sensitive);
 }
 
@@ -936,7 +936,7 @@ FilmEditor::content_timeline_clicked ()
                _timeline_dialog = 0;
        }
        
-       _timeline_dialog = new TimelineDialog (this, _film);
+       _timeline_dialog = new DCPTimelineDialog (this, _film);
        _timeline_dialog->Show ();
 }
 
index 23c87e6784510288c54c8e9e96484b520e05bccc..dadb583ae07b028f1c8324b1a0f30db6513c426e 100644 (file)
@@ -33,9 +33,9 @@ class wxNotebook;
 class wxListCtrl;
 class wxListEvent;
 class Film;
-class TimelineDialog;
+class DCPTimelineDialog;
 class Ratio;
-class Timecode;
+class DCPTimecode;
 class FilmEditorPanel;
 class SubtitleContent;
 
@@ -156,5 +156,5 @@ private:
        std::vector<Ratio const *> _ratios;
 
        bool _generally_sensitive;
-       TimelineDialog* _timeline_dialog;
+       DCPTimelineDialog* _timeline_dialog;
 };
index fbca835c2bc2126513b9eb1b0eb49efce3fa1e29..a4a293918df44cedec1b4c5ddc0a4f3ab5587d07 100644 (file)
@@ -36,6 +36,7 @@
 #include "lib/player.h"
 #include "lib/video_content.h"
 #include "lib/video_decoder.h"
+#include "lib/timer.h"
 #include "film_viewer.h"
 #include "wx_util.h"
 
@@ -128,6 +129,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
 
        _player = f->make_player ();
        _player->disable_audio ();
+       _player->set_approximate_size ();
        _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5));
        _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 
@@ -164,7 +166,7 @@ FilmViewer::timer ()
        
        fetch_next_frame ();
 
-       Time const len = _film->length ();
+       DCPTime const len = _film->length ();
 
        if (len) {
                int const new_slider_position = 4096 * _player->video_position() / len;
@@ -211,8 +213,14 @@ void
 FilmViewer::slider_moved ()
 {
        if (_film && _player) {
-               _player->seek (_slider->GetValue() * _film->length() / 4096, false);
-               fetch_next_frame ();
+               try {
+                       _player->seek (_slider->GetValue() * _film->length() / 4096, false);
+                       fetch_next_frame ();
+               } catch (OpenFileError& e) {
+                       /* There was a problem opening a content file; we'll let this slide as it
+                          probably means a missing content file, which we're already taking care of.
+                       */
+               }
        }
 }
 
@@ -251,6 +259,13 @@ FilmViewer::calculate_sizes ()
        _out_size.width = max (64, _out_size.width);
        _out_size.height = max (64, _out_size.height);
 
+       /* The player will round its image down to the nearest 4 pixels
+          to speed up its scale, so do similar here to avoid black borders
+          around things.  This is a bit of a hack.
+       */
+       _out_size.width &= ~3;
+       _out_size.height &= ~3;
+
        _player->set_video_container_size (_out_size);
 }
 
@@ -275,20 +290,24 @@ FilmViewer::check_play_state ()
 }
 
 void
-FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t)
+FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, DCPTime t)
 {
        if (eyes == EYES_RIGHT) {
                return;
        }
-       
-       _frame = image->image ();
+
+       /* Going via BGRA here makes the scaler faster then using RGB24 directly (about
+          twice on x86 Linux).
+       */
+       shared_ptr<Image> im = image->image (PIX_FMT_BGRA, true);
+       _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
        _got_frame = true;
 
        set_position_text (t);
 }
 
 void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text (DCPTime t)
 {
        if (!_film) {
                _frame_number->SetLabel ("0");
@@ -371,13 +390,19 @@ FilmViewer::back_clicked ()
           We want to see the one before it, so we need to go back 2.
        */
 
-       Time p = _player->video_position() - _film->video_frames_to_time (2);
+       DCPTime p = _player->video_position() - _film->video_frames_to_time (2);
        if (p < 0) {
                p = 0;
        }
        
-       _player->seek (p, true);
-       fetch_next_frame ();
+       try {
+               _player->seek (p, true);
+               fetch_next_frame ();
+       } catch (OpenFileError& e) {
+               /* There was a problem opening a content file; we'll let this slide as it
+                  probably means a missing content file, which we're already taking care of.
+               */
+       }
 }
 
 void
index c99c7344047448b088115a9e085bb5eb389937fa..0e5c49c6d8ad91968e439a011ebea6844f7b2472 100644 (file)
@@ -59,7 +59,7 @@ private:
        void slider_moved ();
        void play_clicked ();
        void timer ();
-       void process_video (boost::shared_ptr<PlayerImage>, Eyes, Time);
+       void process_video (boost::shared_ptr<PlayerImage>, Eyes, DCPTime);
        void calculate_sizes ();
        void check_play_state ();
        void fetch_current_frame_again ();
@@ -68,7 +68,7 @@ private:
        void back_clicked ();
        void forward_clicked ();
        void player_changed (bool);
-       void set_position_text (Time);
+       void set_position_text (DCPTime);
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
index 033bd2bd01e97f29965829c8c28c20bd37fa2a3b..493650aae857a0046d152b91bd95b46f8caa5675 100644 (file)
@@ -83,7 +83,7 @@ Timecode::Timecode (wxWindow* parent)
 }
 
 void
-Timecode::set (Time t, int fps)
+Timecode::set (DCPTime t, int fps)
 {
        int const h = t / (3600 * TIME_HZ);
        t -= h * 3600 * TIME_HZ;
@@ -101,10 +101,10 @@ Timecode::set (Time t, int fps)
        _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02d", h, m, s, f));
 }
 
-Time
+DCPTime
 Timecode::get (int fps) const
 {
-       Time t = 0;
+       DCPTime t = 0;
        string const h = wx_to_std (_hours->GetValue ());
        t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
        string const m = wx_to_std (_minutes->GetValue());
index 880b44a31be8754ca54634e542d9e739e328b745..b13e8c3c084b9c35514e805f59e752927fe46ae1 100644 (file)
@@ -26,8 +26,8 @@ class Timecode : public wxPanel
 public:
        Timecode (wxWindow *);
 
-       void set (Time, int);
-       Time get (int) const;
+       void set (DCPTime, int);
+       DCPTime get (int) const;
 
        void set_editable (bool);
 
index 6cc1f79d93dd23c9d0b089648f36edcf1892c893..2119e781322f2cedae4e3083ffd20b7ead4ffda2 100644 (file)
@@ -39,7 +39,7 @@ using boost::optional;
 class View : public boost::noncopyable
 {
 public:
-       View (Timeline& t)
+       View (DCPTimeline& t)
                : _timeline (t)
        {
 
@@ -64,12 +64,12 @@ public:
 protected:
        virtual void do_paint (wxGraphicsContext *) = 0;
        
-       int time_x (Time t) const
+       int time_x (DCPTime t) const
        {
                return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
        }
        
-       Timeline& _timeline;
+       DCPTimeline& _timeline;
 
 private:
        dcpomatic::Rect<int> _last_paint_bbox;
@@ -80,7 +80,7 @@ private:
 class ContentView : public View
 {
 public:
-       ContentView (Timeline& tl, shared_ptr<Content> c)
+       ContentView (DCPTimeline& tl, shared_ptr<Content> c)
                : View (tl)
                , _content (c)
                , _track (0)
@@ -139,8 +139,8 @@ private:
                        return;
                }
 
-               Time const position = cont->position ();
-               Time const len = cont->length_after_trim ();
+               DCPTime const position = cont->position ();
+               DCPTime const len = cont->length_after_trim ();
 
                wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
 
@@ -203,7 +203,7 @@ private:
 class AudioContentView : public ContentView
 {
 public:
-       AudioContentView (Timeline& tl, shared_ptr<Content> c)
+       AudioContentView (DCPTimeline& tl, shared_ptr<Content> c)
                : ContentView (tl, c)
        {}
        
@@ -222,7 +222,7 @@ private:
 class VideoContentView : public ContentView
 {
 public:
-       VideoContentView (Timeline& tl, shared_ptr<Content> c)
+       VideoContentView (DCPTimeline& tl, shared_ptr<Content> c)
                : ContentView (tl, c)
        {}
 
@@ -243,10 +243,10 @@ private:
        }
 };
 
-class TimeAxisView : public View
+class DCPTimeAxisView : public View
 {
 public:
-       TimeAxisView (Timeline& tl, int y)
+       DCPTimeAxisView (DCPTimeline& tl, int y)
                : View (tl)
                , _y (y)
        {}
@@ -291,7 +291,7 @@ private:
                path.AddLineToPoint (_timeline.width(), _y);
                gc->StrokePath (path);
 
-               Time t = 0;
+               DCPTime t = 0;
                while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
                        wxGraphicsPath path = gc->CreatePath ();
                        path.MoveToPoint (time_x (t), _y - 4);
@@ -326,11 +326,11 @@ private:
 };
 
 
-Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
+DCPTimeline::DCPTimeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
        : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
        , _film_editor (ed)
        , _film (film)
-       , _time_axis_view (new TimeAxisView (*this, 32))
+       , _time_axis_view (new DCPTimeAxisView (*this, 32))
        , _tracks (0)
        , _pixels_per_time_unit (0)
        , _left_down (false)
@@ -343,22 +343,22 @@ Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
        SetDoubleBuffered (true);
 #endif 
 
-       Bind (wxEVT_PAINT,      boost::bind (&Timeline::paint,       this));
-       Bind (wxEVT_LEFT_DOWN,  boost::bind (&Timeline::left_down,   this, _1));
-       Bind (wxEVT_LEFT_UP,    boost::bind (&Timeline::left_up,     this, _1));
-       Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down,  this, _1));
-       Bind (wxEVT_MOTION,     boost::bind (&Timeline::mouse_moved, this, _1));
-       Bind (wxEVT_SIZE,       boost::bind (&Timeline::resized,     this));
+       Bind (wxEVT_PAINT,      boost::bind (&DCPTimeline::paint,       this));
+       Bind (wxEVT_LEFT_DOWN,  boost::bind (&DCPTimeline::left_down,   this, _1));
+       Bind (wxEVT_LEFT_UP,    boost::bind (&DCPTimeline::left_up,     this, _1));
+       Bind (wxEVT_RIGHT_DOWN, boost::bind (&DCPTimeline::right_down,  this, _1));
+       Bind (wxEVT_MOTION,     boost::bind (&DCPTimeline::mouse_moved, this, _1));
+       Bind (wxEVT_SIZE,       boost::bind (&DCPTimeline::resized,     this));
 
        playlist_changed ();
 
        SetMinSize (wxSize (640, tracks() * track_height() + 96));
 
-       _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+       _playlist_connection = film->playlist()->Changed.connect (bind (&DCPTimeline::playlist_changed, this));
 }
 
 void
-Timeline::paint ()
+DCPTimeline::paint ()
 {
        wxPaintDC dc (this);
 
@@ -377,7 +377,7 @@ Timeline::paint ()
 }
 
 void
-Timeline::playlist_changed ()
+DCPTimeline::playlist_changed ()
 {
        ensure_ui_thread ();
        
@@ -406,7 +406,7 @@ Timeline::playlist_changed ()
 }
 
 void
-Timeline::assign_tracks ()
+DCPTimeline::assign_tracks ()
 {
        for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
                shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
@@ -465,13 +465,13 @@ Timeline::assign_tracks ()
 }
 
 int
-Timeline::tracks () const
+DCPTimeline::tracks () const
 {
        return _tracks;
 }
 
 void
-Timeline::setup_pixels_per_time_unit ()
+DCPTimeline::setup_pixels_per_time_unit ()
 {
        shared_ptr<const Film> film = _film.lock ();
        if (!film) {
@@ -482,7 +482,7 @@ Timeline::setup_pixels_per_time_unit ()
 }
 
 shared_ptr<View>
-Timeline::event_to_view (wxMouseEvent& ev)
+DCPTimeline::event_to_view (wxMouseEvent& ev)
 {
        ViewList::iterator i = _views.begin();
        Position<int> const p (ev.GetX(), ev.GetY());
@@ -498,7 +498,7 @@ Timeline::event_to_view (wxMouseEvent& ev)
 }
 
 void
-Timeline::left_down (wxMouseEvent& ev)
+DCPTimeline::left_down (wxMouseEvent& ev)
 {
        shared_ptr<View> view = event_to_view (ev);
        shared_ptr<ContentView> content_view = dynamic_pointer_cast<ContentView> (view);
@@ -539,7 +539,7 @@ Timeline::left_down (wxMouseEvent& ev)
 }
 
 void
-Timeline::left_up (wxMouseEvent& ev)
+DCPTimeline::left_up (wxMouseEvent& ev)
 {
        _left_down = false;
 
@@ -551,7 +551,7 @@ Timeline::left_up (wxMouseEvent& ev)
 }
 
 void
-Timeline::mouse_moved (wxMouseEvent& ev)
+DCPTimeline::mouse_moved (wxMouseEvent& ev)
 {
        if (!_left_down) {
                return;
@@ -561,7 +561,7 @@ Timeline::mouse_moved (wxMouseEvent& ev)
 }
 
 void
-Timeline::right_down (wxMouseEvent& ev)
+DCPTimeline::right_down (wxMouseEvent& ev)
 {
        shared_ptr<View> view = event_to_view (ev);
        shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (view);
@@ -578,7 +578,7 @@ Timeline::right_down (wxMouseEvent& ev)
 }
 
 void
-Timeline::set_position_from_event (wxMouseEvent& ev)
+DCPTimeline::set_position_from_event (wxMouseEvent& ev)
 {
        wxPoint const p = ev.GetPosition();
 
@@ -597,13 +597,13 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                return;
        }
        
-       Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
+       DCPTime new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
        
        if (_snap) {
                
                bool first = true;
-               Time nearest_distance = TIME_MAX;
-               Time nearest_new_position = TIME_MAX;
+               DCPTime nearest_distance = TIME_MAX;
+               DCPTime nearest_new_position = TIME_MAX;
                
                /* Find the nearest content edge; this is inefficient */
                for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
@@ -614,7 +614,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                        
                        {
                                /* Snap starts to ends */
-                               Time const d = abs (cv->content()->end() - new_position);
+                               DCPTime const d = abs (cv->content()->end() - new_position);
                                if (first || d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->end();
@@ -623,7 +623,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                        
                        {
                                /* Snap ends to starts */
-                               Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
+                               DCPTime const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
                                if (d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
@@ -653,25 +653,25 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
 }
 
 void
-Timeline::force_redraw (dcpomatic::Rect<int> const & r)
+DCPTimeline::force_redraw (dcpomatic::Rect<int> const & r)
 {
        RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
 }
 
 shared_ptr<const Film>
-Timeline::film () const
+DCPTimeline::film () const
 {
        return _film.lock ();
 }
 
 void
-Timeline::resized ()
+DCPTimeline::resized ()
 {
        setup_pixels_per_time_unit ();
 }
 
 void
-Timeline::clear_selection ()
+DCPTimeline::clear_selection ()
 {
        for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
                shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
@@ -681,8 +681,8 @@ Timeline::clear_selection ()
        }
 }
 
-Timeline::ContentViewList
-Timeline::selected_views () const
+DCPTimeline::ContentViewList
+DCPTimeline::selected_views () const
 {
        ContentViewList sel;
        
@@ -697,7 +697,7 @@ Timeline::selected_views () const
 }
 
 ContentList
-Timeline::selected_content () const
+DCPTimeline::selected_content () const
 {
        ContentList sel;
        ContentViewList views = selected_views ();
index ef1d10797c6e75d114c9d55d7b5d97ff40777279..b3ee77fff84644a14eb2158ab38fd64778b19a6d 100644 (file)
@@ -29,12 +29,12 @@ class Film;
 class View;
 class ContentView;
 class FilmEditor;
-class TimeAxisView;
+class DCPTimeAxisView;
 
-class Timeline : public wxPanel
+class DCPTimeline : public wxPanel
 {
 public:
-       Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>);
+       DCPTimeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>);
 
        boost::shared_ptr<const Film> film () const;
 
@@ -94,13 +94,13 @@ private:
        FilmEditor* _film_editor;
        boost::weak_ptr<Film> _film;
        ViewList _views;
-       boost::shared_ptr<TimeAxisView> _time_axis_view;
+       boost::shared_ptr<DCPTimeAxisView> _time_axis_view;
        int _tracks;
        double _pixels_per_time_unit;
        bool _left_down;
        wxPoint _down_point;
        boost::shared_ptr<ContentView> _down_view;
-       Time _down_view_position;
+       DCPTime _down_view_position;
        bool _first_move;
        ContentMenu _menu;
        bool _snap;
index dbf7ae232be2cd5f72dd8a247e98a80fff968fa7..a63c219dbfb43ca3966cd076653f6a9afd18e44c 100644 (file)
@@ -28,8 +28,8 @@ using std::list;
 using std::cout;
 using boost::shared_ptr;
 
-TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
-       : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+DCPTimelineDialog::DCPTimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
+       : wxDialog (ed, wxID_ANY, _("DCPTimeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
        , _timeline (this, ed, film)
 {
        wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
@@ -46,11 +46,11 @@ TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
        sizer->SetSizeHints (this);
 
        _snap->SetValue (_timeline.snap ());
-       _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&TimelineDialog::snap_toggled, this));
+       _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPTimelineDialog::snap_toggled, this));
 }
 
 void
-TimelineDialog::snap_toggled ()
+DCPTimelineDialog::snap_toggled ()
 {
        _timeline.set_snap (_snap->GetValue ());
 }
index 1e595500303b5b964b0ba54157c67d47f260625b..b64445d9cf88447a8b5638f1d579641864700879 100644 (file)
 
 class Playlist;
 
-class TimelineDialog : public wxDialog
+class DCPTimelineDialog : public wxDialog
 {
 public:
-       TimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
+       DCPTimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
 
 private:
        void snap_toggled ();
        
-       Timeline _timeline;
+       DCPTimeline _timeline;
        wxCheckBox* _snap;
 };
index 8854f7e7b54441c5c2689b7b157d4f71c4089384..b439d13c4e47412f1950283ae9beb584942ef85f 100644 (file)
@@ -334,7 +334,7 @@ VideoPanel::setup_description ()
 
        d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
        ++lines;
-       FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+       FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
        d << std_to_wx (frc.description) << "\n";
        ++lines;
 
index 65c29325c348178b2fd4febc13fa8ac7f01044ff..bba05ae9bef5a4db7ffbdb52f0d7860f153fd225 100644 (file)
 
 */
 
+/** @file  test/ffmpeg_audio_test.cc
+ *  @brief A simple test of reading audio from an FFmpeg file.
+ */
+
 #include <boost/test/unit_test.hpp>
 #include <libdcp/cpl.h>
 #include <libdcp/dcp.h>
index 6caf0d07a2a9d2cf9277afa69f5320b774ce24ae..53303f1543bcda650274f7d2736a1668a7691af8 100644 (file)
@@ -38,7 +38,6 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                content->_audio_stream->first_audio = 0;
                FFmpegDecoder decoder (film, content, true, true);
                BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
        }
 
        {
@@ -47,7 +46,6 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                content->_audio_stream->first_audio = 600;
                FFmpegDecoder decoder (film, content, true, true);
                BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
        }
 
        {
@@ -56,7 +54,6 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                content->_audio_stream->first_audio = 0;
                FFmpegDecoder decoder (film, content, true, true);
                BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
        }
 
        {
@@ -66,7 +63,6 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                content->_audio_stream->first_audio = 0;
                FFmpegDecoder decoder (film, content, true, true);
                BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
-               BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
        }
 
        {
@@ -76,6 +72,5 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                content->_audio_stream->first_audio = 4.1;
                FFmpegDecoder decoder (film, content, true, true);
                BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
        }
 }
diff --git a/test/ffmpeg_seek_test.cc b/test/ffmpeg_seek_test.cc
new file mode 100644 (file)
index 0000000..3c175f0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file  test/ffmpeg_seek_test.cc
+ *  @brief Test seek using Player with an FFmpegDecoder; note that the player
+ *  can hide problems with FFmpegDecoder seeking as it will skip frames / insert
+ *  black as it sees fit.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+using boost::optional;
+
+#define FFMPEG_SEEK_TEST_DEBUG 1
+
+optional<DCPTime> first_video;
+optional<DCPTime> first_audio;
+shared_ptr<Film> film;
+
+static void
+process_video (shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime t)
+{
+       if (!first_video) {
+               first_video = t;
+       }
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers>, DCPTime t)
+{
+       if (!first_audio) {
+               first_audio = t;
+       }
+}
+
+static string
+print_time (DCPTime t, float fps)
+{
+       stringstream s;
+       s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f";
+       return s.str ();
+}
+
+static void
+check (shared_ptr<Player> p, DCPTime t)
+{
+       first_video.reset ();
+       first_audio.reset ();
+
+#if FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "\n-- Seek to " << print_time (t, 24) << "\n";
+#endif 
+       
+       p->seek (t, true);
+       while (!first_video || !first_audio) {
+               p->pass ();
+       }
+
+#if FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "First video " << print_time (first_video.get(), 24) << "\n";
+       cout << "First audio " << print_time (first_audio.get(), 24) << "\n";
+#endif 
+
+       /* Outputs should be on or after seek time */
+       BOOST_CHECK (first_video.get() >= t);
+       BOOST_CHECK (first_audio.get() >= t);
+       /* And should be rounded to frame boundaries */
+       BOOST_CHECK ((first_video.get() % (TIME_HZ / film->video_frame_rate())) == 0);
+       BOOST_CHECK ((first_audio.get() % (TIME_HZ / film->audio_frame_rate())) == 0);
+}
+
+/* Test basic seeking */
+BOOST_AUTO_TEST_CASE (ffmpeg_seek_test)
+{
+       film = new_test_film ("ffmpeg_seek_test");
+       film->set_name ("ffmpeg_seek_test");
+       film->set_container (Ratio::from_id ("185"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/staircase.mov"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       shared_ptr<Player> player = film->make_player ();
+       player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5));
+       player->Audio.connect (boost::bind (&process_audio, _1, _2));
+
+       check (player, 0);
+       check (player, 0.1 * TIME_HZ);
+       check (player, 0.2 * TIME_HZ);
+       check (player, 0.3 * TIME_HZ);
+}
index fdfdcf4529739f126ffab9da192aafeec44f8503..2135b3738f50a669b39b2678a51bd404eb755a2f 100644 (file)
@@ -47,91 +47,102 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 60;
        int best = film->playlist()->best_dcp_frame_rate ();
-       FrameRateConversion frc = FrameRateConversion (60, best);
+       FrameRateChange frc = FrameRateChange (60, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (50, best);
+       frc = FrameRateChange (50, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (48, best);
+       frc = FrameRateChange (48, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 30;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (30, best);
+       frc = FrameRateChange (30, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 29.97;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (29.97, best);
+       frc = FrameRateChange (29.97, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 30 / 29.97, 0.1);
        
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (25, best);
+       frc = FrameRateChange (25, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 24;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (24, best);
+       frc = FrameRateChange (24, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 14.5;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (14.5, best);
+       frc = FrameRateChange (14.5, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 15 / 14.5, 0.1);
 
        content->_video_frame_rate = 12.6;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12.6, best);
+       frc = FrameRateChange (12.6, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 25 / 25.2, 0.1);
 
        content->_video_frame_rate = 12.4;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12.4, best);
+       frc = FrameRateChange (12.4, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 25 / 24.8, 0.1);
 
        content->_video_frame_rate = 12;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12, best);
+       frc = FrameRateChange (12, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        /* Now add some more rates and see if it will use them
           in preference to skip/repeat.
@@ -144,34 +155,38 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 60;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (60, best);
+       frc = FrameRateChange (60, best);
        BOOST_CHECK_EQUAL (best, 60);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (50, best);
+       frc = FrameRateChange (50, best);
        BOOST_CHECK_EQUAL (best, 50);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (48, best);
+       frc = FrameRateChange (48, best);
        BOOST_CHECK_EQUAL (best, 48);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        /* Check some out-there conversions (not the best) */
        
-       frc = FrameRateConversion (14.99, 24);
+       frc = FrameRateChange (14.99, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 24 / (2 * 14.99), 0.1);
 
        /* Check some conversions with limited DCP targets */
 
@@ -181,14 +196,15 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (25, best);
+       frc = FrameRateChange (25, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 24.0 / 25, 0.1);
 }
 
-/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+/* Test Playlist::best_dcp_frame_rate and FrameRateChange
    with two pieces of content.
 */
 BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
@@ -266,7 +282,7 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
        content->_video_frame_rate = 14.99;
        film->set_video_frame_rate (25);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
-       /* The FrameRateConversion within output_audio_frame_rate should choose to double-up
+       /* The FrameRateChange within output_audio_frame_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
        BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
diff --git a/test/long_ffmpeg_seek_test.cc b/test/long_ffmpeg_seek_test.cc
new file mode 100644 (file)
index 0000000..5285d44
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+#define LONG_FFMPEG_SEEK_TEST_DEBUG 1
+
+boost::optional<DCPTime> first_video;
+boost::optional<DCPTime> first_audio;
+
+static void
+process_video (shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime t)
+{
+       if (!first_video) {
+               first_video = t;
+       }
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers>, DCPTime t)
+{
+       if (!first_audio) {
+               first_audio = t;
+       }
+}
+
+static string
+print_time (DCPTime t, float fps)
+{
+       stringstream s;
+       s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f";
+       return s.str ();
+}
+
+static void
+check (shared_ptr<Player> p, DCPTime t)
+{
+       first_video.reset ();
+       first_audio.reset ();
+
+#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "\n-- Seek to " << print_time (t, 24) << "\n";
+#endif 
+       
+       p->seek (t, true);
+       while (!first_video || !first_audio) {
+               p->pass ();
+       }
+
+#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "First video " << print_time (first_video.get(), 24) << "\n";
+       cout << "First audio " << print_time (first_audio.get(), 24) << "\n";
+#endif 
+       
+       BOOST_CHECK (first_video.get() >= t);
+       BOOST_CHECK (first_audio.get() >= t);
+}
+
+BOOST_AUTO_TEST_CASE (long_ffmpeg_seek_test)
+{
+       shared_ptr<Film> film = new_test_film ("long_ffmpeg_audio_test");
+       film->set_name ("long_ffmpeg_audio_test");
+       film->set_container (Ratio::from_id ("185"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/long_data/dolby_aurora.vob"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       shared_ptr<Player> player = film->make_player ();
+       player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5));
+       player->Audio.connect (boost::bind (&process_audio, _1, _2));
+
+       for (float i = 0; i < 10; i += 0.1) {
+               check (player, i * TIME_HZ);
+       }
+}
+
+
index dfa597431fee6b818d42e195f3676af6bf6229e8..54fe2699f7fd47b19ad9ac666a70857e309d4567 100644 (file)
@@ -34,7 +34,7 @@ struct Video
 {
        boost::shared_ptr<Content> content;
        boost::shared_ptr<const Image> image;
-       Time time;
+       DCPTime time;
 };
 
 class PlayerWrapper
@@ -46,11 +46,11 @@ public:
                _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _5));
        }
 
-       void process_video (shared_ptr<PlayerImage> i, bool, Time t)
+       void process_video (shared_ptr<PlayerImage> i, bool, DCPTime t)
        {
                Video v;
                v.content = _player->_last_video;
-               v.image = i->image ();
+               v.image = i->image (PIX_FMT_RGB24, false);
                v.time = t;
                _queue.push_front (v);
        }
@@ -67,7 +67,7 @@ public:
                return v;
        }
 
-       void seek (Time t, bool ac)
+       void seek (DCPTime t, bool ac)
        {
                _player->seek (t, ac);
                _queue.clear ();
@@ -106,8 +106,6 @@ BOOST_AUTO_TEST_CASE (play_test)
 
        shared_ptr<Player> player = film->make_player ();
        PlayerWrapper wrap (player);
-       /* Seek and audio don't get on at the moment */
-       player->disable_audio ();
 
        for (int i = 0; i < 32; ++i) {
                optional<Video> v = wrap.get_video ();
@@ -119,10 +117,10 @@ BOOST_AUTO_TEST_CASE (play_test)
                }
        }
 
-       player->seek (10 * TIME_HZ / 25, true);
+       player->seek (6 * TIME_HZ / 25, true);
        optional<Video> v = wrap.get_video ();
        BOOST_CHECK (v);
-       BOOST_CHECK_EQUAL (v.get().time, 10 * TIME_HZ / 25);
+       BOOST_CHECK_EQUAL (v.get().time, 6 * TIME_HZ / 25);
 }
 
 #endif
diff --git a/test/repeat_frame_test.cc b/test/repeat_frame_test.cc
new file mode 100644 (file)
index 0000000..73b9ad6
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+/* Test the repeat of frames by the player when putting a 24fps
+   source into a 48fps DCP.
+*/
+BOOST_AUTO_TEST_CASE (repeat_frame_test)
+{
+       shared_ptr<Film> film = new_test_film ("repeat_frame_test");
+       film->set_name ("repeat_frame_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/red_24.mp4"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       film->set_video_frame_rate (48);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       check_dcp ("test/data/repeat_frame_test", film->dir (film->dcp_name ()));
+}
+
index 1ef69b0c26bbb71f0851b15808c0ce1482ad0d9b..5bee3603b2e8c71df95ab07cf5681a9dd8478a25 100644 (file)
@@ -34,13 +34,14 @@ resampler_test_one (int from, int to)
 
        /* 3 hours */
        int64_t const N = from * 60 * 60 * 3;
+
+       /* XXX: no longer checks anything */
        
        for (int64_t i = 0; i < N; i += 1000) {
                shared_ptr<AudioBuffers> a (new AudioBuffers (1, 1000));
                a->make_silent ();
-               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> r = resamp.run (a, i);
-               BOOST_CHECK_EQUAL (r.second, total_out);
-               total_out += r.first->frames ();
+               shared_ptr<const AudioBuffers> r = resamp.run (a);
+               total_out += r->frames ();
        }
 }      
                
diff --git a/test/seek_zero_test.cc b/test/seek_zero_test.cc
new file mode 100644 (file)
index 0000000..c20c99e
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file  test/seek_zero_test.cc
+ *  @brief Test seek to zero with a raw FFmpegDecoder (without the player
+ *  confusing things as it might in ffmpeg_seek_test).
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ffmpeg_decoder.h"
+#include "test.h"
+
+using std::cout;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+BOOST_AUTO_TEST_CASE (seek_zero_test)
+{
+       shared_ptr<Film> film = new_test_film ("seek_zero_test");
+       film->set_name ("seek_zero_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+       content->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (content);
+       wait_for_jobs ();
+
+       FFmpegDecoder decoder (film, content, true, false);
+       shared_ptr<DecodedVideo> a = dynamic_pointer_cast<DecodedVideo> (decoder.peek ());
+       decoder.seek (0, true);
+       shared_ptr<DecodedVideo> b = dynamic_pointer_cast<DecodedVideo> (decoder.peek ());
+
+       /* a will be after no seek, and b after a seek to zero, which should
+          have the same effect.
+       */
+       BOOST_CHECK_EQUAL (a->frame, b->frame);
+}
diff --git a/test/skip_frame_test.cc b/test/skip_frame_test.cc
new file mode 100644 (file)
index 0000000..4ac5192
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+/* Test the skip of frames by the player when putting a 48fps
+   source into a 24fps DCP.
+*/
+BOOST_AUTO_TEST_CASE (skip_frame_test)
+{
+       shared_ptr<Film> film = new_test_film ("skip_frame_test");
+       film->set_name ("skip_frame_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+       film->write_metadata ();
+
+       film->set_video_frame_rate (24);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       check_dcp ("test/data/skip_frame_test", film->dir (film->dcp_name ()));
+}
+
diff --git a/test/subrip_test.cc b/test/subrip_test.cc
new file mode 100644 (file)
index 0000000..12a77c1
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include <libdcp/subtitle_asset.h>
+#include "lib/subrip.h"
+#include "lib/subrip_content.h"
+#include "lib/subrip_decoder.h"
+#include "lib/render_subtitles.h"
+#include "test.h"
+
+using std::list;
+using std::vector;
+using std::string;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+/** Test SubRip::convert_time */
+BOOST_AUTO_TEST_CASE (subrip_time_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), rint (((3 * 60) + 10 + 0.5) * TIME_HZ));
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), rint (((4 * 3600) + (19 * 60) + 51 + 0.782) * TIME_HZ));
+}
+
+/** Test SubRip::convert_coordinate */
+BOOST_AUTO_TEST_CASE (subrip_coordinate_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("foo:42"), 42);
+       BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("X1:999"), 999);
+}
+
+/** Test SubRip::convert_content */
+BOOST_AUTO_TEST_CASE (subrip_content_test)
+{
+       list<string> c;
+       list<SubRipSubtitlePiece> p;
+       
+       c.push_back ("Hello world");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       c.clear ();
+
+       c.push_back ("<b>Hello world</b>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().bold, true);
+       c.clear ();
+
+       c.push_back ("<i>Hello world</i>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().italic, true);
+       c.clear ();
+
+       c.push_back ("<u>Hello world</u>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().underline, true);
+       c.clear ();
+
+       c.push_back ("{b}Hello world{/b}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().bold, true);
+       c.clear ();
+
+       c.push_back ("{i}Hello world{/i}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().italic, true);
+       c.clear ();
+
+       c.push_back ("{u}Hello world{/u}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().underline, true);
+       c.clear ();
+
+       c.push_back ("<b>This is <i>nesting</i> of subtitles</b>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 3);
+       list<SubRipSubtitlePiece>::iterator i = p.begin ();     
+       BOOST_CHECK_EQUAL (i->text, "This is ");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, false);
+       ++i;
+       BOOST_CHECK_EQUAL (i->text, "nesting");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, true);
+       ++i;
+       BOOST_CHECK_EQUAL (i->text, " of subtitles");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, false);
+       ++i;
+       c.clear ();
+}
+
+/** Test parsing of full SubRip file content */
+BOOST_AUTO_TEST_CASE (subrip_parse_test)
+{
+       shared_ptr<SubRipContent> content (new SubRipContent (shared_ptr<Film> (), "test/data/subrip.srt"));
+       content->examine (shared_ptr<Job> ());
+       BOOST_CHECK_EQUAL (content->full_length(), ((3 * 60) + 56.471) * TIME_HZ);
+
+       SubRip s (content);
+
+       vector<SubRipSubtitle>::const_iterator i = s._subtitles.begin();
+
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 49.200) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 52.351) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "This is a subtitle, and it goes over two lines.");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 52.440) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 54.351) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "We have emboldened this");
+       BOOST_CHECK_EQUAL (i->pieces.front().bold, true);
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 54.440) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 56.590) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "And italicised this.");
+       BOOST_CHECK_EQUAL (i->pieces.front().italic, true);
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 56.680) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 58.955) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Shall I compare thee to a summers' day?");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ((2 * 60) + 0.840) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->to, ((2 * 60) + 3.400) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Is this a dagger I see before me?");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ((3 * 60) + 54.560) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->to, ((3 * 60) + 56.471) * TIME_HZ);
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Hello world.");
+
+       ++i;
+       BOOST_CHECK (i == s._subtitles.end ());
+}
+
+/** Test rendering of a SubRip subtitle */
+BOOST_AUTO_TEST_CASE (subrip_render_test)
+{
+       shared_ptr<SubRipContent> content (new SubRipContent (shared_ptr<Film> (), "test/data/subrip.srt"));
+       content->examine (shared_ptr<Job> ());
+       BOOST_CHECK_EQUAL (content->full_length(), ((3 * 60) + 56.471) * TIME_HZ);
+
+       shared_ptr<Film> film = new_test_film ("subrip_render_test");
+
+       shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (film, content));
+       shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (decoder->peek ());
+
+       shared_ptr<Image> image;
+       Position<int> position;
+       render_subtitles (dts->subs, libdcp::Size (1998, 1080), image, position);
+       write_image (image, "build/test/subrip_render_test.png");
+       check_file ("build/test/subrip_render_test.png", "test/data/subrip_render_test.png");
+}
index be2cf15389949c9ead1d23f5804b65c1c50ab99e..e43db71ef9c3ded8b833f3e94c289cc7be2b05fc 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <vector>
 #include <list>
+#include <Magick++.h>
 #include <libxml++/libxml++.h>
 #include <libdcp/dcp.h>
 #include "lib/config.h"
@@ -29,6 +30,7 @@
 #include "lib/job.h"
 #include "lib/cross.h"
 #include "lib/server_finder.h"
+#include "lib/image.h"
 #define BOOST_TEST_DYN_LINK
 #define BOOST_TEST_MODULE dcpomatic_test
 #include <boost/test/unit_test.hpp>
@@ -53,15 +55,16 @@ public:
 
 struct TestConfig
 {
-       TestConfig()
+       TestConfig ()
        {
-               dcpomatic_setup();
+               dcpomatic_setup ();
 
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
                Config::instance()->set_default_dci_metadata (DCIMetadata ());
                Config::instance()->set_default_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
+               Config::instance()->set_default_audio_delay (0);
 
                ServerFinder::instance()->disable ();
 
@@ -98,8 +101,8 @@ void
 check_file (boost::filesystem::path ref, boost::filesystem::path check)
 {
        uintmax_t N = boost::filesystem::file_size (ref);
-       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
-       FILE* ref_file = fopen_boost (ref, "rb");
+       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
+       FILE* ref_file = fopen (ref.c_str(), "rb");
        BOOST_CHECK (ref_file);
        FILE* check_file = fopen_boost (check, "rb");
        BOOST_CHECK (check_file);
@@ -135,7 +138,7 @@ note (libdcp::NoteType t, string n)
 }
 
 void
-check_dcp (string ref, string check)
+check_dcp (boost::filesystem::path ref, boost::filesystem::path check)
 {
        libdcp::DCP ref_dcp (ref);
        ref_dcp.read ();
@@ -230,3 +233,12 @@ wait_for_jobs ()
 
        ui_signaller->ui_idle ();
 }
+
+void
+write_image (shared_ptr<const Image> image, boost::filesystem::path file)
+{
+       using namespace MagickCore;
+
+       Magick::Image m (image->size().width, image->size().height, "ARGB", CharPixel, (void *) image->data()[0]);
+       m.write (file.string ());
+}
index dd007e8c9a15033e0db8112b4f3856bb5582c3e4..a42b41577917aa946c39f4aa6467a632ea449cba 100644 (file)
 #include <boost/filesystem.hpp>
 
 class Film;
+class Image;
 
 extern void wait_for_jobs ();
 extern boost::shared_ptr<Film> new_test_film (std::string);
-extern void check_dcp (std::string, std::string);
+extern void check_dcp (boost::filesystem::path, boost::filesystem::path);
+extern void check_file (boost::filesystem::path ref, boost::filesystem::path check);
 extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list<std::string>);
 extern void check_file (boost::filesystem::path, boost::filesystem::path);
 extern boost::filesystem::path test_film_dir (std::string);
+extern void write_image (boost::shared_ptr<const Image> image, boost::filesystem::path file);
index 4dccb49c6c1d95302fb031794b90d1ba97217357..5733c7d03d69070b2fa195e43665c1b337f146c7 100644 (file)
@@ -53,3 +53,17 @@ BOOST_AUTO_TEST_CASE (md5_digest_test)
        p.push_back ("foobar");
        BOOST_CHECK_THROW (md5_digest (p, shared_ptr<Job> ()), std::runtime_error);
 }
+
+/* Straightforward test of time_round_up_test */
+BOOST_AUTO_TEST_CASE (time_round_up_test)
+{
+       BOOST_CHECK_EQUAL (time_round_up (0, 2), 0);
+       BOOST_CHECK_EQUAL (time_round_up (1, 2), 2);
+       BOOST_CHECK_EQUAL (time_round_up (2, 2), 2);
+       BOOST_CHECK_EQUAL (time_round_up (3, 2), 4);
+       
+       BOOST_CHECK_EQUAL (time_round_up (0, 42), 0);
+       BOOST_CHECK_EQUAL (time_round_up (1, 42), 42);
+       BOOST_CHECK_EQUAL (time_round_up (42, 42), 42);
+       BOOST_CHECK_EQUAL (time_round_up (43, 42), 84);
+}
index c07f2cc335747a8c092c324d691a1fa34b826554..24daa7762001dc3d0ee764d58030cd4f6625a180 100644 (file)
@@ -10,7 +10,7 @@ def configure(conf):
                               """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
 
 def build(bld):
-    obj = bld(features = 'cxx cxxprogram')
+    obj = bld(features='cxx cxxprogram')
     obj.name   = 'unit-tests'
     obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
     obj.use    = 'libdcpomatic'
@@ -27,6 +27,7 @@ def build(bld):
                  ffmpeg_dcp_test.cc
                  ffmpeg_examiner_test.cc
                  ffmpeg_pts_offset.cc
+                 ffmpeg_seek_test.cc
                  file_group_test.cc
                  film_metadata_test.cc
                  frame_rate_test.cc
@@ -36,11 +37,15 @@ def build(bld):
                  pixel_formats_test.cc
                  play_test.cc
                  ratio_test.cc
+                 repeat_frame_test.cc
                  recover_test.cc
                  resampler_test.cc
                  scaling_test.cc
+                 seek_zero_test.cc
                  silence_padding_test.cc
+                 skip_frame_test.cc
                  stream_test.cc
+                 subrip_test.cc
                  test.cc
                  threed_test.cc
                  util_test.cc
@@ -48,3 +53,15 @@ def build(bld):
 
     obj.target = 'unit-tests'
     obj.install_path = ''
+
+    obj = bld(features='cxx cxxprogram')
+    obj.name   = 'long-unit-tests'
+    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+    obj.use    = 'libdcpomatic'
+    obj.source = """
+                 test.cc
+                 long_ffmpeg_seek_test.cc
+                 """
+
+    obj.target = 'long-unit-tests'
+    obj.install_path = ''
diff --git a/wscript b/wscript
index 1bf81bfb4214fed76153f18b2c4537c0a47e7e0d..6c97047fcdf763ef7fceb72e424928d692801159 100644 (file)
--- a/wscript
+++ b/wscript
@@ -145,6 +145,8 @@ def configure(conf):
     conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
     conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
     conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
+    conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
+    conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
 
     conf.check_cxx(fragment="""
                             #include <boost/version.hpp>\n