Untested merge of master.
authorCarl Hetherington <cth@carlh.net>
Sun, 21 Apr 2013 21:41:52 +0000 (22:41 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 21 Apr 2013 21:41:52 +0000 (22:41 +0100)
41 files changed:
ChangeLog
cscript
debian/changelog
debian/rules
src/lib/ab_transcoder.cc
src/lib/audio_decoder.h
src/lib/audio_sink.h
src/lib/audio_source.cc
src/lib/audio_source.h
src/lib/combiner.cc
src/lib/combiner.h
src/lib/decoder.h
src/lib/delay_line.cc
src/lib/delay_line.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/film.cc
src/lib/format.cc
src/lib/format.h
src/lib/imagemagick_decoder.cc
src/lib/imagemagick_decoder.h
src/lib/matcher.cc
src/lib/matcher.h
src/lib/player.cc
src/lib/player.h
src/lib/processor.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/transcoder.cc
src/lib/util.cc
src/lib/util.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_sink.h
src/lib/video_source.cc
src/lib/video_source.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
test/test.cc
wscript

index 3499d97ed1fb42c94b6faf5b2d6162e6f9624116..ffbbcfb2d7554b82d9c009560b49ae7b59f91b82 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,45 @@
+2013-04-21  Carl Hetherington  <cth@carlh.net>
+
+       * Version 0.84 released.
+
+2013-04-21  Carl Hetherington  <cth@carlh.net>
+
+       * Version 0.84beta5 released.
+
+2013-04-20  Carl Hetherington  <cth@carlh.net>
+
+       * Fix bad saving of metadata in locales which use
+       commas to separate decimals (#119).
+
+2013-04-19  Carl Hetherington  <cth@carlh.net>
+
+       * Add basic frame index and timecode to viewer, and previous/next
+       frame buttons.
+
+       * Version 0.84beta4 released.
+
+2013-04-19  Carl Hetherington  <cth@carlh.net>
+
+       * Version 0.84beta3 released.
+
+2013-04-19  Carl Hetherington  <cth@carlh.net>
+
+       * Version 0.84beta2 released.
+
+2013-04-18  Carl Hetherington  <cth@carlh.net>
+
+       * Version 0.84beta1 released.
+
 2013-04-15  Carl Hetherington  <cth@carlh.net>
 
        * Fix error message on forcing language to English (#103).
 
+       * Fix problems with content whose first audio content
+       comes before the first video (resulting in audio being
+       chopped off at the start of the DCP) (#79).
+
+       * Use true 4:3 rather than 1.33.
+
 2013-04-13  Carl Hetherington  <cth@carlh.net>
 
        * Use film-name-derived names for MXFs in DCPs (#54).
 2012-12-18  Carl Hetherington  <cth@carlh.net>
 
        * Version 0.65 released.
->>>>>>> master
 
 2012-12-13  Carl Hetherington  <cth@carlh.net>
 
diff --git a/cscript b/cscript
index 276e4bc402edf48feab791533d6fe02d4350ffec..d985838fc876cd742cdc3c5e87e87eed3cbde346 100644 (file)
--- a/cscript
+++ b/cscript
@@ -1,49 +1,69 @@
 import glob
+import shutil
+import os
 
-release_targets = ['ubuntu-12.04-32', 'ubuntu-12.10-32', 'ubuntu-12.04-64', 'ubuntu-12.10-64', 'source', 'windows-32', 'windows-64']
-
-def build_release(environment, variant, version):
-    if environment == 'windows':
-        command('./waf configure --target-windows')
-        command('./waf clean')
-        command('./waf')
-        shutil.copyfile('build/windows/installer.%s.nsi' % variant, 'build/windows/installer2.%s.nsi' % variant)
-        command('sed -i "s~%%resources%%~%s/windows~g" build/windows/installer2.%s.nsi' % (os.getcwd(), variant))
-        command('sed -i "s~%%deps%%~$WINDOWS_PREFIX~g" build/windows/installer2.%s.nsi' % variant)
-        command('sed -i "s~%%binaries%%~%s/build~g" build/windows/installer2.%s.nsi' % (os.getcwd(), variant))
-        command('sed -i "s~%%bits%%~32~g" build/windows/installer2.%s.nsi' % variant)
-        command('makensis build/windows/installer2.%s.nsi' % variant)
-        return glob.glob('build/windows/*%s*.exe' % variant)[0]
-    elif environment == 'ubuntu':
-        v = variant.split('-')
-        bits = v[1]
-        if bits == '32':
+def dependencies(target):
+    if target.platform == 'windows':
+        return ()
+    else:
+        return (('openjpeg-cdist', None),
+                ('ffmpeg-cdist', '488d5d4496af5e3a3b9d31d6b221e8eeada6b77e'),
+                ('libdcp', 'v0.45'))
+
+def build(env, target):
+    cmd = './waf configure --prefix=%s' % env.work_dir_cscript()
+    if target.platform == 'windows':
+        cmd += ' --target-windows'
+    else:
+        cmd += ' --static'
+    env.command(cmd)
+
+    env.command('./waf')
+
+    if target.platform == 'linux':
+        env.command('./waf install')
+
+
+def package(env, target, version):
+    if target.platform == 'windows':
+        shutil.copyfile('build/windows/installer.%s.nsi' % target.bits, 'build/windows/installer2.%s.nsi' % target.bits)
+        env.command('sed -i "s~%%resources%%~%s/windows~g" build/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
+        env.command('sed -i "s~%%deps%%~%s~g" build/windows/installer2.%s.nsi' % (env.windows_prefix(), target.bits))
+        env.command('sed -i "s~%%binaries%%~%s/build~g" build/windows/installer2.%s.nsi' % (os.getcwd(), target.bits))
+        env.command('sed -i "s~%%bits%%~32~g" build/windows/installer2.%s.nsi' % target.bits)
+        env.command('makensis build/windows/installer2.%s.nsi' % target.bits)
+        return os.path.abspath(glob.glob('build/windows/*%s*.exe' % target.bits)[0])
+    elif target.platform == 'linux':
+        if target.bits == 32:
             cpu = 'i386'
         else:
             cpu = 'amd64'
 
-        shutil.copyfile(os.path.join('builds', 'control-%s' % variant), os.path.join('debian', 'control'))
-        command('./waf dist')
-        f = open(os.path.join('debian', 'files'), 'w')
-        print >>f,'dcpomatic_%s-1_%s.deb video extra' % (version, cpu)
+        shutil.copyfile('builds/control-%s-%d' % (target.version, target.bits), 'debian/control')
+        env.command('./waf dist')
+        f = open('debian/files', 'w')
+        print >>f,'dvdomatic_%s-1_%s.deb video extra' % (version, cpu)
         shutil.rmtree('build/deb', ignore_errors=True)
+
         os.makedirs('build/deb')
         os.chdir('build/deb')
-        shutil.move('../../dcpomatic-%s.tar.bz2' % version, 'dcpomatic_%s.orig.tar.bz2' % version)
-        command('tar xjf dcpomatic_%s.orig.tar.bz2' % version)
-        os.chdir('dcpomatic-%s' % version)
-        command('dch -b -v %s-1 "New upstream release."' % version)
-        command('dpkg-source -b .')
-        command('dpkg-buildpackage')
+        shutil.move('../../dvdomatic-%s.tar.bz2' % version, 'dvdomatic_%s.orig.tar.bz2' % version)
+        env.command('tar xjf dvdomatic_%s.orig.tar.bz2' % version)
+        os.chdir('dvdomatic-%s' % version)
+        env.command('dch -b -v %s-1 "New upstream release."' % version)
+        env.set('CDIST_LINKFLAGS', env.get('LINKFLAGS'))
+        env.set('CDIST_CXXFLAGS', env.get('CXXFLAGS'))
+        env.set('CDIST_PKG_CONFIG_PATH', env.get('PKG_CONFIG_PATH'))
+        env.command('dpkg-buildpackage')
         return os.path.abspath(glob.glob('../*.deb')[0])
 
-def make_pot():
-    command('./waf pot')
-    return [os.path.abspath('build/src/lib/libdcpomatic.pot'),
-            os.path.abspath('build/src/wx/libdcpomatic-wx.pot'),
-           os.path.abspath('build/src/tools/dcpomatic.pot')]
+def make_pot(env):
+    env.command('./waf pot')
+    return [os.path.abspath('build/src/lib/libdvdomatic.pot'),
+            os.path.abspath('build/src/wx/libdvdomatic-wx.pot'),
+           os.path.abspath('build/src/tools/dvdomatic.pot')]
 
-def make_manual():
+def make_manual(env):
     os.chdir('doc/manual')
-    command('make')
+    env.command('make')
     return [os.path.abspath('pdf'), os.path.abspath('html')]
index 0a53eff154451ca9b659f31aa5a91be933cadb00..a0b068d5590d3bd4d97f9f006bbdbb88b6612dc1 100644 (file)
@@ -1,4 +1,40 @@
-dcpomatic (0.83-1) UNRELEASED; urgency=low
+dvdomatic (0.84-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 21 Apr 2013 17:49:54 +0100
+
+dvdomatic (0.84beta5-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Sun, 21 Apr 2013 00:06:12 +0100
+
+dvdomatic (0.84beta4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 17:41:58 +0100
+
+dvdomatic (0.84beta3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 11:36:37 +0100
+
+dvdomatic (0.84beta2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Fri, 19 Apr 2013 11:12:09 +0100
+
+dvdomatic (0.84beta1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan>  Thu, 18 Apr 2013 23:32:17 +0100
+
+dvdomatic (0.83-1) UNRELEASED; urgency=low
 
   * New upstream release.
 
index ba42e5291fabb5689821c7eee5ba168a9a964063..7f7da5530eddbb8d128f2ba9035454482dbe7844 100755 (executable)
@@ -13,7 +13,8 @@
        dh $@ 
 
 override_dh_auto_configure:
-       ./waf --nocache configure --prefix=/usr --static
+       LINKFLAGS=$(CDIST_LINKFLAGS) CXXFLAGS="$(CXXFLAGS) $(CDIST_CXXFLAGS)" PKG_CONFIG_PATH=$(CDIST_PKG_CONFIG_PATH) \
+                ./waf --nocache configure --prefix=/usr --static
 
 override_dh_auto_build:
        ./waf --nocache build
index 81aa5a4bae2192d5622689f32f57ee9f7724d2ae..c6ccfdc67e268492a9cae8eca5de06f53c7b3daf 100644 (file)
@@ -55,28 +55,21 @@ ABTranscoder::ABTranscoder (shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<J
        , _encoder (new Encoder (_film_a))
        , _combiner (new Combiner (a->log()))
 {
-       if (_film_a->has_audio ()) {
-               _matcher.reset (new Matcher (_film_a->log(), _film_a->audio_frame_rate(), _film_a->video_frame_rate()));
-               _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_channels(), _film_a->audio_delay() * _film_a->audio_frame_rate() / 1000));
-               _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
-       }
+       _matcher.reset (new Matcher (_film_a->log(), _film_a->audio_frame_rate(), _film_a->video_frame_rate()));
+       _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() * _film_a->audio_frame_rate() / 1000));
+       _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
 
-       _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
-       _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
+       _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4));
+       _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
 
-       if (_matcher) {
-               _combiner->connect_video (_matcher);
-               _matcher->connect_video (_encoder);
-       } else {
-               _combiner->connect_video (_encoder);
-       }
+       _combiner->connect_video (_delay_line);
+       _delay_line->connect_video (_matcher);
+       _matcher->connect_video (_encoder);
        
-       if (_matcher && _delay_line) {
-               _player_a->connect_audio (_delay_line);
-               _delay_line->connect_audio (_matcher);
-               _matcher->connect_audio (_gain);
-               _gain->connect_audio (_encoder);
-       }
+       _player_a->connect_audio (_delay_line);
+       _delay_line->connect_audio (_matcher);
+       _matcher->connect_audio (_gain);
+       _gain->connect_audio (_encoder);
 }
 
 void
index 418fc6da2b117deb53790bbf890a10ba922ed164..c393e95f12b776f3b93172a4539159c6b78fa8a2 100644 (file)
@@ -32,7 +32,7 @@ class AudioContent;
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
-class AudioDecoder : public AudioSource, public virtual Decoder
+class AudioDecoder : public TimedAudioSource, public virtual Decoder
 {
 public:
        AudioDecoder (boost::shared_ptr<const Film>);
index 08549165710530435f37455ca1562329be1272bb..ba4b772c8a66b02057d070e8d014473479d76604 100644 (file)
@@ -27,4 +27,11 @@ public:
        virtual void process_audio (boost::shared_ptr<AudioBuffers>) = 0;
 };
 
+class TimedAudioSink
+{
+public:
+        /** Call with some audio data */
+        virtual void process_audio (boost::shared_ptr<AudioBuffers>, double t) = 0;
+};
+
 #endif
index 99b59759d07eac7250126429515f7c00863f9677..3dd3027ab4650bd23fd4ef4fc8fd958438e5a7f4 100644 (file)
@@ -38,3 +38,9 @@ AudioSource::connect_audio (shared_ptr<AudioSink> s)
 {
        Audio.connect (bind (process_audio_proxy, weak_ptr<AudioSink> (s), _1));
 }
+
+void
+TimedAudioSource::connect_audio (shared_ptr<TimedAudioSink> s)
+{
+       Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2));
+}
index ee5c606dc4c90542a9b7e66501bdfd8bfb6300eb..dd248071651fbbc0e7c1b497e9cfcdf39c899970 100644 (file)
@@ -28,6 +28,7 @@
 
 class AudioBuffers;
 class AudioSink;
+class TimedAudioSink;
 
 /** A class that emits audio data */
 class AudioSource
@@ -39,4 +40,15 @@ public:
        void connect_audio (boost::shared_ptr<AudioSink>);
 };
 
+
+/** A class that emits audio data with timestamps */
+class TimedAudioSource
+{
+public:
+       /** Emitted when some audio data is ready */
+       boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>, double)> Audio;
+
+       void connect_audio (boost::shared_ptr<TimedAudioSink>);
+};
+
 #endif
index 12ce4a96e289b6c09da748808f28dac6ed629526..0a9eaf6b60bf8e12d2689ade030c7f16c50f0081 100644 (file)
@@ -23,7 +23,7 @@
 using boost::shared_ptr;
 
 Combiner::Combiner (shared_ptr<Log> log)
-       : VideoProcessor (log)
+       : TimedVideoProcessor (log)
 {
 
 }
@@ -33,7 +33,7 @@ Combiner::Combiner (shared_ptr<Log> log)
  *  @param image Frame image.
  */
 void
-Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>)
+Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>, double)
 {
        _image = image;
 }
@@ -43,7 +43,7 @@ Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>)
  *  @param sub Subtitle (which will be put onto the whole frame)
  */
 void
-Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub)
+Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub, double t)
 {
        /* Copy the right half of this image into our _image */
        /* XXX: this should probably be in the Image class */
@@ -62,6 +62,6 @@ Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> s
                }
        }
 
-       Video (_image, false, sub);
+       Video (_image, false, sub, t);
        _image.reset ();
 }
index 68026eaff7eec0814f7017f140ac1c8b8ed12095..a8f1fa804e9bd1549e4dbb727875b71e3c353bcc 100644 (file)
  *  one image used for the left half of the screen and the other for
  *  the right.
  */
-class Combiner : public VideoProcessor
+class Combiner : public TimedVideoProcessor
 {
 public:
        Combiner (boost::shared_ptr<Log> log);
 
-       void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
-       void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
+       void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s, double);
+       void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s, double);
 
 private:
        /** The image that we are currently working on */
index 72b866ffe9e7e6cf2dcad643cbe50b7376569dae..20e32bfbf64ce83d58b38672a0a6be5500085eb7 100644 (file)
@@ -56,6 +56,10 @@ public:
 
        virtual bool pass () = 0;
        virtual bool seek (double);
+       virtual void seek_back () {}
+       virtual void seek_forward () {}
+
+       boost::signals2::signal<void()> OutputChanged;
 
 protected:
        boost::shared_ptr<const Film> _film;
index 53da9a41283a25c5dc9d6ebfa9a5b4a1d51cc4e4..9e6baeba8131f0ebe671f0b9d3112466bfe1e5f8 100644 (file)
 using std::min;
 using boost::shared_ptr;
 
-/** @param channels Number of channels of audio.
- *  @param frames Delay in frames, +ve to move audio later.
+/*  @param seconds Delay in seconds, +ve to move audio later.
  */
-DelayLine::DelayLine (shared_ptr<Log> log, int channels, int frames)
-       : AudioProcessor (log)
-       , _negative_delay_remaining (0)
-       , _frames (frames)
+DelayLine::DelayLine (shared_ptr<Log> log, double seconds)
+       : TimedAudioVideoProcessor (log)
+       , _seconds (seconds)
 {
-       if (_frames > 0) {
-               /* We need a buffer to keep some data in */
-               _buffers.reset (new AudioBuffers (channels, _frames));
-               _buffers->make_silent ();
-       } else if (_frames < 0) {
-               /* We can do -ve delays just by chopping off
-                  the start, so no buffer needed.
-               */
-               _negative_delay_remaining = -_frames;
-       }
+       
 }
 
 void
-DelayLine::process_audio (shared_ptr<AudioBuffers> data)
+DelayLine::process_audio (shared_ptr<AudioBuffers> data, double t)
 {
-       if (_buffers) {
-               /* We have some buffers, so we are moving the audio later */
-
-               /* Copy the input data */
-               AudioBuffers input (*data.get ());
-
-               int to_do = data->frames ();
-
-               /* Write some of our buffer to the output */
-               int const from_buffer = min (to_do, _buffers->frames());
-               data->copy_from (_buffers.get(), from_buffer, 0, 0);
-               to_do -= from_buffer;
-
-               /* Write some of the input to the output */
-               int const from_input = to_do;
-               data->copy_from (&input, from_input, 0, from_buffer);
-
-               int const left_in_buffer = _buffers->frames() - from_buffer;
-
-               /* Shuffle our buffer down */
-               _buffers->move (from_buffer, 0, left_in_buffer);
-
-               /* Copy remaining input data to our buffer */
-               _buffers->copy_from (&input, input.frames() - from_input, from_input, left_in_buffer);
-
-       } else {
+       if (_seconds > 0) {
+               t += _seconds;
+       }
 
-               /* Chop the initial data off until _negative_delay_remaining
-                  is zero, then just pass data.
-               */
+       Audio (data, t);
+}
 
-               int const to_do = min (data->frames(), _negative_delay_remaining);
-               if (to_do) {
-                       data->move (to_do, 0, data->frames() - to_do);
-                       data->set_frames (data->frames() - to_do);
-                       _negative_delay_remaining -= to_do;
-               }
+void
+DelayLine::process_video (boost::shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
+{
+       if (_seconds < 0) {
+               t += _seconds;
        }
 
-       Audio (data);
+       Video (image, same, sub, t);
 }
index c51784f353ab7edbbdcd373f6bcb42da5957bf8c..90f1dcfa7b55b56fd1c42442d3b7fa0311a2fa56 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include "processor.h"
 
-class AudioBuffers;
-
-/** A delay line for audio */
-class DelayLine : public AudioProcessor
+/** A delay line */
+class DelayLine : public TimedAudioVideoProcessor
 {
 public:
-       DelayLine (boost::shared_ptr<Log> log, int channels, int frames);
+       DelayLine (boost::shared_ptr<Log> log, double);
        
-       void process_audio (boost::shared_ptr<AudioBuffers>);
+       void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
+       void process_audio (boost::shared_ptr<AudioBuffers>, double);
 
 private:
-       boost::shared_ptr<AudioBuffers> _buffers;
-       int _negative_delay_remaining; ///< number of frames of negative delay that remain to emit
-       int _frames;
+       double _seconds;
 };
index 577dbd14d084ba6fe8a19db0c8870935a7d7d9dd..719c4cb53ecf03488d8266954b6309205657bb5e 100644 (file)
@@ -115,7 +115,7 @@ FFmpegContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
 
        Content::examine (film, job, quick);
 
-       shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false, true));
+       shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false));
 
        ContentVideoFrame video_length = 0;
        if (quick) {
index eac1d91aea3c93db48178ed6fd7775e9b830c7af..82c7cafd1b8ec629b1473402ed089b06efad0f16 100644 (file)
@@ -63,7 +63,7 @@ using libdcp::Size;
 
 boost::mutex FFmpegDecoder::_mutex;
 
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles, bool video_sync)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles)
        : Decoder (f)
        , VideoDecoder (f)
        , AudioDecoder (f)
@@ -80,16 +80,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
        , _decode_video (video)
        , _decode_audio (audio)
        , _decode_subtitles (subtitles)
-       , _video_sync (video_sync)
 {
        setup_general ();
        setup_video ();
        setup_audio ();
        setup_subtitle ();
-
-       if (!video_sync) {
-               _first_video = 0;
-       }
 }
 
 FFmpegDecoder::~FFmpegDecoder ()
@@ -233,26 +228,26 @@ FFmpegDecoder::pass ()
                        av_strerror (r, buf, sizeof(buf));
                        _film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
                }
-               
+
                /* Get any remaining frames */
                
                _packet.data = 0;
                _packet.size = 0;
-
+               
                /* XXX: should we reset _packet.data and size after each *_decode_* call? */
-
+               
                int frame_finished;
 
                if (_decode_video) {
                        while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                               filter_and_emit_video (_frame);
+                               filter_and_emit_video ();
                        }
                }
 
                if (_ffmpeg_content->audio_stream() && _decode_audio) {
                        decode_audio_packet ();
                }
-
+                       
                return true;
        }
 
@@ -268,16 +263,12 @@ FFmpegDecoder::pass ()
                                _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
                        }
 
-                       if (_video_sync) {
-                               out_with_sync ();
-                       } else {
-                               filter_and_emit_video (_frame);
-                       }
+                       filter_and_emit_video ();
                }
 
        } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
                decode_audio_packet ();
-       } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles && _first_video) {
+       } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles) {
 
                int got_subtitle;
                AVSubtitle sub;
@@ -461,18 +452,20 @@ string
 FFmpegDecoder::stream_name (AVStream* s) const
 {
        stringstream n;
-       
-       AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
-       if (lang) {
-               n << lang->value;
-       }
-       
-       AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
-       if (title) {
-               if (!n.str().empty()) {
-                       n << N_(" ");
+
+       if (s->metadata) {
+               AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
+               if (lang) {
+                       n << lang->value;
+               }
+               
+               AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
+               if (title) {
+                       if (!n.str().empty()) {
+                               n << N_(" ");
+                       }
+                       n << title->value;
                }
-               n << title->value;
        }
 
        if (n.str().empty()) {
@@ -489,97 +482,92 @@ FFmpegDecoder::bytes_per_audio_sample () const
 }
 
 void
-FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
+FFmpegDecoder::filter_and_emit_video ()
 {
        boost::mutex::scoped_lock lm (_filter_graphs_mutex);
        
        shared_ptr<FilterGraph> graph;
 
        list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
-       while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (frame->width, frame->height), (AVPixelFormat) frame->format)) {
+       while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
                ++i;
        }
 
        if (i == _filter_graphs.end ()) {
-               graph.reset (new FilterGraph (_film, this, libdcp::Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+               graph.reset (new FilterGraph (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
                _filter_graphs.push_back (graph);
-               _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), frame->width, frame->height, frame->format));
+               _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
        } else {
                graph = *i;
        }
 
-       list<shared_ptr<Image> > images = graph->process (frame);
+       list<shared_ptr<Image> > images = graph->process (_frame);
 
        for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
-               emit_video (*i, frame_time ());
+               int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+               if (bet != AV_NOPTS_VALUE) {
+                       emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base));
+               } else {
+                       _film->log()->log ("Dropping frame without PTS");
+               }
        }
 }
 
 bool
 FFmpegDecoder::seek (double p)
 {
-       /* This use of AVSEEK_FLAG_BACKWARD is a bit of a hack; without it, if we ask for a seek to the same place as last time
-          (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
-          staying in the same place.
-       */
-       bool const backwards = (p == last_content_time());
-       
+       return do_seek (p, false, false);
+}
+
+void
+FFmpegDecoder::seek_back ()
+{
+       do_seek (last_content_time() - 2.5 / video_frame_rate(), true, true);
+}
+
+void
+FFmpegDecoder::seek_forward ()
+{
+       do_seek (last_content_time() - 0.5 / video_frame_rate(), true, true);
+}
+
+bool
+FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
+{
        int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
 
        int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
-       
+
        avcodec_flush_buffers (_video_codec_context);
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
-       
-       return r < 0;
-}
 
-void
-FFmpegDecoder::out_with_sync ()
-{
-       /* Where we are in the output, in seconds */
-       double const out_pts_seconds = video_frame() / video_frame_rate();
-       
-       /* Where we are in the source, in seconds */
-       double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
-               * av_frame_get_best_effort_timestamp(_frame);
-       
-       _film->log()->log (
-               String::compose (N_("Source video frame ready; source at %1, output at %2"), source_pts_seconds, out_pts_seconds),
-               Log::VERBOSE
-               );
-       
-       if (!_first_video) {
-               _first_video = source_pts_seconds;
-       }
-       
-       /* Difference between where we are and where we should be */
-       double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
-       double const one_frame = 1 / video_frame_rate();
-       
-       /* Insert frames if required to get out_pts_seconds up to pts_seconds */
-       if (delta > one_frame) {
-               int const extra = rint (delta / one_frame);
-               for (int i = 0; i < extra; ++i) {
-                       repeat_last_video (frame_time ());
-                       _film->log()->log (
-                               String::compose (
-                                       N_("Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)"),
-                                       out_pts_seconds, video_frame(), source_pts_seconds, video_frame_rate()
-                                       )
-                               );
+       if (accurate) {
+               while (1) {
+                       int r = av_read_frame (_format_context, &_packet);
+                       if (r < 0) {
+                               return true;
+                       }
+                       
+                       avcodec_get_frame_defaults (_frame);
+                       
+                       if (_packet.stream_index == _video_stream) {
+                               int finished = 0;
+                               int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
+                               if (r >= 0 && finished) {
+                                       int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+                                       if (bet > vt) {
+                                               break;
+                                       }
+                               }
+                       }
+                       
+                       av_free_packet (&_packet);
                }
        }
-       
-       if (delta > -one_frame) {
-               /* Process this frame */
-               filter_and_emit_video (_frame);
-       } else {
-               /* Otherwise we are omitting a frame to keep things right */
-               _film->log()->log (String::compose (N_("Frame removed at %1s"), out_pts_seconds));
-       }
+               
+       return r < 0;
 }
 
 void
@@ -606,12 +594,6 @@ FFmpegDecoder::video_length () const
        return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
 }
 
-double
-FFmpegDecoder::frame_time () const
-{
-       return av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base);
-}
-
 void
 FFmpegDecoder::decode_audio_packet ()
 {
@@ -625,53 +607,21 @@ 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 && frame_finished) {
-                       
-                       /* Where we are in the source, in seconds */
-                       double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                               * av_frame_get_best_effort_timestamp(_frame);
-                       
-                       /* We only decode audio if we've had our first video packet through, and if it
-                          was before this packet.  Until then audio is thrown away.
-                       */
+               if (decode_result >= 0) {
+                       if (frame_finished) {
                        
-                       if ((_first_video && _first_video.get() <= source_pts_seconds) || !_decode_video) {
-                               
-                               if (!_first_audio && _decode_video) {
-                                       _first_audio = source_pts_seconds;
-                                       
-                                       /* This is our first audio frame, and if we've arrived here we must have had our
-                                          first video frame.  Push some silence to make up any gap between our first
-                                          video frame and our first audio.
-                                       */
-                                       
-                                       /* frames of silence that we must push */
-                                       int const s = rint ((_first_audio.get() - _first_video.get()) * _ffmpeg_content->audio_frame_rate ());
-                                       
-                                       _film->log()->log (
-                                               String::compose (
-                                                       N_("First video at %1, first audio at %2, pushing %3 audio frames of silence for %4 channels (%5 bytes per sample)"),
-                                                       _first_video.get(), _first_audio.get(), s, _ffmpeg_content->audio_channels(), bytes_per_audio_sample()
-                                                       )
-                                               );
-                                       
-                                       if (s) {
-                                               shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), s));
-                                               audio->make_silent ();
-                                               Audio (audio);
-                                       }
-                               }
+                               /* Where we are in the source, in seconds */
+                               double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+                                       * av_frame_get_best_effort_timestamp(_frame);
                                
                                int const data_size = av_samples_get_buffer_size (
                                        0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
                                        );
                                
                                assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
-                               Audio (deinterleave_audio (_frame->data, data_size));
+                               Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
                        }
-               }
-
-               if (decode_result >= 0) {
+                       
                        copy_packet.data += decode_result;
                        copy_packet.size -= decode_result;
                }
index f6a53874ae723871d4d6dba390d57dccab047597..174cc39954b9fccc92153ea249cfc421e4458e80 100644 (file)
@@ -57,7 +57,7 @@ class Log;
 class FFmpegDecoder : public VideoDecoder, public AudioDecoder
 {
 public:
-       FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles, bool video_sync);
+       FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles);
        ~FFmpegDecoder ();
 
        float video_frame_rate () const;
@@ -77,6 +77,8 @@ public:
        }
 
        bool seek (double);
+       void seek_forward ();
+       void seek_back ();
        bool pass ();
 
 private:
@@ -88,10 +90,9 @@ private:
        PixelFormat pixel_format () const;
        AVSampleFormat audio_sample_format () const;
        int bytes_per_audio_sample () const;
+       bool do_seek (double, bool, bool);
 
-       void out_with_sync ();
-       void filter_and_emit_video (AVFrame *);
-       double frame_time () const;
+       void filter_and_emit_video ();
 
        void setup_general ();
        void setup_video ();
@@ -123,9 +124,6 @@ private:
 
        AVPacket _packet;
 
-       boost::optional<double> _first_video;
-       boost::optional<double> _first_audio;
-
        std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
        boost::mutex _filter_graphs_mutex;
 
@@ -135,7 +133,6 @@ private:
        bool _decode_video;
        bool _decode_audio;
        bool _decode_subtitles;
-       bool _video_sync;
 
        /* It would appear (though not completely verified) that one must have
           a mutex around calls to avcodec_open* and avcodec_close... and here
index c8aee7a0aa6bdf6bc6b925209a6ae92043f72098..361031fc87d9e6b8309506bb78b8d95216336bd9 100644 (file)
@@ -145,6 +145,8 @@ Film::Film (string d, bool must_exist)
 
        if (must_exist) {
                read_metadata ();
+       } else {
+               write_metadata ();
        }
 
        _log.reset (new FileLog (file ("log")));
@@ -193,6 +195,7 @@ string
 Film::video_state_identifier () const
 {
        assert (format ());
+       LocaleGuard lg;
 
        pair<string, string> f = Filter::ffmpeg_strings (filters());
 
@@ -405,6 +408,7 @@ Film::write_metadata () const
        ContentList the_content = content ();
        
        boost::mutex::scoped_lock lm (_state_mutex);
+       LocaleGuard lg;
 
        boost::filesystem::create_directories (directory());
 
@@ -471,6 +475,7 @@ void
 Film::read_metadata ()
 {
        boost::mutex::scoped_lock lm (_state_mutex);
+       LocaleGuard lg;
 
        if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
                throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
index 3d18edb3e51fedb2f8bd9dd56fe4f8d02951aa9a..a83d53ebd1aa3ba01a486c488cb35d90b3645e67 100644 (file)
@@ -51,7 +51,7 @@ FixedFormat::name () const
                s << _nickname << N_(" (");
        }
 
-       s << setprecision(3) << (_ratio / 100.0) << N_(":1");
+       s << setprecision(3) << _ratio << N_(":1");
 
        if (!_nickname.empty ()) {
                s << N_(")");
@@ -73,51 +73,51 @@ Format::setup_formats ()
 {
        /// TRANSLATORS: these are film picture aspect ratios; "Academy" means 1.37, "Flat" 1.85 and "Scope" 2.39.
        _formats.push_back (
-               new FixedFormat (119, libdcp::Size (1285, 1080), N_("119"), _("1.19"), N_("F")
+               new FixedFormat (1.19, libdcp::Size (1285, 1080), N_("119"), _("1.19"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (133, libdcp::Size (1436, 1080), N_("133"), _("1.33"), N_("F")
+               new FixedFormat (4.0 / 3.0, libdcp::Size (1436, 1080), N_("133"), _("4:3"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (138, libdcp::Size (1485, 1080), N_("138"), _("1.375"), N_("F")
+               new FixedFormat (1.38, libdcp::Size (1485, 1080), N_("138"), _("1.375"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (133, libdcp::Size (1998, 1080), N_("133-in-flat"), _("4:3 within Flat"), N_("F")
+               new FixedFormat (4.0 / 3.0, libdcp::Size (1998, 1080), N_("133-in-flat"), _("4:3 within Flat"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (137, libdcp::Size (1480, 1080), N_("137"), _("Academy"), N_("F")
+               new FixedFormat (1.37, libdcp::Size (1480, 1080), N_("137"), _("Academy"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (166, libdcp::Size (1793, 1080), N_("166"), _("1.66"), N_("F")
+               new FixedFormat (1.66, libdcp::Size (1793, 1080), N_("166"), _("1.66"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (166, libdcp::Size (1998, 1080), N_("166-in-flat"), _("1.66 within Flat"), N_("F")
+               new FixedFormat (1.66, libdcp::Size (1998, 1080), N_("166-in-flat"), _("1.66 within Flat"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (178, libdcp::Size (1998, 1080), N_("178-in-flat"), _("16:9 within Flat"), N_("F")
+               new FixedFormat (1.78, libdcp::Size (1998, 1080), N_("178-in-flat"), _("16:9 within Flat"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (178, libdcp::Size (1920, 1080), N_("178"), _("16:9"), N_("F")
+               new FixedFormat (1.78, libdcp::Size (1920, 1080), N_("178"), _("16:9"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (185, libdcp::Size (1998, 1080), N_("185"), _("Flat"), N_("F")
+               new FixedFormat (1.85, libdcp::Size (1998, 1080), N_("185"), _("Flat"), N_("F")
                        ));
        
        _formats.push_back (
-               new FixedFormat (178, libdcp::Size (2048, 858), N_("178-in-scope"), _("16:9 within Scope"), N_("S")
+               new FixedFormat (1.78, libdcp::Size (2048, 858), N_("178-in-scope"), _("16:9 within Scope"), N_("S")
                        ));
        
        _formats.push_back (
-               new FixedFormat (239, libdcp::Size (2048, 858), N_("239"), _("Scope"), N_("S")
+               new FixedFormat (2.39, libdcp::Size (2048, 858), N_("239"), _("Scope"), N_("S")
                        ));
                
        _formats.push_back (
@@ -182,12 +182,12 @@ Format::all ()
        return _formats;
 }
 
-/** @param r Ratio multiplied by 100 (e.g. 185)
+/** @param r Ratio
  *  @param dcp Size (in pixels) of the images that we should put in a DCP.
  *  @param id ID (e.g. 185)
  *  @param n Nick name (e.g. Flat)
  */
-FixedFormat::FixedFormat (int r, libdcp::Size dcp, string id, string n, string d)
+FixedFormat::FixedFormat (float r, libdcp::Size dcp, string id, string n, string d)
        : Format (dcp, id, n, d)
        , _ratio (r)
 {
@@ -200,7 +200,7 @@ FixedFormat::FixedFormat (int r, libdcp::Size dcp, string id, string n, string d
 int
 Format::dcp_padding (shared_ptr<const Film> f) const
 {
-       int p = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_float(f))) / 2.0);
+       int p = rint ((_dcp_size.width - (_dcp_size.height * ratio(f))) / 2.0);
 
        /* This comes out -ve for Scope; bodge it */
        if (p < 0) {
@@ -211,7 +211,7 @@ Format::dcp_padding (shared_ptr<const Film> f) const
 }
 
 float
-Format::container_ratio_as_float () const
+Format::container_ratio () const
 {
        return static_cast<float> (_dcp_size.width) / _dcp_size.height;
 }
@@ -222,14 +222,8 @@ VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d)
 
 }
 
-int
-VariableFormat::ratio_as_integer (shared_ptr<const Film> f) const
-{
-       return rint (ratio_as_float (f) * 100);
-}
-
 float
-VariableFormat::ratio_as_float (shared_ptr<const Film> f) const
+VariableFormat::ratio (shared_ptr<const Film> f) const
 {
        libdcp::Size const c = f->cropped_size (f->video_size ());
        return float (c.width) / c.height;
index 7958ca5341604e199a181b2295e0708b3614d542..f240c89fc85db4b76ee45f34291e8294dfdbd5b4 100644 (file)
@@ -38,16 +38,8 @@ public:
                , _dci_name (d)
        {}
 
-       /** @return the aspect ratio multiplied by 100
-        *  (e.g. 239 for Cinemascope 2.39:1)
-        */
-       virtual int ratio_as_integer (boost::shared_ptr<const Film> f) const = 0;
-
-       /** @return the ratio as a floating point number */
-       virtual float ratio_as_float (boost::shared_ptr<const Film> f) const = 0;
-
-       /** @return the ratio of the container (including any padding) as a floating point number */
-       float container_ratio_as_float () const;
+       /** @return the ratio of the container (including any padding) */
+       float container_ratio () const;
 
        int dcp_padding (boost::shared_ptr<const Film>) const;
 
@@ -84,6 +76,9 @@ public:
        static void setup_formats ();
 
 protected:     
+        /** @return the ratio */
+       virtual float ratio (boost::shared_ptr<const Film> f) const = 0;
+
        /** libdcp::Size in pixels of the images that we should
         *  put in a DCP for this ratio.  This size will not correspond
         *  to the ratio when we are doing things like 16:9 in a Flat frame.
@@ -107,22 +102,17 @@ private:
 class FixedFormat : public Format
 {
 public:
-       FixedFormat (int, libdcp::Size, std::string, std::string, std::string);
+       FixedFormat (float, libdcp::Size, std::string, std::string, std::string);
 
-       int ratio_as_integer (boost::shared_ptr<const Film>) const {
+       float ratio (boost::shared_ptr<const Film>) const {
                return _ratio;
        }
 
-       float ratio_as_float (boost::shared_ptr<const Film>) const {
-               return _ratio / 100.0;
-       }
-
        std::string name () const;
        
 private:
 
-       /** Ratio expressed as the actual ratio multiplied by 100 */
-       int _ratio;
+       float _ratio;
 };
 
 class VariableFormat : public Format
@@ -130,8 +120,7 @@ class VariableFormat : public Format
 public:
        VariableFormat (libdcp::Size, std::string, std::string, std::string);
 
-       int ratio_as_integer (boost::shared_ptr<const Film> f) const;
-       float ratio_as_float (boost::shared_ptr<const Film> f) const;
+       float ratio (boost::shared_ptr<const Film> f) const;
 
        std::string name () const;
 };
index 7049b7d6e31945ace55eadd9e16bc4ef6f04a170..3888347ca73329359192539aa81a21fd3cc3e1ca 100644 (file)
@@ -65,8 +65,8 @@ ImageMagickDecoder::pass ()
                return true;
        }
 
-       if (have_last_video ()) {
-               repeat_last_video (double (_position) / 24);
+       if (_image) {
+               emit_video (_image, true, double (_position) / 24);
                _position++;
                return false;
        }
@@ -74,11 +74,11 @@ ImageMagickDecoder::pass ()
        Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ());
        
        libdcp::Size size = native_size ();
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false));
+       _image.reset (new SimpleImage (PIX_FMT_RGB24, size, false));
 
        using namespace MagickCore;
        
-       uint8_t* p = image->data()[0];
+       uint8_t* p = _image->data()[0];
        for (int y = 0; y < size.height; ++y) {
                for (int x = 0; x < size.width; ++x) {
                        Magick::Color c = magick_image->pixelColor (x, y);
@@ -90,9 +90,8 @@ ImageMagickDecoder::pass ()
 
        delete magick_image;
 
-       image = image->crop (_film->crop(), true);
-       
-       emit_video (image, double (_position) / 24);
+       _image = _image->crop (_film->crop(), true);
+       emit_video (_image, false, double (_position) / 24);
 
        ++_position;
        return false;
index 40a89bb1594007ad65eb798fab7fdb7eb9be43c1..e7c9dee9a3ac35373668433e6208d02128aafc0a 100644 (file)
@@ -63,5 +63,6 @@ protected:
 
 private:
        boost::shared_ptr<const ImageMagickContent> _imagemagick_content;
+       boost::shared_ptr<Image> _image;
        ContentVideoFrame _position;
 };
index 77ed9b6515bbb3e40add4a89e3b4d6d6d81a0d52..edbb084de7ebd6e4a872cdaf3218a00ab5199d03 100644 (file)
 #include "i18n.h"
 
 using std::min;
+using std::cout;
+using std::list;
 using boost::shared_ptr;
 
 Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
-       : AudioVideoProcessor (log)
+       : Processor (log)
        , _sample_rate (sample_rate)
        , _frames_per_second (frames_per_second)
        , _video_frames (0)
        , _audio_frames (0)
+       , _had_first_video (false)
+       , _had_first_audio (false)
 {
 
 }
 
 void
-Matcher::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
+Matcher::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
 {
-       Video (i, same, s);
-       _video_frames++;
+       _pixel_format = image->pixel_format ();
+       _size = image->size ();
 
-       _pixel_format = i->pixel_format ();
-       _size = i->size ();
+       _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size()));
+
+       if (!_first_input) {
+               _first_input = t;
+       }
+
+       bool const this_is_first_video = !_had_first_video;
+       _had_first_video = true;
+
+       if (this_is_first_video && _had_first_audio) {
+               /* First video since we got audio */
+               fix_start (t);
+       }
+
+       /* Video before audio is fine, since we can make up an arbitrary difference
+          with audio samples (contrasting with video which is quantised to frames)
+       */
+
+       /* Difference between where this video is and where it should be */
+       double const delta = t - _first_input.get() - _video_frames / _frames_per_second;
+       double const one_frame = 1 / _frames_per_second;
+       
+       if (delta > one_frame) {
+               /* Insert frames to make up the difference */
+               int const extra = rint (delta / one_frame);
+               for (int i = 0; i < extra; ++i) {
+                       repeat_last_video ();
+                       _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second));
+               }
+       }
+               
+       if (delta > -one_frame) {
+               Video (image, same, sub);
+               ++_video_frames;
+       } else {
+               /* We are omitting a frame to keep things right */
+               _log->log (String::compose ("Frame removed at %1s", t));
+       }
+       
+       _last_image = image;
+       _last_subtitle = sub;
 }
 
 void
-Matcher::process_audio (shared_ptr<AudioBuffers> b)
+Matcher::process_audio (shared_ptr<AudioBuffers> b, double t)
 {
-       Audio (b);
-       _audio_frames += b->frames ();
-
        _channels = b->channels ();
+
+       _log->log (String::compose ("Matcher audio @ %1 [video=%2, audio=%3, pending_audio=%4]", t, _video_frames, _audio_frames, _pending_audio.size()));
+
+       if (!_first_input) {
+               _first_input = t;
+       }
+
+       bool const this_is_first_audio = _had_first_audio;
+       _had_first_audio = true;
+       
+       if (!_had_first_video) {
+               /* No video yet; we must postpone these data until we have some */
+               _pending_audio.push_back (AudioRecord (b, t));
+       } else if (this_is_first_audio && !_had_first_video) {
+               /* First audio since we got video */
+               _pending_audio.push_back (AudioRecord (b, t));
+               fix_start (_first_input.get ());
+       } else {
+               /* Normal running.  We assume audio time stamps are consecutive */
+               Audio (b);
+               _audio_frames += b->frames ();
+       }
 }
 
 void
@@ -62,39 +124,58 @@ Matcher::process_end ()
                /* We won't do anything */
                return;
        }
+
+       _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames",
+                                   _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames));
        
-       int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
-
-       _log->log (
-               String::compose (
-                       N_("Matching processor has seen %1 video frames (which equals %2 audio frames) and %3 audio frames"),
-                       _video_frames,
-                       video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second),
-                       _audio_frames
-                       )
-               );
+       match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second));
+}
+
+void
+Matcher::fix_start (double first_video)
+{
+       assert (!_pending_audio.empty ());
+
+       _log->log (String::compose ("Fixing start; video at %1, audio at %2", first_video, _pending_audio.front().time));
+
+       match (first_video - _pending_audio.front().time);
+
+       for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
+               process_audio (i->audio, i->time);
+       }
        
-       if (audio_short_by_frames < 0) {
-               
-               _log->log (String::compose (N_("%1 too many audio frames"), -audio_short_by_frames));
-               
-               /* We have seen more audio than video.  Emit enough black video frames so that we reverse this */
-               int const black_video_frames = ceil (-audio_short_by_frames * _frames_per_second / _sample_rate);
+       _pending_audio.clear ();
+}
+
+void
+Matcher::match (double extra_video_needed)
+{
+       _log->log (String::compose ("Match %1", extra_video_needed));
+       
+       if (extra_video_needed > 0) {
+
+               /* Emit black video frames */
                
+               int const black_video_frames = ceil (extra_video_needed * _frames_per_second);
+
                _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames));
 
                shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
                black->make_black ();
                for (int i = 0; i < black_video_frames; ++i) {
                        Video (black, i != 0, shared_ptr<Subtitle>());
+                       ++_video_frames;
                }
-               
-               /* Now recompute our check value */
-               audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
+
+               extra_video_needed -= black_video_frames / _frames_per_second;
        }
-       
-       if (audio_short_by_frames > 0) {
-               _log->log (String::compose (N_("Emitted %1 too few audio frames"), audio_short_by_frames));
+
+       if (extra_video_needed < 0) {
+               
+               /* Emit silence */
+               
+               int64_t to_do = -extra_video_needed * _sample_rate;
+               _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do));
 
                /* Do things in half second blocks as I think there may be limits
                   to what FFmpeg (and in particular the resampler) can cope with.
@@ -103,7 +184,6 @@ Matcher::process_end ()
                shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
                b->make_silent ();
                
-               int64_t to_do = audio_short_by_frames;
                while (to_do > 0) {
                        int64_t const this_time = min (to_do, block);
                        b->set_frames (this_time);
@@ -113,3 +193,16 @@ Matcher::process_end ()
                }
        }
 }
+
+void
+Matcher::repeat_last_video ()
+{
+       if (!_last_image) {
+               _last_image.reset (new SimpleImage (_pixel_format.get(), _size.get(), true));
+               _last_image->make_black ();
+       }
+
+       Video (_last_image, true, _last_subtitle);
+       ++_video_frames;
+}
+
index b1680e13106301380a62412d926e56d6cdbfdef1..f54aa4b6a7c9c37815697fcd03c55b9936936353 100644 (file)
 #include "processor.h"
 #include "ffmpeg_compatibility.h"
 
-class Matcher : public AudioVideoProcessor
+class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource 
 {
 public:
        Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second);
-       void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
-       void process_audio (boost::shared_ptr<AudioBuffers>);
+       void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s, double);
+       void process_audio (boost::shared_ptr<AudioBuffers>, double);
        void process_end ();
 
 private:
+       void fix_start (double);
+       void match (double);
+       void repeat_last_video ();
+       
        int _sample_rate;
        float _frames_per_second;
        int _video_frames;
@@ -37,4 +41,23 @@ private:
        boost::optional<AVPixelFormat> _pixel_format;
        boost::optional<libdcp::Size> _size;
        boost::optional<int> _channels;
+
+       struct AudioRecord {
+               AudioRecord (boost::shared_ptr<AudioBuffers> a, double t)
+                       : audio (a)
+                       , time (t)
+               {}
+               
+               boost::shared_ptr<AudioBuffers> audio;
+               double time;
+       };
+
+       std::list<AudioRecord> _pending_audio;
+
+       boost::optional<double> _first_input;
+       boost::shared_ptr<Image> _last_image;
+       boost::shared_ptr<Subtitle> _last_subtitle;
+
+       bool _had_first_video;
+       bool _had_first_audio;
 };
index c77059b0ab546ce5fdb503fa12b6becce155e3e1..635b67cad728e3cc0d08905b91e427500eef9c26 100644 (file)
@@ -41,7 +41,6 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        , _audio (true)
        , _subtitles (true)
        , _have_valid_decoders (false)
-       , _video_sync (true)
 {
        _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
@@ -92,8 +91,9 @@ Player::pass ()
                        }
                }
 
-               Audio (_audio_buffers);
+               Audio (_audio_buffers, _audio_time.get());
                _audio_buffers.reset ();
+               _audio_time = boost::none;
        }
 
        return done;
@@ -118,18 +118,21 @@ Player::set_progress (shared_ptr<Job> job)
 }
 
 void
-Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
+Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s, double t)
 {
-       Video (i, same, s);
+       /* XXX: this time will need mangling to add on the offset of the start of the content */
+       Video (i, same, s, t);
 }
 
 void
-Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b)
+Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b, double t)
 {
+       /* XXX: this time will need mangling to add on the offset of the start of the content */
        AudioMapping mapping = _film->audio_mapping ();
        if (!_audio_buffers) {
                _audio_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ()));
                _audio_buffers->make_silent ();
+               _audio_time = t;
        }
 
        for (int i = 0; i < b->channels(); ++i) {
@@ -141,8 +144,9 @@ Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers>
 
        if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
                /* We can just emit this audio now as it will all be here */
-               Audio (_audio_buffers);
+               Audio (_audio_buffers, t);
                _audio_buffers.reset ();
+               _audio_time = boost::none;
        }
 }
 
@@ -177,6 +181,20 @@ Player::seek (double t)
        return false;
 }
 
+
+void
+Player::seek_back ()
+{
+       /* XXX */
+}
+
+void
+Player::seek_forward ()
+{
+       /* XXX */
+}
+
+
 void
 Player::setup_decoders ()
 {
@@ -198,13 +216,12 @@ Player::setup_decoders ()
                                        new FFmpegDecoder (
                                                _film, fc, _video,
                                                _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
-                                               _subtitles,
-                                               _video_sync
+                                               _subtitles
                                                )
                                        );
 
                                if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
-                                       fd->Audio.connect (bind (&Player::process_audio, this, fc, _1));
+                                       fd->Audio.connect (bind (&Player::process_audio, this, fc, _1, _2));
                                }
 
                                d = fd;
@@ -227,17 +244,11 @@ Player::setup_decoders ()
                for (list<shared_ptr<const SndfileContent> >::iterator i = sc.begin(); i != sc.end(); ++i) {
                        shared_ptr<SndfileDecoder> d (new SndfileDecoder (_film, *i));
                        _sndfile_decoders.push_back (d);
-                       d->Audio.connect (bind (&Player::process_audio, this, *i, _1));
+                       d->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
                }
        }
 }
 
-void
-Player::disable_video_sync ()
-{
-       _video_sync = false;
-}
-
 double
 Player::last_video_time () const
 {
index 9a55b8599a4031dce55af54e202e060af32dda45..539fae67a2ad7730e226af84bffd95f37f1dad14 100644 (file)
@@ -35,7 +35,7 @@ class Film;
 class Playlist;
 class AudioContent;
 
-class Player : public VideoSource, public AudioSource, public VideoSink, public boost::enable_shared_from_this<Player>
+class Player : public TimedVideoSource, public TimedAudioSource, public TimedVideoSink, public boost::enable_shared_from_this<Player>
 {
 public:
        Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
@@ -43,17 +43,18 @@ public:
        void disable_video ();
        void disable_audio ();
        void disable_subtitles ();
-       void disable_video_sync ();
 
        bool pass ();
        void set_progress (boost::shared_ptr<Job>);
        bool seek (double);
+       void seek_back ();
+       void seek_forward ();
 
        double last_video_time () const;
 
 private:
-       void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s);
-       void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<AudioBuffers>);
+       void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double);
+       void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<AudioBuffers>, double);
        void setup_decoders ();
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int);
@@ -71,8 +72,7 @@ private:
        std::list<boost::shared_ptr<SndfileDecoder> > _sndfile_decoders;
 
        boost::shared_ptr<AudioBuffers> _audio_buffers;
-
-       bool _video_sync;
+       boost::optional<double> _audio_time;
 };
 
 #endif
index 5dbafab7f9d6d7c518e0a98e15383bd385640e64..7b7735faafa139a43b60f69f1796387edf79e413 100644 (file)
@@ -67,6 +67,15 @@ public:
        {}
 };
 
+class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink
+{
+public:
+       TimedAudioVideoProcessor (boost::shared_ptr<Log> log)
+               : Processor (log)
+       {}
+};
+                               
+
 /** @class AudioProcessor
  *  @brief A processor which handles just audio data.
  */
@@ -95,4 +104,12 @@ public:
        {}
 };
 
+class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink
+{
+public:
+       TimedVideoProcessor (boost::shared_ptr<Log> log)
+               : Processor (log)
+       {}
+};     
+
 #endif
index c7311112acdd28cf4303acb5fb581912bdaa932e..4db45f1d40f14b53552997665fbff8198ba59df9 100644 (file)
@@ -42,6 +42,7 @@ SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const Sndfi
                throw DecodeError (_("could not open audio file for reading"));
        }
 
+       _done = 0;
        _remaining = _info.frames;
 }
 
@@ -64,7 +65,8 @@ SndfileDecoder::pass ()
        shared_ptr<AudioBuffers> audio (new AudioBuffers (_sndfile_content->audio_channels(), this_time));
        sf_read_float (_sndfile, audio->data(0), this_time);
        audio->set_frames (this_time);
-       Audio (audio);
+       Audio (audio, double(_done) / audio_frame_rate());
+       _done += this_time;
        _remaining -= this_time;
 
        return (_remaining == 0);
index 2900afea0b3f258aaa5c1031328a716b800f9bef..b999a66d15f40644bb0313614c5d9c771e7b17cf 100644 (file)
@@ -42,5 +42,6 @@ private:
        boost::shared_ptr<const SndfileContent> _sndfile_content;
        SNDFILE* _sndfile;
        SF_INFO _info;
+       ContentAudioFrame _done;
        ContentAudioFrame _remaining;
 };
index 2a8ce50448c1ed31dc778767d702561e71d63522..ea3f27ad8f94f10175e1b6e81237b5e1d6059c12 100644 (file)
@@ -50,29 +50,22 @@ Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<Job> j)
        , _player (f->player ())
        , _encoder (new Encoder (f))
 {
-       if (f->has_audio ()) {
-               _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate()));
-               _delay_line.reset (new DelayLine (f->log(), f->audio_channels(), f->audio_delay() * f->audio_frame_rate() / 1000));
-               _gain.reset (new Gain (f->log(), f->audio_gain()));
-       }
+       _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate()));
+       _delay_line.reset (new DelayLine (f->log(), f->audio_delay() * f->audio_frame_rate() / 1000));
+       _gain.reset (new Gain (f->log(), f->audio_gain()));
 
        if (!f->with_subtitles ()) {
                _player->disable_subtitles ();
        }
 
-       if (_matcher) {
-               _player->connect_video (_matcher);
-               _matcher->connect_video (_encoder);
-       } else {
-               _player->connect_video (_encoder);
-       }
+       _player->connect_video (_delay_line);
+       _delay_line->connect_video (_matcher);
+       _matcher->connect_video (_encoder);
        
-       if (_matcher && _delay_line && f->has_audio ()) {
-               _player->connect_audio (_delay_line);
-               _delay_line->connect_audio (_matcher);
-               _matcher->connect_audio (_gain);
-               _gain->connect_audio (_encoder);
-       }
+       _player->connect_audio (_delay_line);
+       _delay_line->connect_audio (_matcher);
+       _matcher->connect_audio (_gain);
+       _gain->connect_audio (_encoder);
 }
 
 void
@@ -86,15 +79,9 @@ Transcoder::go ()
                _player->set_progress (_job);
        }
 
-       if (_delay_line) {
-               _delay_line->process_end ();
-       }
-       if (_matcher) {
-               _matcher->process_end ();
-       }
-       if (_gain) {
-               _gain->process_end ();
-       }
+       _delay_line->process_end ();
+       _matcher->process_end ();
+       _gain->process_end ();
        _encoder->process_end ();
 }
 
index ad08c6ab47fed8c022f4bd2b26df04e0c4b996d2..56932720c24a1c1f777a21cd11f08641e2918368 100644 (file)
@@ -976,3 +976,22 @@ FrameRateConversion::FrameRateConversion (float source, int dcp)
                }
        }
 }
+
+LocaleGuard::LocaleGuard ()
+       : _old (0)
+{
+       char const * old = setlocale (LC_NUMERIC, 0);
+
+        if (old) {
+                _old = strdup (old);
+                if (strcmp (_old, "POSIX")) {
+                        setlocale (LC_NUMERIC, "POSIX");
+                }
+        }
+}
+
+LocaleGuard::~LocaleGuard ()
+{
+       setlocale (LC_NUMERIC, _old);
+       free (_old);
+}
index 065801a889b3888769794522d80c094b91782eff..02cc742aa52f62a8fac1a467d1c7a44b310f8625 100644 (file)
@@ -198,5 +198,16 @@ private:
 extern int64_t video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second);
 extern std::pair<std::string, int> cpu_info ();
 
+class LocaleGuard
+{
+public:
+       LocaleGuard ();
+       ~LocaleGuard ();
+       
+private:
+       char* _old;
+};
+
+
 #endif
 
index 99d711693e44fa942b268375f7f2f6bfda955c3e..fd82384416b6166a461b0f9d9d43e1430ae2695b 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "i18n.h"
 
+using std::cout;
 using boost::shared_ptr;
 using boost::optional;
 
@@ -43,51 +44,17 @@ VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
  *  @param t Time of the frame within the source, in seconds.
  */
 void
-VideoDecoder::emit_video (shared_ptr<Image> image, double t)
+VideoDecoder::emit_video (shared_ptr<Image> image, bool same, double t)
 {
        shared_ptr<Subtitle> sub;
        if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
                sub = _timed_subtitle->subtitle ();
        }
 
-       signal_video (image, false, sub, t);
-}
-
-bool
-VideoDecoder::have_last_video () const
-{
-       return _last_image;
-}
-
-/** Called by subclasses to repeat the last video frame that we
- *  passed to emit_video().  If emit_video hasn't yet been called,
- *  we will generate a black frame.
- */
-void
-VideoDecoder::repeat_last_video (double t)
-{
-       if (!_last_image) {
-               _last_image.reset (new SimpleImage (pixel_format(), native_size(), true));
-               _last_image->make_black ();
-       }
-
-       signal_video (_last_image, true, _last_subtitle, t);
-}
-
-/** Emit our signal to say that some video data is ready.
- *  @param image Video frame.
- *  @param same true if `image' is the same as the last one we emitted.
- *  @param sub Subtitle for this frame, or 0.
- */
-void
-VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub, double t)
-{
        TIMING (N_("Decoder emits %1"), _video_frame);
-       Video (image, same, sub);
+       Video (image, same, sub, t);
        ++_video_frame;
 
-       _last_image = image;
-       _last_subtitle = sub;
        _last_content_time = t;
 }
 
index 23817c05536f4c568cef8cdc7a8358ec3991c074..0b05b2f7143690237754d810d8557d8e80cb9d77 100644 (file)
@@ -25,7 +25,7 @@
 
 class VideoContent;
 
-class VideoDecoder : public VideoSource, public virtual Decoder
+class VideoDecoder : public TimedVideoSource, public virtual Decoder
 {
 public:
        VideoDecoder (boost::shared_ptr<const Film>);
@@ -56,21 +56,14 @@ protected:
        
        virtual PixelFormat pixel_format () const = 0;
 
-       void emit_video (boost::shared_ptr<Image>, double);
+       void emit_video (boost::shared_ptr<Image>, bool, double);
        void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
-       bool have_last_video () const;
-       void repeat_last_video (double);
 
 private:
-       void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
-
        int _video_frame;
        double _last_content_time;
        
        boost::shared_ptr<TimedSubtitle> _timed_subtitle;
-
-       boost::shared_ptr<Image> _last_image;
-       boost::shared_ptr<Subtitle> _last_subtitle;
 };
 
 #endif
index c68651005121c1e3afbe14c606f54eceddc61668..167d3980eb020531457f8240ddc99803483f3343 100644 (file)
@@ -37,4 +37,16 @@ public:
        virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0;
 };
 
+class TimedVideoSink
+{
+public:
+       /** Call with a frame of video.
+        *  @param i Video frame image.
+        *  @param same true if i is the same as last time we were called.
+        *  @param s A subtitle that should be on this frame, or 0.
+        *  @param t Source timestamp.
+        */
+       virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double t) = 0;
+};
+
 #endif
index 1c4d6466cbd5c159bbcdd81de23745821b700080..ccb76f02018a45268ed74d22d1d3b344b53cfea5 100644 (file)
@@ -41,3 +41,9 @@ VideoSource::connect_video (shared_ptr<VideoSink> s)
        */
        Video.connect (bind (process_video_proxy, boost::weak_ptr<VideoSink> (s), _1, _2, _3));
 }
+
+void
+TimedVideoSource::connect_video (shared_ptr<TimedVideoSink> s)
+{
+       Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4));
+}
index e7c9805ef613d527d00f374d141b475ab36d9b4e..d2aa045a715e858fa8854fc22a2ccda098aa7908 100644 (file)
 #include "util.h"
 
 class VideoSink;
+class TimedVideoSink;
 class Subtitle;
 class Image;
 
 /** @class VideoSource
- *  @param A class that emits video data.
+ *  @param A class that emits video data without timestamps.
  */
 class VideoSource
 {
@@ -49,4 +50,22 @@ public:
        void connect_video (boost::shared_ptr<VideoSink>);
 };
 
+/** @class TimedVideoSource
+ *  @param A class that emits video data with timestamps.
+ */
+class TimedVideoSource
+{
+public:
+
+       /** Emitted when a video frame is ready.
+        *  First parameter is the video image.
+        *  Second parameter is true if the image is the same as the last one that was emitted.
+        *  Third parameter is either 0 or a subtitle that should be on this frame.
+        *  Fourth parameter is the source timestamp of this frame.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double)> Video;
+
+       void connect_video (boost::shared_ptr<TimedVideoSink>);
+};
+
 #endif
index cba19c07c2ba6d093d6e420d33f13f4803bc47e0..688be3bd06ab163158b785e65e7b0db61354d0a7 100644 (file)
@@ -56,6 +56,10 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        : wxPanel (p)
        , _panel (new wxPanel (this))
        , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
+       , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
+       , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
+       , _frame (new wxStaticText (this, wxID_ANY, wxT("")))
+       , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
        , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
        , _display_frame_x (0)
        , _got_frame (false)
@@ -71,11 +75,23 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        _v_sizer->Add (_panel, 1, wxEXPAND);
 
        wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
+
+       wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
+       time_sizer->Add (_frame, 0, wxEXPAND);
+       time_sizer->Add (_timecode, 0, wxEXPAND);
+
+       h_sizer->Add (_back_button, 0, wxALL, 2);
+       h_sizer->Add (time_sizer, 0, wxEXPAND);
+       h_sizer->Add (_forward_button, 0, wxALL, 2);
        h_sizer->Add (_play_button, 0, wxEXPAND);
        h_sizer->Add (_slider, 1, wxEXPAND);
 
        _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
 
+       _frame->SetMinSize (wxSize (84, -1));
+       _back_button->SetMinSize (wxSize (32, -1));
+       _forward_button->SetMinSize (wxSize (32, -1));
+
        _panel->Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (FilmViewer::paint_panel), 0, this);
        _panel->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (FilmViewer::panel_sized), 0, this);
        _slider->Connect (wxID_ANY, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
@@ -83,6 +99,8 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        _slider->Connect (wxID_ANY, wxEVT_SCROLL_PAGEDOWN, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
        _play_button->Connect (wxID_ANY, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler (FilmViewer::play_clicked), 0, this);
        _timer.Connect (wxID_ANY, wxEVT_TIMER, wxTimerEventHandler (FilmViewer::timer), 0, this);
+       _back_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmViewer::back_clicked), 0, this);
+       _forward_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmViewer::forward_clicked), 0, this);
 
        set_film (f);
 
@@ -100,13 +118,11 @@ FilmViewer::film_changed (Film::Property p)
                update_from_raw ();
                break;
        case Film::CONTENT:
-       {
                calculate_sizes ();
                get_frame ();
                _panel->Refresh ();
                _v_sizer->Layout ();
                break;
-       }
        case Film::WITH_SUBTITLES:
        case Film::SUBTITLE_OFFSET:
        case Film::SUBTITLE_SCALE:
@@ -144,12 +160,11 @@ FilmViewer::set_film (shared_ptr<Film> f)
 
        _player = f->player ();
        _player->disable_audio ();
-       _player->disable_video_sync ();
        /* Don't disable subtitles here as we may need them, and it's nice to be able to turn them
           on and off without needing obtain a new Player.
        */
        
-       _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
+       _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
        
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
        _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
@@ -326,7 +341,7 @@ FilmViewer::calculate_sizes ()
        Format const * format = _film->format ();
        
        float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
-       float const film_ratio = format ? format->container_ratio_as_float () : 1.78;
+       float const film_ratio = format ? format->container_ratio () : 1.78;
                        
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
@@ -376,7 +391,7 @@ FilmViewer::check_play_state ()
 }
 
 void
-FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub)
+FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub, double t)
 {
        _raw_frame = image;
        _raw_sub = sub;
@@ -384,6 +399,19 @@ FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> s
        raw_to_display ();
 
        _got_frame = true;
+
+       double const fps = _film->video_frame_rate ();
+       _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps))));
+
+       double w = t;
+       int const h = (w / 3600);
+       w -= h * 3600;
+       int const m = (w / 60);
+       w -= m * 60;
+       int const s = floor (w);
+       w -= s;
+       int const f = rint (w * fps);
+       _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f));
 }
 
 /** Get a new _raw_frame from the decoder and then do
@@ -447,3 +475,29 @@ FilmViewer::film_content_changed (weak_ptr<Content>, int p)
                slider_moved (ev);
        }
 }
+
+void
+FilmViewer::back_clicked (wxCommandEvent &)
+{
+       if (!_player) {
+               return;
+       }
+       
+       _player->seek_back ();
+       get_frame ();
+       _panel->Refresh ();
+       _panel->Update ();
+}
+
+void
+FilmViewer::forward_clicked (wxCommandEvent &)
+{
+       if (!_player) {
+               return;
+       }
+
+       _player->seek_forward ();
+       get_frame ();
+       _panel->Refresh ();
+       _panel->Update ();
+}
index c81c65acd782d4deb26cb1e7c37c1c540e308955..814a095af374753ac17a596651e9397211b0b5e0 100644 (file)
@@ -67,7 +67,7 @@ private:
        void slider_moved (wxScrollEvent &);
        void play_clicked (wxCommandEvent &);
        void timer (wxTimerEvent &);
-       void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
+       void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
        void calculate_sizes ();
        void check_play_state ();
        void update_from_raw ();
@@ -75,6 +75,8 @@ private:
        void raw_to_display ();
        void get_frame ();
        void active_jobs_changed (bool);
+       void back_clicked (wxCommandEvent &);
+       void forward_clicked (wxCommandEvent &);
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
@@ -82,6 +84,10 @@ private:
        wxSizer* _v_sizer;
        wxPanel* _panel;
        wxSlider* _slider;
+       wxButton* _back_button;
+       wxButton* _forward_button;
+       wxStaticText* _frame;
+       wxStaticText* _timecode;
        wxToggleButton* _play_button;
        wxTimer _timer;
 
index a2c0de25048443675eb61dc55c4a6a80c8fdb953..46fadd5703cb8eacb155b197337ba5ab41d4530a 100644 (file)
@@ -28,7 +28,6 @@
 #include "job_manager.h"
 #include "util.h"
 #include "exceptions.h"
-#include "delay_line.h"
 #include "image.h"
 #include "log.h"
 #include "dcp_video_frame.h"
@@ -240,11 +239,13 @@ BOOST_AUTO_TEST_CASE (format_test)
        
        Format const * f = Format::from_nickname ("Flat");
        BOOST_CHECK (f);
-//     BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
+       BOOST_CHECK_EQUAL (f->dcp_size().width, 1998);
+       BOOST_CHECK_EQUAL (f->dcp_size().height, 1080);
        
        f = Format::from_nickname ("Scope");
        BOOST_CHECK (f);
-//     BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
+       BOOST_CHECK_EQUAL (f->dcp_size().width, 2048);
+       BOOST_CHECK_EQUAL (f->dcp_size().height, 858);
 }
 
 BOOST_AUTO_TEST_CASE (util_test)
@@ -270,101 +271,6 @@ public:
        void do_log (string) {}
 };
 
-void
-do_positive_delay_line_test (int delay_length, int data_length)
-{
-       shared_ptr<NullLog> log (new NullLog);
-       
-       DelayLine d (log, 6, delay_length);
-       shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length));
-
-       int in = 0;
-       int out = 0;
-       int returned = 0;
-       int zeros = 0;
-       
-       for (int i = 0; i < 64; ++i) {
-               for (int j = 0; j < data_length; ++j) {
-                       for (int c = 0; c < 6; ++c ) {
-                               data->data(c)[j] = in;
-                               ++in;
-                       }
-               }
-
-               /* This only works because the delay line modifies the parameter */
-               d.process_audio (data);
-               returned += data->frames ();
-
-               for (int j = 0; j < data->frames(); ++j) {
-                       if (zeros < delay_length) {
-                               for (int c = 0; c < 6; ++c) {
-                                       BOOST_CHECK_EQUAL (data->data(c)[j], 0);
-                               }
-                               ++zeros;
-                       } else {
-                               for (int c = 0; c < 6; ++c) {
-                                       BOOST_CHECK_EQUAL (data->data(c)[j], out);
-                                       ++out;
-                               }
-                       }
-               }
-       }
-
-       BOOST_CHECK_EQUAL (returned, 64 * data_length);
-}
-
-void
-do_negative_delay_line_test (int delay_length, int data_length)
-{
-       shared_ptr<NullLog> log (new NullLog);
-
-       DelayLine d (log, 6, delay_length);
-       shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length));
-
-       int in = 0;
-       int out = -delay_length * 6;
-       int returned = 0;
-       
-       for (int i = 0; i < 256; ++i) {
-               data->set_frames (data_length);
-               for (int j = 0; j < data_length; ++j) {
-                       for (int c = 0; c < 6; ++c) {
-                               data->data(c)[j] = in;
-                               ++in;
-                       }
-               }
-
-               /* This only works because the delay line modifies the parameter */
-               d.process_audio (data);
-               returned += data->frames ();
-
-               for (int j = 0; j < data->frames(); ++j) {
-                       for (int c = 0; c < 6; ++c) {
-                               BOOST_CHECK_EQUAL (data->data(c)[j], out);
-                               ++out;
-                       }
-               }
-       }
-
-       returned += -delay_length;
-       BOOST_CHECK_EQUAL (returned, 256 * data_length);
-}
-
-BOOST_AUTO_TEST_CASE (delay_line_test)
-{
-       do_positive_delay_line_test (64, 128);
-       do_positive_delay_line_test (128, 64);
-       do_positive_delay_line_test (3, 512);
-       do_positive_delay_line_test (512, 3);
-
-       do_positive_delay_line_test (0, 64);
-
-       do_negative_delay_line_test (-64, 128);
-       do_negative_delay_line_test (-128, 64);
-       do_negative_delay_line_test (-3, 512);
-       do_negative_delay_line_test (-512, 3);
-}
-
 BOOST_AUTO_TEST_CASE (md5_digest_test)
 {
        string const t = md5_digest ("test/md5.test");
diff --git a/wscript b/wscript
index f784d760dd40222821f89b78da17cf4c5a5bf26f..5288cab14141bce30f51ff60e979ddcf5dd7f3dc 100644 (file)
--- a/wscript
+++ b/wscript
@@ -3,7 +3,7 @@ import os
 import sys
 
 APPNAME = 'dcpomatic'
-VERSION = '0.84pre'
+VERSION = '0.85pre'
 
 def options(opt):
     opt.load('compiler_cxx')
@@ -55,7 +55,7 @@ def configure(conf):
         conf.env.append_value('CXXFLAGS', '-O2')
 
     if not conf.options.static:
-        conf.check_cfg(package = 'libdcp', atleast_version = '0.41', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+        conf.check_cfg(package = 'libdcp', atleast_version = '0.45', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
         conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
         conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
         conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)