Merge master branch.
authorCarl Hetherington <cth@carlh.net>
Wed, 23 Jan 2013 20:15:13 +0000 (20:15 +0000)
committerCarl Hetherington <cth@carlh.net>
Wed, 23 Jan 2013 20:15:13 +0000 (20:15 +0000)
55 files changed:
NOTES [new file with mode: 0644]
src/lib/ab_transcode_job.cc
src/lib/ab_transcode_job.h
src/lib/ab_transcoder.cc
src/lib/ab_transcoder.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/check_hashes_job.cc
src/lib/check_hashes_job.h
src/lib/config.cc
src/lib/config.h
src/lib/dcp_video_frame.cc
src/lib/dcp_video_frame.h
src/lib/decoder.cc
src/lib/decoder.h
src/lib/decoder_factory.cc
src/lib/decoder_factory.h
src/lib/encoder.cc
src/lib/encoder.h
src/lib/examine_content_job.cc
src/lib/external_audio_decoder.cc
src/lib/external_audio_decoder.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/film.cc
src/lib/film.h
src/lib/filter_graph.cc
src/lib/format.cc
src/lib/image.cc
src/lib/image.h
src/lib/imagemagick_decoder.cc
src/lib/imagemagick_decoder.h
src/lib/log.cc
src/lib/matcher.cc
src/lib/options.h
src/lib/server.cc
src/lib/subtitle.cc
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/util.cc
src/lib/util.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/writer.cc [new file with mode: 0644]
src/lib/writer.h [new file with mode: 0644]
src/lib/wscript
src/tools/makedcp.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/properties_dialog.cc
test/metadata.ref
test/test.cc

diff --git a/NOTES b/NOTES
new file mode 100644 (file)
index 0000000..245017e
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,4 @@
+
+... perhaps generate the CPL hash on the fly
+Make check of hashes optional; recovery in general
+Fix multi-reel or remove it
\ No newline at end of file
index a6233c185783d42055be316361ff4e4713c76a3a..0efd277bb73799a76fdbf0f68aec04e36f7ab2d0 100644 (file)
@@ -30,12 +30,11 @@ using std::string;
 using boost::shared_ptr;
 
 /** @param f Film to compare.
- *  @param o Options.
+ *  @param o Decode options.
  */
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
+ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req)
        : Job (f, req)
-       , _decode_opt (od)
-       , _encode_opt (oe)
+       , _decode_opt (o)
 {
        _film_b.reset (new Film (*_film));
        _film_b->set_scaler (Config::instance()->reference_scaler ());
@@ -53,7 +52,7 @@ ABTranscodeJob::run ()
 {
        try {
                /* _film_b is the one with reference filters */
-               ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film, _encode_opt)));
+               ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film)));
                w.go ();
                set_progress (1);
                set_state (FINISHED_OK);
index 86a2a81b8b2b0e4416503eb00ae1c7025f5dd5a2..983842038f936cb4ad0f0e63aeef8600e82712aa 100644 (file)
 
 #include <boost/shared_ptr.hpp>
 #include "job.h"
+#include "options.h"
 
 class Film;
-class DecodeOptions;
-class EncodeOptions;
 
 /** @class ABTranscodeJob
  *  @brief Job to run a transcoder which produces output for A/B comparison of various settings.
@@ -40,8 +39,7 @@ class ABTranscodeJob : public Job
 public:
        ABTranscodeJob (
                boost::shared_ptr<Film> f,
-               boost::shared_ptr<const DecodeOptions> od,
-               boost::shared_ptr<const EncodeOptions> oe,
+               DecodeOptions o,
                boost::shared_ptr<Job> req
                );
 
@@ -49,8 +47,7 @@ public:
        void run ();
 
 private:
-       boost::shared_ptr<const DecodeOptions> _decode_opt;
-       boost::shared_ptr<const EncodeOptions> _encode_opt;
+       DecodeOptions _decode_opt;
        
        /** Copy of our Film using the reference filters and scaler */
        boost::shared_ptr<Film> _film_b;
index 53af43b5d91a2be5f259bad4d1d725fb3f18c067..fc4fb8daa44c96105f11ea14a5562bc847344ca9 100644 (file)
@@ -49,7 +49,7 @@ using boost::shared_ptr;
  */
 
 ABTranscoder::ABTranscoder (
-       shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
+       shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
        : _film_a (a)
        , _film_b (b)
        , _job (j)
index 7bfcb393cb32182d833bde5cc52eeae0e47327a1..58a08af04ca7fbe67eddd78937af60c0fd53b51d 100644 (file)
@@ -31,7 +31,6 @@ class Job;
 class Encoder;
 class VideoDecoder;
 class AudioDecoder;
-class DecodeOptions;
 class Image;
 class Log;
 class Subtitle;
@@ -51,7 +50,7 @@ public:
        ABTranscoder (
                boost::shared_ptr<Film> a,
                boost::shared_ptr<Film> b,
-               boost::shared_ptr<const DecodeOptions> o,
+               DecodeOptions o,
                Job* j,
                boost::shared_ptr<Encoder> e
                );
index 9d8de971c654356a753f85fc48b8618d49568fc9..a038dd2bb22131b8ea4d2888216acd224a0fddc1 100644 (file)
@@ -23,7 +23,7 @@
 using boost::optional;
 using boost::shared_ptr;
 
-AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
+AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
        : Decoder (f, o, j)
 {
 
index 013a6327f1d8587b3e9d4991f3603f618e995da5..3bf585f4de1f77259de867f218d66fa6fe4fe141 100644 (file)
@@ -34,7 +34,7 @@
 class AudioDecoder : public AudioSource, public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       AudioDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
 
        virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
 
index 701584c74dfb7ca7c2f0f4b32f2623d0e57c50cb..55a744552162ff5b48153e4b32aeab87e9e4afb1 100644 (file)
@@ -34,10 +34,9 @@ using std::stringstream;
 using std::ifstream;
 using boost::shared_ptr;
 
-CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
+CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req)
        : Job (f, req)
-       , _decode_opt (od)
-       , _encode_opt (oe)
+       , _decode_opt (o)
        , _bad (0)
 {
 
@@ -54,16 +53,14 @@ CheckHashesJob::run ()
 {
        _bad = 0;
 
-       if (!_film->dcp_length()) {
-               throw EncodeError ("cannot check hashes of a DCP with unknown length");
+       if (!_film->dcp_intrinsic_duration()) {
+               throw EncodeError ("cannot check hashes of a DCP with unknown intrinsic duration");
        }
        
-       SourceFrame const N = _film->dcp_trim_start() + _film->dcp_length().get();
-       DCPFrameRate const dfr = dcp_frame_rate (_film->frames_per_second ());
-       
-       for (SourceFrame i = _film->dcp_trim_start(); i < N; i += dfr.skip) {
-               string const j2k_file = _encode_opt->frame_out_path (i, false);
-               string const hash_file = _encode_opt->hash_out_path (i, false);
+       int const N = _film->dcp_intrinsic_duration().get();
+       for (int i = 0; i < N; ++i) {
+               string const j2k_file = _film->frame_out_path (i, false);
+               string const hash_file = _film->hash_out_path (i, false);
 
                if (!boost::filesystem::exists (j2k_file)) {
                        _film->log()->log (String::compose ("Frame %1 has a missing J2K file.", i));
@@ -92,13 +89,13 @@ CheckHashesJob::run ()
                shared_ptr<Job> tc;
 
                if (_film->dcp_ab()) {
-                       tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
+                       tc.reset (new ABTranscodeJob (_film, _decode_opt, shared_from_this()));
                } else {
-                       tc.reset (new TranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
+                       tc.reset (new TranscodeJob (_film, _decode_opt, shared_from_this()));
                }
                
                JobManager::instance()->add_after (shared_from_this(), tc);
-               JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, _encode_opt, tc)));
+               JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, tc)));
        }
                
        set_progress (1);
index c41af9d3f885fbfc0bb63873b3dd85a5b0c0afff..5fa17382db17cbb0935554c91e53805e1adcdf9a 100644 (file)
 */
 
 #include "job.h"
-
-class DecodeOptions;
-class EncodeOptions;
+#include "options.h"
 
 class CheckHashesJob : public Job
 {
 public:
        CheckHashesJob (
                boost::shared_ptr<Film> f,
-               boost::shared_ptr<const DecodeOptions> od,
-               boost::shared_ptr<const EncodeOptions> oe,
+               DecodeOptions od,
                boost::shared_ptr<Job> req
                );
 
@@ -37,7 +34,6 @@ public:
        std::string status () const;
 
 private:
-       boost::shared_ptr<const DecodeOptions> _decode_opt;
-       boost::shared_ptr<const EncodeOptions> _encode_opt;
+       DecodeOptions _decode_opt;
        int _bad;
 };
index 65d01bf00b64654e0e96762d6fb0f43f43e2aec6..307b9684414359ffb823a937f3febc515711db84 100644 (file)
@@ -44,6 +44,10 @@ Config::Config ()
        , _tms_path (".")
        , _sound_processor (SoundProcessor::from_id ("dolby_cp750"))
 {
+       _allowed_dcp_frame_rates.push_back (24);
+       _allowed_dcp_frame_rates.push_back (25);
+       _allowed_dcp_frame_rates.push_back (30);
+       
        ifstream f (file().c_str ());
        string line;
        while (getline (f, line)) {
index c84ce76b514bf64cf6e370c8231871857832c534..c41437efbe290f6aacad37ec5387fd21dc4d99db 100644 (file)
@@ -94,6 +94,10 @@ public:
                return _sound_processor;
        }
 
+       std::list<int> allowed_dcp_frame_rates () const {
+               return _allowed_dcp_frame_rates;
+       }
+       
        /** @param n New number of local encoding threads */
        void set_num_local_encoding_threads (int n) {
                _num_local_encoding_threads = n;
@@ -140,7 +144,11 @@ public:
        void set_tms_password (std::string p) {
                _tms_password = p;
        }
-       
+
+       void set_allowed_dcp_frame_rates (std::list<int> const & r) {
+               _allowed_dcp_frame_rates = r;
+       }
+
        void write () const;
 
        static Config* instance ();
@@ -172,6 +180,7 @@ private:
        std::string _tms_password;
        /** Our sound processor */
        SoundProcessor const * _sound_processor;
+       std::list<int> _allowed_dcp_frame_rates;
 
        /** Singleton instance, or 0 */
        static Config* _instance;
index 921a1876b5cf560fe4e95c63f6a48c6442a40299..c3f0fed03dcff3932faf25eb5a854089cf23afbf 100644 (file)
 using std::string;
 using std::stringstream;
 using std::ofstream;
+using std::cout;
 using boost::shared_ptr;
+using libdcp::Size;
 
 /** Construct a DCP video frame.
  *  @param input Input image.
  *  @param out Required size of output, in pixels (including any padding).
  *  @param s Scaler to use.
  *  @param p Number of pixels of padding either side of the image.
- *  @param f Index of the frame within the Film.
- *  @param fps Frames per second of the Film.
+ *  @param f Index of the frame within the DCP's intrinsic duration.
+ *  @param fps Frames per second of the Film's source.
  *  @param pp FFmpeg post-processing string to use.
  *  @param clut Colour look-up table to use (see Config::colour_lut_index ())
  *  @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
@@ -75,8 +77,8 @@ using boost::shared_ptr;
  */
 DCPVideoFrame::DCPVideoFrame (
        shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub,
-       libdcp::Size out, int p, int subtitle_offset, float subtitle_scale,
-       Scaler const * s, SourceFrame f, float fps, string pp, int clut, int bw, Log* l
+       Size out, int p, int subtitle_offset, float subtitle_scale,
+       Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l
        )
        : _input (yuv)
        , _subtitle (sub)
@@ -86,7 +88,7 @@ DCPVideoFrame::DCPVideoFrame (
        , _subtitle_scale (subtitle_scale)
        , _scaler (s)
        , _frame (f)
-       , _frames_per_second (dcp_frame_rate(fps).frames_per_second)
+       , _frames_per_second (DCPFrameRate(fps).frames_per_second)
        , _post_process (pp)
        , _colour_lut (clut)
        , _j2k_bandwidth (bw)
@@ -371,14 +373,41 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
        return e;
 }
 
+EncodedData::EncodedData (int s)
+       : _data (new uint8_t[s])
+       , _size (s)
+{
+
+}
+
+EncodedData::EncodedData (string file)
+{
+       _size = boost::filesystem::file_size (file);
+       _data = new uint8_t[_size];
+
+       FILE* f = fopen (file.c_str(), "rb");
+       if (!f) {
+               throw FileError ("could not open file for reading", file);
+       }
+       
+       fread (_data, 1, _size, f);
+       fclose (f);
+}
+
+
+EncodedData::~EncodedData ()
+{
+       delete[] _data;
+}
+
 /** Write this data to a J2K file.
- *  @param opt Options.
- *  @param frame Frame index.
+ *  @param Film Film.
+ *  @param frame DCP frame index.
  */
 void
-EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
+EncodedData::write (shared_ptr<const Film> film, int frame) const
 {
-       string const tmp_j2k = opt->frame_out_path (frame, true);
+       string const tmp_j2k = film->frame_out_path (frame, true);
 
        FILE* f = fopen (tmp_j2k.c_str (), "wb");
        
@@ -389,16 +418,18 @@ EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
        fwrite (_data, 1, _size, f);
        fclose (f);
 
-       string const real_j2k = opt->frame_out_path (frame, false);
+       string const real_j2k = film->frame_out_path (frame, false);
 
        /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
        boost::filesystem::rename (tmp_j2k, real_j2k);
+}
 
-       /* Write a file containing the hash */
-       string const hash = opt->hash_out_path (frame, false);
+void
+EncodedData::write_hash (shared_ptr<const Film> film, int frame) const
+{
+       string const hash = film->hash_out_path (frame, false);
        ofstream h (hash.c_str());
        h << md5_digest (_data, _size) << "\n";
-       h.close ();
 }
 
 /** Send this data to a socket.
@@ -413,14 +444,15 @@ EncodedData::send (shared_ptr<Socket> socket)
        socket->write (_data, _size, 30);
 }
 
-/** @param s Size of data in bytes */
-RemotelyEncodedData::RemotelyEncodedData (int s)
-       : EncodedData (new uint8_t[s], s)
+LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s)
+       : EncodedData (s)
 {
-
+       memcpy (_data, d, s);
 }
 
-RemotelyEncodedData::~RemotelyEncodedData ()
+/** @param s Size of data in bytes */
+RemotelyEncodedData::RemotelyEncodedData (int s)
+       : EncodedData (s)
 {
-       delete[] _data;
+
 }
index c0eff3f358a2581565266c4b37695577e1831a76..e988b663a80493158b552a959d8b8ec09010fea3 100644 (file)
@@ -26,7 +26,7 @@
  */
 
 class FilmState;
-class EncodeOptions;
+class Film;
 class ServerDescription;
 class Scaler;
 class Image;
@@ -39,18 +39,16 @@ class Subtitle;
 class EncodedData
 {
 public:
-       /** @param d Data (will not be freed by this class, but may be by subclasses)
-        *  @param s libdcp::Size of data, in bytes.
-        */
-       EncodedData (uint8_t* d, int s)
-               : _data (d)
-               , _size (s)
-       {}
+       /** @param s Size of data, in bytes */
+       EncodedData (int s);
+
+       EncodedData (std::string f);
 
-       virtual ~EncodedData () {}
+       virtual ~EncodedData ();
 
        void send (boost::shared_ptr<Socket> socket);
-       void write (boost::shared_ptr<const EncodeOptions>, SourceFrame);
+       void write (boost::shared_ptr<const Film>, int) const;
+       void write_hash (boost::shared_ptr<const Film>, int) const;
 
        /** @return data */
        uint8_t* data () const {
@@ -65,6 +63,10 @@ public:
 protected:
        uint8_t* _data; ///< data
        int _size;      ///< data size in bytes
+
+private:
+       /* No copy construction */
+       EncodedData (EncodedData const &);
 };
 
 /** @class LocallyEncodedData
@@ -75,12 +77,10 @@ protected:
 class LocallyEncodedData : public EncodedData
 {
 public:
-       /** @param d Data (which will not be freed by this class)
-        *  @param s libdcp::Size of data, in bytes.
+       /** @param d Data (which will be copied by this class)
+        *  @param s Size of data, in bytes.
         */
-       LocallyEncodedData (uint8_t* d, int s)
-               : EncodedData (d, s)
-       {}
+       LocallyEncodedData (uint8_t* d, int s);
 };
 
 /** @class RemotelyEncodedData
@@ -91,7 +91,6 @@ class RemotelyEncodedData : public EncodedData
 {
 public:
        RemotelyEncodedData (int s);
-       ~RemotelyEncodedData ();
 };
 
 /** @class DCPVideoFrame
@@ -108,7 +107,7 @@ class DCPVideoFrame
 public:
        DCPVideoFrame (
                boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size,
-               int, int, float, Scaler const *, SourceFrame, float, std::string, int, int, Log *
+               int, int, float, Scaler const *, int, float, std::string, int, int, Log *
                );
        
        virtual ~DCPVideoFrame ();
@@ -116,7 +115,7 @@ public:
        boost::shared_ptr<EncodedData> encode_locally ();
        boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
 
-       SourceFrame frame () const {
+       int frame () const {
                return _frame;
        }
        
@@ -125,12 +124,12 @@ private:
 
        boost::shared_ptr<const Image> _input; ///< the input image
        boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image
-       libdcp::Size _out_size;                  ///< the required size of the output, in pixels
+       libdcp::Size _out_size;                ///< the required size of the output, in pixels
        int _padding;
        int _subtitle_offset;
        float _subtitle_scale;
        Scaler const * _scaler;          ///< scaler to use
-       SourceFrame _frame;              ///< frame index within the Film's source
+       int _frame;                      ///< frame index within the DCP's intrinsic duration
        int _frames_per_second;          ///< Frames per second that we will use for the DCP (rounded)
        std::string _post_process;       ///< FFmpeg post-processing string to use
        int _colour_lut;                 ///< Colour look-up table to use
index 7066b488e6507352320c06767caaa216218c0275..fd0abee4183e540b4a4db5c5a9cb3588e5d8a34c 100644 (file)
@@ -46,10 +46,10 @@ using boost::shared_ptr;
 using boost::optional;
 
 /** @param f Film.
- *  @param o Options.
+ *  @param o Decode options.
  *  @param j Job that we are running within, or 0
  */
-Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
+Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o, Job* j)
        : _film (f)
        , _opt (o)
        , _job (j)
index 3908afa2fbfed522b24266846925fad693ee374f..cc4c87373616e6f4f469e4f3ce9018b4ee5aa929 100644 (file)
@@ -34,9 +34,9 @@
 #include "video_source.h"
 #include "audio_source.h"
 #include "film.h"
+#include "options.h"
 
 class Job;
-class DecodeOptions;
 class Image;
 class Log;
 class DelayLine;
@@ -54,7 +54,7 @@ class FilterGraph;
 class Decoder
 {
 public:
-       Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       Decoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
        virtual ~Decoder () {}
 
        virtual bool pass () = 0;
@@ -66,8 +66,8 @@ public:
 protected:
        /** our Film */
        boost::shared_ptr<Film> _film;
-       /** our options */
-       boost::shared_ptr<const DecodeOptions> _opt;
+       /** our decode options */
+       DecodeOptions _opt;
        /** associated Job, or 0 */
        Job* _job;
 
index 2a0d828e2153d4e418e8f254277f98f719a33754..c4d818f497f6d7eec7f173eea9fa445d9a666f21 100644 (file)
@@ -36,7 +36,7 @@ using boost::dynamic_pointer_cast;
 
 Decoders
 decoder_factory (
-       shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j
+       shared_ptr<Film> f, DecodeOptions o, Job* j
        )
 {
        if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
index 47d977ce7581dd966d7db70980aca574882f0be5..445a1c8a2f7a27462f6532801f51ae4810a7a8a9 100644 (file)
@@ -24,8 +24,9 @@
  *  @brief A method to create appropriate decoders for some content.
  */
 
+#include "options.h"
+
 class Film;
-class DecodeOptions;
 class Job;
 class VideoDecoder;
 class AudioDecoder;
@@ -43,7 +44,7 @@ struct Decoders {
 };
 
 extern Decoders decoder_factory (
-       boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *
+       boost::shared_ptr<Film>, DecodeOptions, Job *
        );
 
 #endif
index efedfcfef76e18bd30923a3ca193ac25a19a3129..6b14b269820589cf2288007365b0fd37befd00a5 100644 (file)
@@ -24,6 +24,7 @@
 #include <iostream>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
+#include <libdcp/picture_asset.h>
 #include "encoder.h"
 #include "util.h"
 #include "options.h"
@@ -34,7 +35,9 @@
 #include "config.h"
 #include "dcp_video_frame.h"
 #include "server.h"
+#include "format.h"
 #include "cross.h"
+#include "writer.h"
 
 using std::pair;
 using std::string;
@@ -50,41 +53,26 @@ int const Encoder::_history_size = 25;
 /** @param f Film that we are encoding.
  *  @param o Options.
  */
-Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
+Encoder::Encoder (shared_ptr<Film> f)
        : _film (f)
-       , _opt (o)
        , _just_skipped (false)
-       , _video_frame (0)
-       , _audio_frame (0)
+       , _video_frames_in (0)
+       , _video_frames_out (0)
 #ifdef HAVE_SWRESAMPLE   
        , _swr_context (0)
-#endif   
-       , _audio_frames_written (0)
-       , _process_end (false)
+#endif
+       , _have_a_real_frame (false)
+       , _terminate_encoder (false)
 {
-       if (_film->audio_stream()) {
-               /* Create sound output files with .tmp suffixes; we will rename
-                  them if and when we complete.
-               */
-               for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) {
-                       SF_INFO sf_info;
-                       sf_info.samplerate = dcp_audio_sample_rate (_film->audio_stream()->sample_rate());
-                       /* We write mono files */
-                       sf_info.channels = 1;
-                       sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
-                       SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info);
-                       if (f == 0) {
-                               throw CreateFileError (_opt->multichannel_audio_out_path (i, true));
-                       }
-                       _sound_files.push_back (f);
-               }
-       }
+       
 }
 
 Encoder::~Encoder ()
 {
-       close_sound_files ();
        terminate_worker_threads ();
+       if (_writer) {
+               _writer->finish ();
+       }
 }
 
 void
@@ -130,6 +118,8 @@ Encoder::process_begin ()
                        _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
                }
        }
+
+       _writer.reset (new Writer (_film));
 }
 
 
@@ -153,32 +143,20 @@ Encoder::process_end ()
                        }
 
                        out->set_frames (frames);
-                       write_audio (out);
+                       _writer->write (out);
                }
 
                swr_free (&_swr_context);
        }
 #endif
 
-       if (_film->audio_stream()) {
-               close_sound_files ();
-               
-               /* Rename .wav.tmp files to .wav */
-               for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) {
-                       if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) {
-                               boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false));
-                       }
-                       boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false));
-               }
-       }
-
        boost::mutex::scoped_lock lock (_worker_mutex);
 
-       _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ()));
+       _film->log()->log ("Clearing queue of " + lexical_cast<string> (_encode_queue.size ()));
 
        /* Keep waking workers until the queue is empty */
-       while (!_queue.empty ()) {
-               _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE);
+       while (!_encode_queue.empty ()) {
+               _film->log()->log ("Waking with " + lexical_cast<string> (_encode_queue.size ()), Log::VERBOSE);
                _worker_condition.notify_all ();
                _worker_condition.wait (lock);
        }
@@ -187,7 +165,7 @@ Encoder::process_end ()
        
        terminate_worker_threads ();
 
-       _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size()));
+       _film->log()->log ("Mopping up " + lexical_cast<string> (_encode_queue.size()));
 
        /* The following sequence of events can occur in the above code:
             1. a remote worker takes the last image off the queue
@@ -198,22 +176,18 @@ Encoder::process_end ()
             So just mop up anything left in the queue here.
        */
 
-       for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
+       for (list<shared_ptr<DCPVideoFrame> >::iterator i = _encode_queue.begin(); i != _encode_queue.end(); ++i) {
                _film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ()));
                try {
-                       shared_ptr<EncodedData> e = (*i)->encode_locally ();
-                       e->write (_opt, (*i)->frame ());
+                       _writer->write ((*i)->encode_locally(), (*i)->frame ());
                        frame_done ();
                } catch (std::exception& e) {
                        _film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
                }
        }
 
-       /* Now do links (or copies on windows) to duplicate frames */
-       for (list<pair<int, int> >::iterator i = _links_required.begin(); i != _links_required.end(); ++i) {
-               link (_opt->frame_out_path (i->first, false), _opt->frame_out_path (i->second, false));
-               link (_opt->hash_out_path (i->first, false), _opt->hash_out_path (i->second, false));
-       }
+       _writer->finish ();
+       _writer.reset ();
 }      
 
 /** @return an estimate of the current number of frames we are encoding per second,
@@ -241,12 +215,12 @@ Encoder::skipping () const
        return _just_skipped;
 }
 
-/** @return Number of video frames that have been received */
-SourceFrame
-Encoder::video_frame () const
+/** @return Number of video frames that have been sent out */
+int
+Encoder::video_frames_out () const
 {
        boost::mutex::scoped_lock (_history_mutex);
-       return _video_frame;
+       return _video_frames_out;
 }
 
 /** Should be called when a frame has been encoded successfully.
@@ -279,91 +253,67 @@ Encoder::frame_skipped ()
 void
 Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub)
 {
-       if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) {
-               ++_video_frame;
+       DCPFrameRate dfr (_film->frames_per_second ());
+       
+       if (dfr.skip && (_video_frames_in % 2)) {
+               ++_video_frames_in;
                return;
        }
 
-       if (_opt->video_range) {
-               pair<SourceFrame, SourceFrame> const r = _opt->video_range.get();
-               if (_video_frame < r.first || _video_frame >= r.second) {
-                       ++_video_frame;
-                       return;
-               }
-       }
-
        boost::mutex::scoped_lock lock (_worker_mutex);
 
        /* Wait until the queue has gone down a bit */
-       while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) {
-               TIMING ("decoder sleeps with queue of %1", _queue.size());
+       while (_encode_queue.size() >= _worker_threads.size() * 2 && !_terminate_encoder) {
+               TIMING ("decoder sleeps with queue of %1", _encode_queue.size());
                _worker_condition.wait (lock);
-               TIMING ("decoder wakes with queue of %1", _queue.size());
+               TIMING ("decoder wakes with queue of %1", _encode_queue.size());
        }
 
-       if (_process_end) {
+       if (_terminate_encoder) {
                return;
        }
 
        /* Only do the processing if we don't already have a file for this frame */
-       if (boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) {
+       if (boost::filesystem::exists (_film->frame_out_path (_video_frames_out, false))) {
                frame_skipped ();
                return;
        }
 
-       if (same && _last_real_frame) {
-               /* Use the last frame that we encoded.  We need to postpone doing the actual link,
-                  as on windows the link is really a copy and the reference frame might not have
-                  finished encoding yet.
-               */
-               _links_required.push_back (make_pair (_last_real_frame.get(), _video_frame));
+       if (same && _have_a_real_frame) {
+               /* Use the last frame that we encoded. */
+               _writer->repeat (_video_frames_out);
+               frame_done ();
        } else {
                /* Queue this new frame for encoding */
                pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
-               TIMING ("adding to queue of %1", _queue.size ());
-               _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+               TIMING ("adding to queue of %1", _encode_queue.size ());
+               _encode_queue.push_back (boost::shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(),
-                                                 _film->scaler(), _video_frame, _film->frames_per_second(), s.second,
+                                                 image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
+                                                 _film->subtitle_offset(), _film->subtitle_scale(),
+                                                 _film->scaler(), _video_frames_out, _film->frames_per_second(), s.second,
                                                  _film->colour_lut(), _film->j2k_bandwidth(),
                                                  _film->log()
                                                  )
                                          ));
                
                _worker_condition.notify_all ();
-               _last_real_frame = _video_frame;
+               _have_a_real_frame = true;
        }
 
-       ++_video_frame;
+       ++_video_frames_in;
+       ++_video_frames_out;
+
+       if (dfr.repeat) {
+               _writer->repeat (_video_frames_out);
+               ++_video_frames_out;
+               frame_done ();
+       }
 }
 
 void
 Encoder::process_audio (shared_ptr<AudioBuffers> data)
 {
-       if (_opt->audio_range) {
-               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (*data.get ()));
-               
-               /* Range that we are encoding */
-               pair<int64_t, int64_t> required_range = _opt->audio_range.get();
-               /* Range of this block of data */
-               pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames());
-
-               if (this_range.second < required_range.first || required_range.second < this_range.first) {
-                       /* No part of this audio is within the required range */
-                       return;
-               } else if (required_range.first >= this_range.first && required_range.first < this_range.second) {
-                       /* Trim start */
-                       int64_t const shift = required_range.first - this_range.first;
-                       trimmed->move (shift, 0, trimmed->frames() - shift);
-                       trimmed->set_frames (trimmed->frames() - shift);
-               } else if (required_range.second >= this_range.first && required_range.second < this_range.second) {
-                       /* Trim end */
-                       trimmed->set_frames (required_range.second - this_range.first);
-               }
-
-               data = trimmed;
-       }
-
 #if HAVE_SWRESAMPLE
        /* Maybe sample-rate convert */
        if (_swr_context) {
@@ -405,36 +355,14 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data)
                data = b;
        }
 
-       write_audio (data);
-       
-       _audio_frame += data->frames ();
-}
-
-void
-Encoder::write_audio (shared_ptr<const AudioBuffers> audio)
-{
-       for (int i = 0; i < audio->channels(); ++i) {
-               sf_write_float (_sound_files[i], audio->data(i), audio->frames());
-       }
-
-       _audio_frames_written += audio->frames ();
+       _writer->write (data);
 }
 
-void
-Encoder::close_sound_files ()
-{
-       for (vector<SNDFILE*>::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) {
-               sf_close (*i);
-       }
-
-       _sound_files.clear ();
-}      
-
 void
 Encoder::terminate_worker_threads ()
 {
        boost::mutex::scoped_lock lock (_worker_mutex);
-       _process_end = true;
+       _terminate_encoder = true;
        _worker_condition.notify_all ();
        lock.unlock ();
 
@@ -457,18 +385,18 @@ Encoder::encoder_thread (ServerDescription* server)
 
                TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id());
                boost::mutex::scoped_lock lock (_worker_mutex);
-               while (_queue.empty () && !_process_end) {
+               while (_encode_queue.empty () && !_terminate_encoder) {
                        _worker_condition.wait (lock);
                }
 
-               if (_process_end) {
+               if (_terminate_encoder) {
                        return;
                }
 
-               TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
-               boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+               TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _encode_queue.size());
+               boost::shared_ptr<DCPVideoFrame> vf = _encode_queue.front ();
                _film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
-               _queue.pop_front ();
+               _encode_queue.pop_front ();
                
                lock.unlock ();
 
@@ -508,14 +436,14 @@ Encoder::encoder_thread (ServerDescription* server)
                }
 
                if (encoded) {
-                       encoded->write (_opt, vf->frame ());
+                       _writer->write (encoded, vf->frame ());
                        frame_done ();
                } else {
                        lock.lock ();
                        _film->log()->log (
                                String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame())
                                );
-                       _queue.push_front (vf);
+                       _encode_queue.push_front (vf);
                        lock.unlock ();
                }
 
@@ -527,18 +455,3 @@ Encoder::encoder_thread (ServerDescription* server)
                _worker_condition.notify_all ();
        }
 }
-
-void
-Encoder::link (string a, string b) const
-{
-#ifdef DVDOMATIC_POSIX                 
-       int const r = symlink (a.c_str(), b.c_str());
-       if (r) {
-               throw EncodeError (String::compose ("could not create symlink from %1 to %2", a, b));
-       }
-#endif
-       
-#ifdef DVDOMATIC_WINDOWS
-       boost::filesystem::copy_file (a, b);
-#endif                 
-}
index 52ccfc166833f60a2e0e9a6efe4583092f15ad54..429b46a18c4f1d60191e2b40585f9abdbda04939 100644 (file)
@@ -39,18 +39,18 @@ extern "C" {
 #include <libswresample/swresample.h>
 }
 #endif
-#include <sndfile.h>
 #include "util.h"
 #include "video_sink.h"
 #include "audio_sink.h"
 
-class EncodeOptions;
 class Image;
 class Subtitle;
 class AudioBuffers;
 class Film;
 class ServerDescription;
 class DCPVideoFrame;
+class EncodedData;
+class Writer;
 
 /** @class Encoder
  *  @brief Encoder to J2K and WAV for DCP.
@@ -62,7 +62,7 @@ class DCPVideoFrame;
 class Encoder : public VideoSink, public AudioSink
 {
 public:
-       Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o);
+       Encoder (boost::shared_ptr<Film> f);
        virtual ~Encoder ();
 
        /** Called to indicate that a processing run is about to begin */
@@ -83,17 +83,20 @@ public:
 
        float current_frames_per_second () const;
        bool skipping () const;
-       SourceFrame video_frame () const;
+       int video_frames_out () const;
 
-protected:
+private:
        
        void frame_done ();
        void frame_skipped ();
        
+       void write_audio (boost::shared_ptr<const AudioBuffers> audio);
+
+       void encoder_thread (ServerDescription *);
+       void terminate_worker_threads ();
+
        /** Film that we are encoding */
-       boost::shared_ptr<const Film> _film;
-       /** Options */
-       boost::shared_ptr<const EncodeOptions> _opt;
+       boost::shared_ptr<Film> _film;
 
        /** Mutex for _time_history, _just_skipped and _last_frame */
        mutable boost::mutex _history_mutex;
@@ -107,37 +110,22 @@ protected:
        bool _just_skipped;
 
        /** Number of video frames received so far */
-       SourceFrame _video_frame;
-       /** Number of audio frames received so far */
-       int64_t _audio_frame;
-
-private:
-       void close_sound_files ();
-       void write_audio (boost::shared_ptr<const AudioBuffers> audio);
-
-       void encoder_thread (ServerDescription *);
-       void terminate_worker_threads ();
-       void link (std::string, std::string) const;
+       SourceFrame _video_frames_in;
+       /** Number of video frames written for the DCP so far */
+       int _video_frames_out;
 
 #if HAVE_SWRESAMPLE    
        SwrContext* _swr_context;
 #endif
 
-       /** List of links that we need to create when all frames have been processed;
-        *  such that we need to call link (first, second) for each member of this list.
-        *  In other words, `first' is a `real' frame and `second' should be a link to `first'.
-        */
-       std::list<std::pair<int, int> > _links_required;
-
-       std::vector<SNDFILE*> _sound_files;
-       int64_t _audio_frames_written;
-
-       boost::optional<int> _last_real_frame;
-       bool _process_end;
-       std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
+       bool _have_a_real_frame;
+       bool _terminate_encoder;
+       std::list<boost::shared_ptr<DCPVideoFrame> > _encode_queue;
        std::list<boost::thread *> _worker_threads;
        mutable boost::mutex _worker_mutex;
        boost::condition _worker_condition;
+
+       boost::shared_ptr<Writer> _writer;
 };
 
 #endif
index a783cde339f646a009d7b4c3da9eea617ace0b16..69a757e2b94bb65ad5f37128fb2c896386011f51 100644 (file)
@@ -78,8 +78,8 @@ ExamineContentJob::run ()
                _film->unset_length ();
                _film->set_crop (Crop ());
                
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               o->decode_audio = false;
+               DecodeOptions o;
+               o.decode_audio = false;
                
                Decoders decoders = decoder_factory (_film, o, this);
                
@@ -96,8 +96,7 @@ ExamineContentJob::run ()
 
                /* Get a quick decoder to get the content's length from its header */
                
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               Decoders d = decoder_factory (_film, o, 0);
+               Decoders d = decoder_factory (_film, DecodeOptions(), 0);
                _film->set_length (d.video->length());
        
                _film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get()));
index 25c8068b6fcdb02726a0b93f8163b7ef6b968e1b..36605141886aed7ec596bb024b0425bfedc23f94 100644 (file)
@@ -31,7 +31,7 @@ using std::cout;
 using boost::shared_ptr;
 using boost::optional;
 
-ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
+ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
        : Decoder (f, o, j)
        , AudioDecoder (f, o, j)
 {
index 2558955eb1985f45c396d5067a753f1109ec976d..37e53bca7f5ac1453adb54d635849a266bb384af 100644 (file)
@@ -44,7 +44,7 @@ private:
 class ExternalAudioDecoder : public AudioDecoder
 {
 public:
-       ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       ExternalAudioDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
 
        bool pass ();
 
index aff3ff666d0ce38cb689565d399cec8034cdcde4..81f40564460a035856332ae46f25cacf14d1a9dd 100644 (file)
@@ -58,8 +58,9 @@ using std::list;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
+using libdcp::Size;
 
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
        : Decoder (f, o, j)
        , VideoDecoder (f, o, j)
        , AudioDecoder (f, o, j)
@@ -78,7 +79,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions
        setup_audio ();
        setup_subtitle ();
 
-       if (!o->video_sync) {
+       if (!o.video_sync) {
                _first_video = 0;
        }
 }
@@ -239,7 +240,7 @@ FFmpegDecoder::pass ()
                        filter_and_emit_video (_frame);
                }
 
-               if (_audio_stream && _opt->decode_audio) {
+               if (_audio_stream && _opt.decode_audio) {
                        while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
                                int const data_size = av_samples_get_buffer_size (
                                        0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
@@ -267,14 +268,14 @@ FFmpegDecoder::pass ()
                                _film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size));
                        }
 
-                       if (_opt->video_sync) {
+                       if (_opt.video_sync) {
                                out_with_sync ();
                        } else {
                                filter_and_emit_video (_frame);
                        }
                }
 
-       } else if (ffa && _packet.stream_index == ffa->id() && _opt->decode_audio) {
+       } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
 
                int frame_finished;
                if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
@@ -323,7 +324,7 @@ FFmpegDecoder::pass ()
                        }
                }
                        
-       } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) {
+       } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles && _first_video) {
 
                int got_subtitle;
                AVSubtitle sub;
index 3b564b826fb90172b58f5121aed68197d3599ca4..9a4e65ebc2ccd7a4db041df1dae59f6f03ec03e9 100644 (file)
@@ -86,7 +86,7 @@ private:
 class FFmpegDecoder : public VideoDecoder, public AudioDecoder
 {
 public:
-       FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
        ~FFmpegDecoder ();
 
        float frames_per_second () const;
index 5a11b0ca969ec5bfb262168158fb3be52e52514f..ae9edbfdb0a93a954a6e7c13bc967738598430e4 100644 (file)
@@ -39,7 +39,6 @@
 #include "ab_transcode_job.h"
 #include "transcode_job.h"
 #include "scp_dcp_job.h"
-#include "make_dcp_job.h"
 #include "log.h"
 #include "options.h"
 #include "exceptions.h"
@@ -72,8 +71,9 @@ using boost::to_upper_copy;
 using boost::ends_with;
 using boost::starts_with;
 using boost::optional;
+using libdcp::Size;
 
-int const Film::state_version = 1;
+int const Film::state_version = 2;
 
 /** Construct a Film object in a given directory, reading any metadata
  *  file that exists in that directory.  An exception will be thrown if
@@ -89,8 +89,8 @@ Film::Film (string d, bool must_exist)
        , _dcp_content_type (0)
        , _format (0)
        , _scaler (Scaler::from_id ("bicubic"))
-       , _dcp_trim_start (0)
-       , _dcp_trim_end (0)
+       , _trim_start (0)
+       , _trim_end (0)
        , _dcp_ab (false)
        , _use_content_audio (true)
        , _audio_gain (0)
@@ -155,8 +155,8 @@ Film::Film (Film const & o)
        , _crop              (o._crop)
        , _filters           (o._filters)
        , _scaler            (o._scaler)
-       , _dcp_trim_start    (o._dcp_trim_start)
-       , _dcp_trim_end      (o._dcp_trim_end)
+       , _trim_start        (o._trim_start)
+       , _trim_end          (o._trim_end)
        , _dcp_ab            (o._dcp_ab)
        , _content_audio_stream (o._content_audio_stream)
        , _external_audio    (o._external_audio)
@@ -179,6 +179,7 @@ Film::Film (Film const & o)
        , _package_type      (o._package_type)
        , _size              (o._size)
        , _length            (o._length)
+       , _dcp_intrinsic_duration (o._dcp_intrinsic_duration)
        , _content_digest    (o._content_digest)
        , _content_audio_streams (o._content_audio_streams)
        , _external_audio_stream (o._external_audio_stream)
@@ -254,7 +255,9 @@ Film::make_dcp (bool transcode)
        }
        
        log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video")));
-       log()->log (String::compose ("Content length %1", length().get()));
+       if (length()) {
+               log()->log (String::compose ("Content length %1", length().get()));
+       }
        log()->log (String::compose ("Content digest %1", content_digest()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
@@ -287,47 +290,20 @@ Film::make_dcp (bool transcode)
                throw MissingSettingError ("name");
        }
 
-       shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
-       oe->out_size = format()->dcp_size ();
-       oe->padding = format()->dcp_padding (shared_from_this ());
-       if (dcp_length ()) {
-               oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
-               if (audio_stream()) {
-                       oe->audio_range = make_pair (
-
-                               video_frames_to_audio_frames (
-                                       oe->video_range.get().first,
-                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
-                                       dcp_frame_rate (frames_per_second()).frames_per_second
-                                       ),
-                               
-                               video_frames_to_audio_frames (
-                                       oe->video_range.get().second,
-                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
-                                       dcp_frame_rate (frames_per_second()).frames_per_second
-                                       )
-                               );
-               }
-                       
-       }
-       
-       oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
-
-       shared_ptr<DecodeOptions> od (new DecodeOptions);
-       od->decode_subtitles = with_subtitles ();
+       DecodeOptions od;
+       od.decode_subtitles = with_subtitles ();
 
        shared_ptr<Job> r;
 
        if (transcode) {
                if (dcp_ab()) {
-                       r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
+                       r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
                } else {
-                       r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
+                       r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
                }
        }
 
-       r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
-       JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
+       // r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, r)));
 }
 
 /** Start a job to examine our content file */
@@ -411,8 +387,8 @@ Film::write_metadata () const
                f << "filter " << (*i)->id () << "\n";
        }
        f << "scaler " << _scaler->id () << "\n";
-       f << "dcp_trim_start " << _dcp_trim_start << "\n";
-       f << "dcp_trim_end " << _dcp_trim_end << "\n";
+       f << "trim_start " << _trim_start << "\n";
+       f << "trim_end " << _trim_end << "\n";
        f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
        if (_content_audio_stream) {
                f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n";
@@ -443,6 +419,7 @@ Film::write_metadata () const
        f << "width " << _size.width << "\n";
        f << "height " << _size.height << "\n";
        f << "length " << _length.get_value_or(0) << "\n";
+       f << "dcp_intrinsic_duration " << _dcp_intrinsic_duration.get_value_or(0) << "\n";
        f << "content_digest " << _content_digest << "\n";
 
        for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
@@ -523,10 +500,10 @@ Film::read_metadata ()
                        _filters.push_back (Filter::from_id (v));
                } else if (k == "scaler") {
                        _scaler = Scaler::from_id (v);
-               } else if (k == "dcp_trim_start") {
-                       _dcp_trim_start = atoi (v.c_str ());
-               } else if (k == "dcp_trim_end") {
-                       _dcp_trim_end = atoi (v.c_str ());
+               } else if ( ((!version || version < 2) && k == "trim_start") || k == "trim_start") {
+                       _trim_start = atoi (v.c_str ());
+               } else if ( ((!version || version < 2) && k == "trim_end") || k == "trim_end") {
+                       _trim_end = atoi (v.c_str ());
                } else if (k == "dcp_ab") {
                        _dcp_ab = (v == "1");
                } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
@@ -587,6 +564,11 @@ Film::read_metadata ()
                        if (vv) {
                                _length = vv;
                        }
+               } else if (k == "dcp_intrinsic_duration") {
+                       int const vv = atoi (v.c_str ());
+                       if (vv) {
+                               _dcp_intrinsic_duration = vv;
+                       }
                } else if (k == "content_digest") {
                        _content_digest = v;
                } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
@@ -698,30 +680,25 @@ Film::target_audio_sample_rate () const
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
 
-       DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
+       DCPFrameRate dfr (frames_per_second ());
 
-       /* Compensate for the fact that video will be rounded to the
-          nearest integer number of frames per second.
+       /* 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 (dfr.run_fast) {
-               t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
+
+       if (dfr.change_speed) {
+               t *= _frames_per_second * dfr.factor() / dfr.frames_per_second;
        }
 
        return rint (t);
 }
 
-boost::optional<int>
-Film::dcp_length () const
+int
+Film::still_duration_in_frames () const
 {
-       if (content_type() == STILL) {
-               return _still_duration * frames_per_second();
-       }
-       
-       if (!length()) {
-               return boost::optional<int> ();
-       }
-
-       return length().get() - dcp_trim_start() - dcp_trim_end();
+       return still_duration() * frames_per_second();
 }
 
 /** @return a DCI-compliant name for a DCP of this film */
@@ -893,8 +870,7 @@ Film::set_content (string c)
        */
 
        try {
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               Decoders d = decoder_factory (shared_from_this(), o, 0);
+               Decoders d = decoder_factory (shared_from_this(), DecodeOptions(), 0);
                
                set_size (d.video->native_size ());
                set_frames_per_second (d.video->frames_per_second ());
@@ -1069,23 +1045,23 @@ Film::set_scaler (Scaler const * s)
 }
 
 void
-Film::set_dcp_trim_start (int t)
+Film::set_trim_start (int t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_start = t;
+               _trim_start = t;
        }
-       signal_changed (DCP_TRIM_START);
+       signal_changed (TRIM_START);
 }
 
 void
-Film::set_dcp_trim_end (int t)
+Film::set_trim_end (int t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_end = t;
+               _trim_end = t;
        }
-       signal_changed (DCP_TRIM_END);
+       signal_changed (TRIM_END);
 }
 
 void
@@ -1116,8 +1092,7 @@ Film::set_external_audio (vector<string> a)
                _external_audio = a;
        }
 
-       shared_ptr<DecodeOptions> o (new DecodeOptions);
-       shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
+       shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), DecodeOptions(), 0));
        if (decoder->audio_stream()) {
                _external_audio_stream = decoder->audio_stream ();
        }
@@ -1324,7 +1299,17 @@ Film::unset_length ()
                _length = boost::none;
        }
        signal_changed (LENGTH);
-}      
+}
+
+void
+Film::set_dcp_intrinsic_duration (int d)
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _dcp_intrinsic_duration = d;
+       }
+       signal_changed (DCP_INTRINSIC_DURATION);
+}
 
 void
 Film::set_content_digest (string d)
@@ -1405,3 +1390,45 @@ Film::audio_stream () const
 
        return _external_audio_stream;
 }
+
+/** @param f DCP frame index.
+ *  @param t true to return a temporary file path, otherwise a permanent one.
+ *  @return The path to write this video frame to.
+ */
+string
+Film::frame_out_path (int f, bool t) const
+{
+       stringstream s;
+       s << j2k_dir() << "/";
+       s.width (8);
+       s << std::setfill('0') << f << ".j2c";
+
+       if (t) {
+               s << ".tmp";
+       }
+
+       return s.str ();
+}
+
+string
+Film::hash_out_path (int f, bool t) const
+{
+       return frame_out_path (f, t) + ".md5";
+}
+
+/** @param c Audio channel index.
+ *  @param t true to return a temporary file path, otherwise a permanent one.
+ *  @return The path to write this audio file to.
+ */
+string
+Film::multichannel_audio_out_path (int c, bool t) const
+{
+       stringstream s;
+       s << dir ("wavs") << "/" << (c + 1) << ".wav";
+       if (t) {
+               s << ".tmp";
+       }
+       
+       return s.str ();
+}
+
index d3530b81772ac43f117d2d4f2734778c9d0d28c9..60646b0c8d78adc52f6fa8da0740d704926ac1c9 100644 (file)
@@ -78,6 +78,10 @@ public:
        std::string file (std::string f) const;
        std::string dir (std::string d) const;
 
+       std::string frame_out_path (int f, bool t) const;
+       std::string hash_out_path (int f, bool t) const;
+       std::string multichannel_audio_out_path (int c, bool t) const;
+       
        std::string content_path () const;
        ContentType content_type () const;
        
@@ -87,10 +91,13 @@ public:
        void read_metadata ();
 
        libdcp::Size cropped_size (libdcp::Size) const;
-       boost::optional<int> dcp_length () const;
        std::string dci_name () const;
        std::string dcp_name () const;
 
+       boost::optional<int> dcp_intrinsic_duration () const {
+               return _dcp_intrinsic_duration;
+       }
+
        /** @return true if our state has changed since we last saved it */
        bool dirty () const {
                return _dirty;
@@ -114,8 +121,8 @@ public:
                CROP,
                FILTERS,
                SCALER,
-               DCP_TRIM_START,
-               DCP_TRIM_END,
+               TRIM_START,
+               TRIM_END,
                DCP_AB,
                CONTENT_AUDIO_STREAM,
                EXTERNAL_AUDIO,
@@ -132,6 +139,7 @@ public:
                DCI_METADATA,
                SIZE,
                LENGTH,
+               DCP_INTRINSIC_DURATION,
                CONTENT_AUDIO_STREAMS,
                SUBTITLE_STREAMS,
                FRAMES_PER_SECOND,
@@ -190,14 +198,14 @@ public:
                return _scaler;
        }
 
-       SourceFrame dcp_trim_start () const {
+       int trim_start () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_trim_start;
+               return _trim_start;
        }
 
-       SourceFrame dcp_trim_end () const {
+       int trim_end () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_trim_end;
+               return _trim_end;
        }
 
        bool dcp_ab () const {
@@ -235,6 +243,8 @@ public:
                return _still_duration;
        }
 
+       int still_duration_in_frames () const;
+
        boost::shared_ptr<SubtitleStream> subtitle_stream () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _subtitle_stream;
@@ -353,8 +363,8 @@ public:
        void set_bottom_crop (int);
        void set_filters (std::vector<Filter const *>);
        void set_scaler (Scaler const *);
-       void set_dcp_trim_start (int);
-       void set_dcp_trim_end (int);
+       void set_trim_start (int);
+       void set_trim_end (int);
        void set_dcp_ab (bool);
        void set_content_audio_stream (boost::shared_ptr<AudioStream>);
        void set_external_audio (std::vector<std::string>);
@@ -378,6 +388,7 @@ public:
        void set_size (libdcp::Size);
        void set_length (SourceFrame);
        void unset_length ();
+       void set_dcp_intrinsic_duration (int);
        void set_content_digest (std::string);
        void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
        void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
@@ -434,9 +445,9 @@ private:
        /** Scaler algorithm to use */
        Scaler const * _scaler;
        /** Frames to trim off the start of the DCP */
-       int _dcp_trim_start;
+       int _trim_start;
        /** Frames to trim off the end of the DCP */
-       int _dcp_trim_end;
+       int _trim_end;
        /** true to create an A/B comparison DCP, where the left half of the image
            is the video without any filters or post-processing, and the right half
            has the specified filters and post-processing.
@@ -484,10 +495,11 @@ private:
 
        /* Data which are cached to speed things up */
 
-       /** libdcp::Size, in pixels, of the source (ignoring cropping) */
+       /** Size, in pixels, of the source (ignoring cropping) */
        libdcp::Size _size;
        /** The length of the source, in video frames (as far as we know) */
        boost::optional<SourceFrame> _length;
+       boost::optional<int> _dcp_intrinsic_duration;
        /** MD5 digest of our content file */
        std::string _content_digest;
        /** The audio streams in our content */
index 6cd7dc2cbb52f38e8f9eb9db971a1f8d4bdc2d7e..3a13d93d069c5f7b0bd84545e4a560089056092f 100644 (file)
@@ -47,6 +47,7 @@ using std::stringstream;
 using std::string;
 using std::list;
 using boost::shared_ptr;
+using libdcp::Size;
 
 /** Construct a FilterGraph for the settings in a film.
  *  @param film Film.
index 088a16059d7f68ac4432f029afacd0ab0b966cb5..4583dd0e58602840c95cb5b1724c5ab557a3fb6a 100644 (file)
@@ -35,6 +35,7 @@ using std::setprecision;
 using std::stringstream;
 using std::vector;
 using boost::shared_ptr;
+using libdcp::Size;
 
 vector<Format const *> Format::_formats;
 
index feda09ec578bb41baeda31f033c7b770d650a45a..9223fdc5d0019cdfecbe2a2e20930c8546a7c426 100644 (file)
@@ -42,6 +42,7 @@ extern "C" {
 
 using namespace std;
 using namespace boost;
+using libdcp::Size;
 
 void
 Image::swap (Image& other)
@@ -95,11 +96,15 @@ Image::components () const
 }
 
 shared_ptr<Image>
-Image::scale (libdcp::Size out_size, Scaler const * scaler, bool aligned) const
+Image::scale (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) const
 {
        assert (scaler);
+       /* Empirical testing suggests that sws_scale() will crash if
+          the input image is not aligned.
+       */
+       assert (aligned ());
 
-       shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned));
+       shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, result_aligned));
 
        struct SwsContext* scale_context = sws_getContext (
                size().width, size().height, pixel_format(),
@@ -124,14 +129,18 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, bool aligned) const
  *  @param scaler Scaler to use.
  */
 shared_ptr<Image>
-Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool aligned) const
+Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool result_aligned) const
 {
        assert (scaler);
+       /* Empirical testing suggests that sws_scale() will crash if
+          the input image is not aligned.
+       */
+       assert (aligned ());
 
        libdcp::Size content_size = out_size;
        content_size.width -= (padding * 2);
 
-       shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, aligned));
+       shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, result_aligned));
 
        struct SwsContext* scale_context = sws_getContext (
                size().width, size().height, pixel_format(),
@@ -152,7 +161,7 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons
           scheme of things.
        */
        if (padding > 0) {
-               shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, aligned));
+               shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned));
                padded_rgb->make_black ();
 
                /* XXX: we are cheating a bit here; we know the frame is RGB so we can
@@ -244,13 +253,25 @@ Image::make_black ()
 {
        switch (_pixel_format) {
        case PIX_FMT_YUV420P:
-       case PIX_FMT_YUV422P10LE:
        case PIX_FMT_YUV422P:
                memset (data()[0], 0, lines(0) * stride()[0]);
-               memset (data()[1], 0x80, lines(1) * stride()[1]);
-               memset (data()[2], 0x80, lines(2) * stride()[2]);
+               memset (data()[1], 0x7f, lines(1) * stride()[1]);
+               memset (data()[2], 0x7f, lines(2) * stride()[2]);
                break;
 
+       case PIX_FMT_YUV422P10LE:
+               memset (data()[0], 0, lines(0) * stride()[0]);
+               for (int i = 1; i < 3; ++i) {
+                       int16_t* p = reinterpret_cast<int16_t*> (data()[i]);
+                       for (int y = 0; y < size().height; ++y) {
+                               for (int x = 0; x < line_size()[i] / 2; ++x) {
+                                       p[x] = (1 << 9) - 1;
+                               }
+                               p += stride()[i] / 2;
+                       }
+               }
+               break;
+               
        case PIX_FMT_RGB24:             
                memset (data()[0], 0, lines(0) * stride()[0]);
                break;
@@ -349,7 +370,7 @@ Image::bytes_per_pixel (int c) const
                        return 0.5;
                }
        case PIX_FMT_YUV422P10LE:
-               if (c == 1) {
+               if (c == 0) {
                        return 2;
                } else {
                        return 1;
@@ -472,6 +493,12 @@ SimpleImage::size () const
        return _size;
 }
 
+bool
+SimpleImage::aligned () const
+{
+       return _aligned;
+}
+
 FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b)
        : Image (p)
        , _buffer (b)
@@ -509,6 +536,13 @@ FilterBufferImage::size () const
        return libdcp::Size (_buffer->video->w, _buffer->video->h);
 }
 
+bool
+FilterBufferImage::aligned () const
+{
+       /* XXX? */
+       return true;
+}
+
 RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im)
        : SimpleImage (im->pixel_format(), im->size(), false)
 {
index adee8bc4d1e5196d9c3e086322b1e68c9c3b00c6..23f13a648c8b8cd29d16d1c25772d3dd50b89bf1 100644 (file)
@@ -65,9 +65,11 @@ public:
        /** @return Array of strides for each line (including any alignment padding bytes) */
        virtual int * stride () const = 0;
 
-       /** @return libdcp::Size of the image, in pixels */
+       /** @return Size of the image, in pixels */
        virtual libdcp::Size size () const = 0;
 
+       virtual bool aligned () const = 0;
+
        int components () const;
        int lines (int) const;
 
@@ -107,6 +109,7 @@ public:
        int * line_size () const;
        int * stride () const;
        libdcp::Size size () const;
+       bool aligned () const;
 
 private:
        /* Not allowed */
@@ -131,6 +134,7 @@ public:
        int * line_size () const;
        int * stride () const;
        libdcp::Size size () const;
+       bool aligned () const;
 
 protected:
        void allocate ();
index 5ebd6c8e1ccaba8ba498b6d453ad127573d6c0c3..99b9e1d340c47e5bfe8784b568ec486da754bb87 100644 (file)
 
 using std::cout;
 using boost::shared_ptr;
+using libdcp::Size;
 
 ImageMagickDecoder::ImageMagickDecoder (
-       boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
+       boost::shared_ptr<Film> f, DecodeOptions o, Job* j)
        : Decoder (f, o, j)
        , VideoDecoder (f, o, j)
 {
@@ -70,7 +71,7 @@ bool
 ImageMagickDecoder::pass ()
 {
        if (_iter == _files.end()) {
-               if (!_film->dcp_length() || video_frame() >= _film->dcp_length().get()) {
+               if (video_frame() >= _film->still_duration_in_frames()) {
                        return true;
                }
 
@@ -97,7 +98,7 @@ ImageMagickDecoder::pass ()
 
        delete magick_image;
 
-       image = image->crop (_film->crop(), false);
+       image = image->crop (_film->crop(), true);
        
        emit_video (image, 0);
 
index c4795b003732dd3c500d5e326df6de7fbecca131..84a6f15f9aa8b63d762ada2deb5ac94ccd756646 100644 (file)
@@ -26,7 +26,7 @@ namespace Magick {
 class ImageMagickDecoder : public VideoDecoder
 {
 public:
-       ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
 
        float frames_per_second () const {
                /* We don't know */
index 06cff04956b8b1a28fde9ff31c493def5a5930f7..7459700eacd43b783da9b61673866f8535c929b4 100644 (file)
@@ -28,7 +28,7 @@
 using namespace std;
 
 Log::Log ()
-       : _level (VERBOSE)
+       : _level (STANDARD)
 {
 
 }
index 2b7a080fc91aea71f522570205cd5053246d7cce..182fb306c695d1008b4b8be8d50732af4cddda55 100644 (file)
@@ -81,7 +81,7 @@ Matcher::process_end ()
                
                _log->log (String::compose ("Emitting %1 frames of black video", black_video_frames));
 
-               shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), false));
+               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>());
index 2f2f44b641e26ad37644beffaa4d2789aa5f653c..2cd7dffdedaff57a9ba8bc107045dc710d0bbb03 100644 (file)
 
 */
 
-/** @file src/options.h
- *  @brief Options for a transcoding operation.
- */
-
-#include <string>
-#include <iomanip>
-#include <sstream>
-#include <boost/optional.hpp>
-#include "util.h"
+#ifndef DVDOMATIC_OPTIONS_H
+#define DVDOMATIC_OPTIONS_H
 
-/** @class EncodeOptions
- *  @brief EncodeOptions for an encoding operation.
- *
- *  These are settings which may be different, in different circumstances, for
- *  the same film; ie they are options for a particular operation.
+/** @file src/options.h
+ *  @brief Options for a decoding operation.
  */
-class EncodeOptions
-{
-public:
-
-       EncodeOptions (std::string f, std::string e, std::string m)
-               : padding (0)
-               , video_skip (0)
-               , _frame_out_path (f)
-               , _frame_out_extension (e)
-               , _multichannel_audio_out_path (m)
-       {}
-
-       /** @return The path to write video frames to */
-       std::string frame_out_path () const {
-               return _frame_out_path;
-       }
-
-       /** @param f Source frame index.
-        *  @param t true to return a temporary file path, otherwise a permanent one.
-        *  @return The path to write this video frame to.
-        */
-       std::string frame_out_path (SourceFrame f, bool t) const {
-               std::stringstream s;
-               s << _frame_out_path << "/";
-               s.width (8);
-               s << std::setfill('0') << f << _frame_out_extension;
-
-               if (t) {
-                       s << ".tmp";
-               }
-
-               return s.str ();
-       }
-
-       std::string hash_out_path (SourceFrame f, bool t) const {
-               return frame_out_path (f, t) + ".md5";
-       }
-
-       /** @return Path to write multichannel audio data to */
-       std::string multichannel_audio_out_path () const {
-               return _multichannel_audio_out_path;
-       }
-
-       /** @param c Audio channel index.
-        *  @param t true to return a temporary file path, otherwise a permanent one.
-        *  @return The path to write this audio file to.
-        */
-       std::string multichannel_audio_out_path (int c, bool t) const {
-               std::stringstream s;
-               s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav";
-               if (t) {
-                       s << ".tmp";
-               }
-
-               return s.str ();
-       }
-
-       libdcp::Size out_size;      ///< size of output images
-       int padding;                ///< number of pixels of padding (in terms of the output size) each side of the image
-
-       /** Range of video frames to encode (in DCP frames) */
-       boost::optional<std::pair<int, int> > video_range;
-       /** Range of audio frames to decode (in the DCP's sampling rate) */
-       boost::optional<std::pair<int64_t, int64_t> > audio_range;
-       
-       /** Skip frames such that we don't decode any frame where (index % decode_video_skip) != 0; e.g.
-        *  1 for every frame, 2 for every other frame, etc.
-        */
-       SourceFrame video_skip; 
-
-private:
-       /** Path of the directory to write video frames to */
-       std::string _frame_out_path;
-       /** Extension to use for video frame files (including the leading .) */
-       std::string _frame_out_extension;
-       /** Path of the directory to write audio files to */
-       std::string _multichannel_audio_out_path;
-};
-
 
 class DecodeOptions
 {
@@ -126,3 +37,5 @@ public:
        bool decode_subtitles;
        bool video_sync;
 };
+
+#endif
index 1bb8f205ed65e734606d82196cacd3cf91ae115e..d75ab0fb679d05f894638158c8ec16db79d63153 100644 (file)
@@ -45,6 +45,7 @@ using boost::algorithm::is_any_of;
 using boost::algorithm::split;
 using boost::thread;
 using boost::bind;
+using libdcp::Size;
 
 /** Create a server description from a string of metadata returned from as_metadata().
  *  @param v Metadata.
index 8a9998d6a9394501c7a5b489ae2bef755a86a099..3754e5acf012c9a8dfe921fab46bb02440f397ef 100644 (file)
@@ -27,6 +27,7 @@
 
 using namespace std;
 using namespace boost;
+using libdcp::Size;
 
 /** Construct a TimedSubtitle.  This is a subtitle image, position,
  *  and a range of time over which it should be shown.
index dfb9b107192748f7dbe8c6b4f2280cd69fa05057..6dd74c36c6ee9fd62dcc6eb31f1a8eaa3b045094 100644 (file)
@@ -37,13 +37,12 @@ using std::setprecision;
 using boost::shared_ptr;
 
 /** @param s Film to use.
- *  @param o Options.
+ *  @param o Decode options.
  *  @param req Job that must be completed before this job is run.
  */
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
+TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req)
        : Job (f, req)
-       , _decode_opt (od)
-       , _encode_opt (oe)
+       , _decode_opt (o)
 {
        
 }
@@ -62,13 +61,16 @@ TranscodeJob::run ()
                _film->log()->log ("Transcode job starting");
                _film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay()));
 
-               _encoder.reset (new Encoder (_film, _encode_opt));
+               _encoder.reset (new Encoder (_film));
                Transcoder w (_film, _decode_opt, this, _encoder);
                w.go ();
                set_progress (1);
                set_state (FINISHED_OK);
 
+               _film->set_dcp_intrinsic_duration (_encoder->video_frames_out ());
+
                _film->log()->log ("Transcode job completed successfully");
+               _film->log()->log (String::compose ("DCP intrinsic duration is %1", _encoder->video_frames_out()));
 
        } catch (std::exception& e) {
 
@@ -116,11 +118,19 @@ TranscodeJob::remaining_time () const
                return 0;
        }
 
-       if (!_film->dcp_length()) {
+       if (!_film->length()) {
                return 0;
        }
 
+       /* Compute approximate proposed length here, as it's only here that we need it */
+       int length = _film->length().get();
+       DCPFrameRate const dfr (_film->frames_per_second ());
+       if (dfr.skip) {
+               length /= 2;
+       }
+       /* If we are repeating it shouldn't affect transcode time, so don't take it into account */
+
        /* We assume that dcp_length() is valid, if it is set */
-       SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame();
+       int const left = length - _encoder->video_frames_out();
        return left / fps;
 }
index 97f655e15c212e78aba5ad9f6564dbfc84f9f7e9..8f78e7f6a899b8d3e5dda3f11da4adce2a1fdfcc 100644 (file)
 
 #include <boost/shared_ptr.hpp>
 #include "job.h"
+#include "options.h"
 
 class Encoder;
-class DecodeOptions;
-class EncodeOptions;
 
 /** @class TranscodeJob
  *  @brief A job which transcodes from one format to another.
@@ -34,7 +33,7 @@ class EncodeOptions;
 class TranscodeJob : public Job
 {
 public:
-       TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> od, boost::shared_ptr<const EncodeOptions> oe, boost::shared_ptr<Job> req);
+       TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od, boost::shared_ptr<Job> req);
        
        std::string name () const;
        void run ();
@@ -44,7 +43,6 @@ protected:
        int remaining_time () const;
 
 private:
-       boost::shared_ptr<const DecodeOptions> _decode_opt;
-       boost::shared_ptr<const EncodeOptions> _encode_opt;
+       DecodeOptions _decode_opt;
        boost::shared_ptr<Encoder> _encoder;
 };
index 87a1fb3f28c8435587e7601b11de13cde3b08a30..93963761e2c3d8a7fdd74bfa4c83119b50102367 100644 (file)
@@ -48,7 +48,7 @@ using boost::dynamic_pointer_cast;
  *  @param j Job that we are running under, or 0.
  *  @param e Encoder to use.
  */
-Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
        : _job (j)
        , _encoder (e)
        , _decoders (decoder_factory (f, o, j))
index b50113742369c817aa3e0b57d28158f31d111b41..786010869d35bb04f4528b3827601a62983a3a42 100644 (file)
@@ -36,8 +36,6 @@ class Gain;
 class VideoDecoder;
 class AudioDecoder;
 class DelayLine;
-class EncodeOptions;
-class DecodeOptions;
 
 /** @class Transcoder
  *  @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
@@ -50,7 +48,7 @@ class Transcoder
 public:
        Transcoder (
                boost::shared_ptr<Film> f,
-               boost::shared_ptr<const DecodeOptions> o,
+               DecodeOptions o,
                Job* j,
                boost::shared_ptr<Encoder> e
                );
index 7f370b89647557199657f47b4d47037ec5b552c0..872985024ac9a5779bfab4f9fa6fdd4988984047 100644 (file)
@@ -26,6 +26,7 @@
 #include <iomanip>
 #include <iostream>
 #include <fstream>
+#include <climits>
 #ifdef DVDOMATIC_POSIX
 #include <execinfo.h>
 #include <cxxabi.h>
@@ -58,9 +59,11 @@ extern "C" {
 #include "dcp_content_type.h"
 #include "filter.h"
 #include "sound_processor.h"
+#include "config.h"
 
 using namespace std;
 using namespace boost;
+using libdcp::Size;
 
 thread::id ui_thread;
 
@@ -330,25 +333,100 @@ md5_digest (string file)
        return s.str ();
 }
 
-/** @param fps Arbitrary frames-per-second value.
- *  @return DCPFrameRate for this frames-per-second.
- */
-DCPFrameRate
-dcp_frame_rate (float fps)
+static bool about_equal (float a, float b)
+{
+       /* A film of F seconds at f FPS will be Ff frames;
+          Consider some delta FPS d, so if we run the same
+          film at (f + d) FPS it will last F(f + d) seconds.
+
+          Hence the difference in length over the length of the film will
+          be F(f + d) - Ff frames
+           = Ff + Fd - Ff frames
+           = Fd frames
+           = Fd/f seconds
+          So if we accept a difference of 1 frame, ie 1/f seconds, we can
+          say that
+
+          1/f = Fd/f
+       ie 1 = Fd
+       ie d = 1/F
+          So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+          FPS error is 1/F ~= 0.0001 ~= 10-e4
+       */
+
+       return (fabs (a - b) < 1e-4);
+}
+
+class FrameRateCandidate
 {
-       DCPFrameRate dfr;
+public:
+       FrameRateCandidate (float source_, int dcp_)
+               : source (source_)
+               , dcp (dcp_)
+       {}
 
-       dfr.run_fast = (fps != rint (fps));
-       dfr.frames_per_second = rint (fps);
-       dfr.skip = 1;
+       bool skip () const {
+               return !about_equal (source, dcp) && source > dcp;
+       }
+
+       bool repeat () const {
+               return !about_equal (source, dcp) && source < dcp;
+       }
+
+       float source;
+       int dcp;
+};
+
+/** @param fps Arbitrary source frames-per-second value */
+/** XXX: this could be slow-ish */
+DCPFrameRate::DCPFrameRate (float source_fps)
+{
+       list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
+
+       /* Work out what rates we could manage, including those achieved by using skip / repeat. */
+       list<FrameRateCandidate> candidates;
+
+       /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
+       for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+               candidates.push_back (FrameRateCandidate (*i, *i));
+       }
+
+       /* Then the skip/repeat ones */
+       for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+               candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
+               candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
+       }
+
+       /* Pick the best one, bailing early if we hit an exact match */
+       float error = numeric_limits<float>::max ();
+       boost::optional<FrameRateCandidate> best;
+       list<FrameRateCandidate>::iterator i = candidates.begin();
+       while (i != candidates.end()) {
+               
+               if (about_equal (i->source, source_fps)) {
+                       best = *i;
+                       break;
+               }
+
+               float const e = fabs (i->source - source_fps);
+               if (e < error) {
+                       error = e;
+                       best = *i;
+               }
+
+               ++i;
+       }
 
-       /* XXX: somewhat arbitrary */
-       if (fps == 50) {
-               dfr.frames_per_second = 25;
-               dfr.skip = 2;
+       if (!best) {
+               throw EncodeError ("cannot find a suitable DCP frame rate for this source");
        }
 
-       return dfr;
+       frames_per_second = best->dcp;
+       skip = best->skip ();
+       repeat = best->repeat ();
+       change_speed = !about_equal (source_fps * factor(), frames_per_second);
 }
 
 /** @param An arbitrary sampling rate.
index 77fb943e090fa8866ac7b6eb021b6bb5786c8cdb..c4940a5d795b3c615d6861c15a0bc7ba804ce2f1 100644 (file)
@@ -62,16 +62,37 @@ typedef int SourceFrame;
 
 struct DCPFrameRate
 {
+       DCPFrameRate (float);
+
+       /** @return factor by which to multiply a source frame rate
+           to get the effective rate after any skip or repeat has happened.
+       */
+       float factor () const {
+               if (skip) {
+                       return 0.5;
+               } else if (repeat) {
+                       return 2;
+               }
+
+               return 1;
+       }
+       
        /** frames per second for the DCP */
        int frames_per_second;
-       /** Skip every `skip' frames.  e.g. if this is 1, we skip nothing;
-        *  if it's 2, we skip every other frame.
-        */
-       int skip;
-       /** true if this DCP will run its video faster than the source
-        *  (e.g. if the source is 29.97fps and we will run the DCP at 30fps)
+       /** true to skip every other frame */
+       bool skip;
+       /** true to repeat every frame once */
+       bool repeat;
+       /** true if this DCP will run its video faster or slower than the source
+        *  without taking into account `repeat'.
+        *  (e.g. change_speed will be true if
+        *          source is 29.97fps, DCP is 30fps
+        *          source is 14.50fps, DCP is 30fps
+        *  but not if
+        *          source is 15.00fps, DCP is 30fps
+        *          source is 12.50fps, DCP is 25fps)
         */
-       bool run_fast;
+       bool change_speed;
 };
 
 enum ContentType {
@@ -157,7 +178,6 @@ struct Rect
 
 extern std::string crop_string (Position, libdcp::Size);
 extern int dcp_audio_sample_rate (int);
-extern DCPFrameRate dcp_frame_rate (float);
 extern int dcp_audio_channels (int);
 extern std::string colour_lut_index_to_name (int index);
 extern int stride_round_up (int, int const *, int);
index e0a7576eeaf5c51670cf1702c6b7b4597467db15..0fa16bc325b8b6ee8a309098c54a066359d99e1a 100644 (file)
@@ -28,7 +28,7 @@
 using boost::shared_ptr;
 using boost::optional;
 
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
+VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
        : Decoder (f, o, j)
        , _video_frame (0)
        , _last_source_time (0)
@@ -57,7 +57,7 @@ void
 VideoDecoder::repeat_last_video ()
 {
        if (!_last_image) {
-               _last_image.reset (new SimpleImage (pixel_format(), native_size(), false));
+               _last_image.reset (new SimpleImage (pixel_format(), native_size(), true));
                _last_image->make_black ();
        }
 
index b18082c69ef0c67b2dbecd6c137964edf5647adc..ef1ab041a052589f003816e26b239dd4e2ef2a9b 100644 (file)
@@ -27,7 +27,7 @@
 class VideoDecoder : public VideoSource, public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+       VideoDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
 
        /** @return video frames per second, or 0 if unknown */
        virtual float frames_per_second () const = 0;
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
new file mode 100644 (file)
index 0000000..a434db0
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+    Copyright (C) 2012 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/picture_asset.h>
+#include <libdcp/sound_asset.h>
+#include <libdcp/reel.h>
+#include "writer.h"
+#include "compose.hpp"
+#include "film.h"
+#include "format.h"
+#include "log.h"
+#include "dcp_video_frame.h"
+
+using std::make_pair;
+using std::pair;
+using boost::shared_ptr;
+
+unsigned int const Writer::_maximum_frames_in_memory = 8;
+
+Writer::Writer (shared_ptr<Film> f)
+       : _film (f)
+       , _thread (0)
+       , _finish (false)
+       , _last_written_frame (-1)
+{
+       _picture_asset.reset (
+               new libdcp::MonoPictureAsset (
+                       _film->dir (_film->dcp_name()),
+                       "video.mxf",
+                       DCPFrameRate (_film->frames_per_second()).frames_per_second,
+                       _film->format()->dcp_size()
+                       )
+               );
+       
+       _picture_asset_writer = _picture_asset->start_write ();
+
+       if (_film->audio_channels() > 0) {
+               _sound_asset.reset (
+                       new libdcp::SoundAsset (
+                               _film->dir (_film->dcp_name()),
+                               "audio.mxf",
+                               DCPFrameRate (_film->frames_per_second()).frames_per_second,
+                               _film->audio_channels(),
+                               dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
+                               )
+                       );
+
+               _sound_asset_writer = _sound_asset->start_write ();
+       }
+       
+       _thread = new boost::thread (boost::bind (&Writer::thread, this));
+}
+
+void
+Writer::write (shared_ptr<const EncodedData> encoded, int frame)
+{
+       boost::mutex::scoped_lock lock (_mutex);
+       _queue.push_back (make_pair (encoded, frame));
+       _condition.notify_all ();
+}
+
+/** This method is not thread safe */
+void
+Writer::write (shared_ptr<const AudioBuffers> audio)
+{
+       _sound_asset_writer->write (audio->data(), audio->frames());
+}
+
+struct QueueSorter
+{
+       bool operator() (pair<shared_ptr<const EncodedData>, int> const & a, pair<shared_ptr<const EncodedData>, int> const & b) {
+               return a.second < b.second;
+       }
+};
+
+void
+Writer::thread ()
+{
+       while (1)
+       {
+               boost::mutex::scoped_lock lock (_mutex);
+
+               while (1) {
+                       if (_finish ||
+                           _queue.size() > _maximum_frames_in_memory ||
+                           (!_queue.empty() && _queue.front().second == (_last_written_frame + 1))) {
+                                   
+                                   break;
+                           }
+
+                           TIMING ("writer sleeps with a queue of %1; %2 pending", _queue.size(), _pending.size());
+                           _condition.wait (lock);
+                           TIMING ("writer wakes with a queue of %1", _queue.size());
+
+                           _queue.sort (QueueSorter ());
+               }
+
+               if (_finish && _queue.empty() && _pending.empty()) {
+                       return;
+               }
+
+               /* Write any frames that we can write; i.e. those that are in sequence */
+               while (!_queue.empty() && _queue.front().second == (_last_written_frame + 1)) {
+                       pair<boost::shared_ptr<const EncodedData>, int> encoded = _queue.front ();
+                       _queue.pop_front ();
+
+                       lock.unlock ();
+                       _film->log()->log (String::compose ("Writer writes %1 to MXF", encoded.second));
+                       if (encoded.first) {
+                               _picture_asset_writer->write (encoded.first->data(), encoded.first->size());
+                               encoded.first->write_hash (_film, encoded.second);
+                               _last_written = encoded.first;
+                       } else {
+                               _picture_asset_writer->write (_last_written->data(), _last_written->size());
+                               _last_written->write_hash (_film, encoded.second);
+                       }
+                       lock.lock ();
+
+                       ++_last_written_frame;
+               }
+
+               while (_queue.size() > _maximum_frames_in_memory) {
+                       /* Too many frames in memory which can't yet be written to the stream.
+                          Put some to disk.
+                       */
+
+                       pair<boost::shared_ptr<const EncodedData>, int> encoded = _queue.back ();
+                       _queue.pop_back ();
+                       if (!encoded.first) {
+                               /* This is a `repeat-last' frame, so no need to write it to disk */
+                               continue;
+                       }
+
+                       lock.unlock ();
+                       _film->log()->log (String::compose ("Writer full (awaiting %1); pushes %2 to disk", _last_written_frame + 1, encoded.second));
+                       encoded.first->write (_film, encoded.second);
+                       lock.lock ();
+
+                       _pending.push_back (encoded.second);
+               }
+
+               while (_queue.size() < _maximum_frames_in_memory && !_pending.empty()) {
+                       /* We have some space in memory.  Fetch some frames back off disk. */
+
+                       _pending.sort ();
+                       int const fetch = _pending.front ();
+
+                       lock.unlock ();
+                       _film->log()->log (String::compose ("Writer pulls %1 back from disk", fetch));
+                       shared_ptr<const EncodedData> encoded;
+                       if (boost::filesystem::exists (_film->frame_out_path (fetch, false))) {
+                               /* It's an actual frame (not a repeat-last); load it in */
+                               encoded.reset (new EncodedData (_film->frame_out_path (fetch, false)));
+                       }
+                       lock.lock ();
+
+                       _queue.push_back (make_pair (encoded, fetch));
+                       _pending.remove (fetch);
+               }
+       }
+
+}
+
+void
+Writer::finish ()
+{
+       if (!_thread) {
+               return;
+       }
+       
+       boost::mutex::scoped_lock lock (_mutex);
+       _finish = true;
+       _condition.notify_all ();
+       lock.unlock ();
+
+       _thread->join ();
+       delete _thread;
+       _thread = 0;
+
+       _picture_asset_writer->finalize ();
+
+       if (_sound_asset_writer) {
+               _sound_asset_writer->finalize ();
+       }
+
+       int const frames = _last_written_frame + 1;
+       int const duration = frames - _film->trim_start() - _film->trim_end();
+       
+       _film->set_dcp_intrinsic_duration (frames);
+       
+       _picture_asset->set_entry_point (_film->trim_start ());
+       _picture_asset->set_duration (duration);
+
+       if (_sound_asset) {
+               _sound_asset->set_entry_point (_film->trim_start ());
+               _sound_asset->set_duration (duration);
+       }
+       
+       libdcp::DCP dcp (_film->dir (_film->dcp_name()));
+       DCPFrameRate dfr (_film->frames_per_second ());
+
+       shared_ptr<libdcp::CPL> cpl (
+               new libdcp::CPL (_film->dir (_film->dcp_name()), _film->dcp_name(), _film->dcp_content_type()->libdcp_kind (), frames, dfr.frames_per_second)
+               );
+       
+       dcp.add_cpl (cpl);
+
+       cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
+                                                        _picture_asset,
+                                                        _sound_asset,
+                                                        shared_ptr<libdcp::SubtitleAsset> ()
+                                                        )
+                              ));
+
+       dcp.write_xml ();
+}
+
+/** Tell the writer that frame `f' should be a repeat of the frame before it */
+void
+Writer::repeat (int f)
+{
+       boost::mutex::scoped_lock lock (_mutex);
+       _queue.push_back (make_pair (shared_ptr<const EncodedData> (), f));
+}
diff --git a/src/lib/writer.h b/src/lib/writer.h
new file mode 100644 (file)
index 0000000..1aaea4d
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+    Copyright (C) 2012 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 <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/condition.hpp>
+
+class Film;
+class EncodedData;
+class AudioBuffers;
+
+namespace libdcp {
+       class MonoPictureAsset;
+       class MonoPictureAssetWriter;
+       class SoundAsset;
+       class SoundAssetWriter;
+}
+
+class Writer
+{
+public:
+       Writer (boost::shared_ptr<Film>);
+       
+       void write (boost::shared_ptr<const EncodedData>, int);
+       void write (boost::shared_ptr<const AudioBuffers>);
+       void repeat (int f);
+       void finish ();
+
+private:
+
+       void thread ();
+
+       boost::shared_ptr<Film> _film;
+
+       boost::thread* _thread;
+       bool _finish;
+       std::list<std::pair<boost::shared_ptr<const EncodedData>, int> > _queue;
+       mutable boost::mutex _mutex;
+       boost::condition _condition;
+       boost::shared_ptr<const EncodedData> _last_written;
+       std::list<int> _pending;
+       int _last_written_frame;
+       static const unsigned int _maximum_frames_in_memory;
+
+       boost::shared_ptr<libdcp::MonoPictureAsset> _picture_asset;
+       boost::shared_ptr<libdcp::MonoPictureAssetWriter> _picture_asset_writer;
+       boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
+       boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+};
index b2b639f06d375928c9872e12b288136fa3f2da0f..02c4263735ef5972636265c7764a755f806c8be0 100644 (file)
@@ -40,7 +40,6 @@ def build(bld):
                 job_manager.cc
                 log.cc
                 lut.cc
-                make_dcp_job.cc
                  matcher.cc
                  scp_dcp_job.cc
                 scaler.cc
@@ -56,6 +55,7 @@ def build(bld):
                 version.cc
                  video_decoder.cc
                  video_source.cc
+                 writer.cc
                 """
 
     obj.target = 'dvdomatic'
index 900c31bfc9fca7630bc8002ba833128be88563ff..447b0ddc0061fe93f8e11ae9dfb66d7be650e719 100644 (file)
@@ -26,7 +26,6 @@
 #include "film.h"
 #include "filter.h"
 #include "transcode_job.h"
-#include "make_dcp_job.h"
 #include "job_manager.h"
 #include "ab_transcode_job.h"
 #include "util.h"
index 72f2d48071316beeec6c7c2a0fc27d7974763642..4e99bcd960da73a6ce9fe99b40c065ab7c98baf4 100644 (file)
@@ -140,11 +140,11 @@ FilmEditor::make_film_panel ()
                video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                video_control (add_label_to_sizer (s, _film_panel, "Start"));
-               _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_dcp_trim_start));
+               _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+               s->Add (video_control (_trim_start));
                video_control (add_label_to_sizer (s, _film_panel, "End"));
-               _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_dcp_trim_end));
+               _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+               s->Add (video_control (_trim_end));
 
                _film_sizer->Add (s);
        }
@@ -189,8 +189,8 @@ FilmEditor::connect_to_widgets ()
        _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
        _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
        _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
-       _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
-       _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
+       _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
+       _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
        _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
        _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
        _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
@@ -289,8 +289,8 @@ FilmEditor::make_video_panel ()
        _right_crop->SetRange (0, 1024);
        _bottom_crop->SetRange (0, 1024);
        _still_duration->SetRange (1, 60 * 60);
-       _dcp_trim_start->SetRange (0, 100);
-       _dcp_trim_end->SetRange (0, 100);
+       _trim_start->SetRange (0, 100);
+       _trim_end->SetRange (0, 100);
        _j2k_bandwidth->SetRange (50, 250);
 }
 
@@ -620,10 +620,12 @@ FilmEditor::film_changed (Film::Property p)
                } 
                _length->SetLabel (std_to_wx (s.str ()));
                if (_film->length()) {
-                       _dcp_trim_start->SetRange (0, _film->length().get());
-                       _dcp_trim_end->SetRange (0, _film->length().get());
+                       _trim_start->SetRange (0, _film->length().get());
+                       _trim_end->SetRange (0, _film->length().get());
                }
                break;
+       case Film::DCP_INTRINSIC_DURATION:
+               break;
        case Film::DCP_CONTENT_TYPE:
                checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
                _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
@@ -634,11 +636,11 @@ FilmEditor::film_changed (Film::Property p)
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
                break;
-       case Film::DCP_TRIM_START:
-               checked_set (_dcp_trim_start, _film->dcp_trim_start());
+       case Film::TRIM_START:
+               checked_set (_trim_start, _film->trim_start());
                break;
-       case Film::DCP_TRIM_END:
-               checked_set (_dcp_trim_end, _film->dcp_trim_end());
+       case Film::TRIM_END:
+               checked_set (_trim_end, _film->trim_end());
                break;
        case Film::AUDIO_GAIN:
                checked_set (_audio_gain, _film->audio_gain ());
@@ -761,8 +763,8 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::CROP);
        film_changed (Film::FILTERS);
        film_changed (Film::SCALER);
-       film_changed (Film::DCP_TRIM_START);
-       film_changed (Film::DCP_TRIM_END);
+       film_changed (Film::TRIM_START);
+       film_changed (Film::TRIM_END);
        film_changed (Film::DCP_AB);
        film_changed (Film::CONTENT_AUDIO_STREAM);
        film_changed (Film::EXTERNAL_AUDIO);
@@ -805,8 +807,8 @@ FilmEditor::set_things_sensitive (bool s)
        _scaler->Enable (s);
        _audio_stream->Enable (s);
        _dcp_content_type->Enable (s);
-       _dcp_trim_start->Enable (s);
-       _dcp_trim_end->Enable (s);
+       _trim_start->Enable (s);
+       _trim_end->Enable (s);
        _dcp_ab->Enable (s);
        _colour_lut->Enable (s);
        _j2k_bandwidth->Enable (s);
@@ -920,23 +922,23 @@ FilmEditor::still_duration_changed (wxCommandEvent &)
 }
 
 void
-FilmEditor::dcp_trim_start_changed (wxCommandEvent &)
+FilmEditor::trim_start_changed (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
 
-       _film->set_dcp_trim_start (_dcp_trim_start->GetValue ());
+       _film->set_trim_start (_trim_start->GetValue ());
 }
 
 void
-FilmEditor::dcp_trim_end_changed (wxCommandEvent &)
+FilmEditor::trim_end_changed (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
 
-       _film->set_dcp_trim_end (_dcp_trim_end->GetValue ());
+       _film->set_trim_end (_trim_end->GetValue ());
 }
 
 void
index 8a900f1e4bedf05bf325183739d96e0b34383ec5..7272315fcfedc88af94bf8bc9b9f3f977057652e 100644 (file)
@@ -63,8 +63,8 @@ private:
        void content_changed (wxCommandEvent &);
        void trust_content_header_changed (wxCommandEvent &);
        void format_changed (wxCommandEvent &);
-       void dcp_trim_start_changed (wxCommandEvent &);
-       void dcp_trim_end_changed (wxCommandEvent &);
+       void trim_start_changed (wxCommandEvent &);
+       void trim_end_changed (wxCommandEvent &);
        void dcp_content_type_changed (wxCommandEvent &);
        void dcp_ab_toggled (wxCommandEvent &);
        void scaler_changed (wxCommandEvent &);
@@ -165,8 +165,8 @@ private:
        /** The Film's duration for still sources */
        wxSpinCtrl* _still_duration;
 
-       wxSpinCtrl* _dcp_trim_start;
-       wxSpinCtrl* _dcp_trim_end;
+       wxSpinCtrl* _trim_start;
+       wxSpinCtrl* _trim_end;
        /** Selector to generate an A/B comparison DCP */
        wxCheckBox* _dcp_ab;
 
index e014a8731e0a6b09b369ace8e2ff69030cd76a74..4176f4f426e8287f1dffc16b335542ed132a0e05 100644 (file)
@@ -44,6 +44,7 @@ using std::max;
 using std::cout;
 using std::list;
 using boost::shared_ptr;
+using libdcp::Size;
 
 FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        : wxPanel (p)
@@ -98,10 +99,10 @@ FilmViewer::film_changed (Film::Property p)
                break;
        case Film::CONTENT:
        {
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               o->decode_audio = false;
-               o->decode_subtitles = true;
-               o->video_sync = false;
+               DecodeOptions o;
+               o.decode_audio = false;
+               o.decode_subtitles = true;
+               o.video_sync = false;
                _decoders = decoder_factory (_film, o, 0);
                _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
                _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
index b03c6b32c54d95e3b23cda98d63e121ee5338b29..0cf75cf5175bad2d56ca668f46c7d5f41cf54b5c 100644 (file)
@@ -91,9 +91,9 @@ PropertiesDialog::frames_already_encoded () const
                return "";
        }
        
-       if (_film->dcp_length()) {
+       if (_film->length()) {
                /* XXX: encoded_frames() should check which frames have been encoded */
-               u << " (" << ((_film->encoded_frames() - _film->dcp_trim_start()) * 100 / _film->dcp_length().get()) << "%)";
+               u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
        }
        return u.str ();
 }
index 3aea4bcba86d4054fc40f018195602c7637dbfc5..ff0c2511803225b6bf951aeb9abc894b4ec0047f 100644 (file)
@@ -1,4 +1,4 @@
-version 1
+version 2
 name fred
 use_dci_name 1
 content 
@@ -12,8 +12,8 @@ bottom_crop 4
 filter pphb
 filter unsharp
 scaler bicubic
-dcp_trim_start 42
-dcp_trim_end 99
+trim_start 42
+trim_end 99
 dcp_ab 1
 use_content_audio 1
 audio_gain 0
@@ -34,6 +34,7 @@ package_type
 width 0
 height 0
 length 0
+dcp_intrinsic_duration 0
 content_digest 
 external_audio_stream external 0 0
 frames_per_second 0
index 5f6d687ac506cded67e844a386494333a580c430..9f1248f294dc0a92e07e78ec620eb7b1b2079bc9 100644 (file)
@@ -69,9 +69,69 @@ new_test_film (string name)
        return shared_ptr<Film> (new Film (d, false));
 }
 
-BOOST_AUTO_TEST_CASE (film_metadata_test)
+
+/* Check that Image::make_black works, and doesn't use values which crash
+   sws_scale().
+*/
+BOOST_AUTO_TEST_CASE (make_black_test)
 {
+       /* This needs to happen in the first test */
        dvdomatic_setup ();
+
+       libdcp::Size in_size (512, 512);
+       libdcp::Size out_size (1024, 1024);
+
+       {
+               /* Plain RGB input */
+               boost::shared_ptr<Image> foo (new SimpleImage (AV_PIX_FMT_RGB24, in_size, true));
+               foo->make_black ();
+               boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
+               
+               uint8_t* p = bar->data()[0];
+               for (int y = 0; y < bar->size().height; ++y) {
+                       uint8_t* q = p;
+                       for (int x = 0; x < bar->line_size()[0]; ++x) {
+                               BOOST_CHECK_EQUAL (*q++, 0);
+                       }
+                       p += bar->stride()[0];
+               }
+       }
+
+       {
+               /* YUV420P input */
+               boost::shared_ptr<Image> foo (new SimpleImage (AV_PIX_FMT_YUV420P, in_size, true));
+               foo->make_black ();
+               boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
+               
+               uint8_t* p = bar->data()[0];
+               for (int y = 0; y < bar->size().height; ++y) {
+                       uint8_t* q = p;
+                       for (int x = 0; x < bar->line_size()[0]; ++x) {
+                               BOOST_CHECK_EQUAL (*q++, 0);
+                       }
+                       p += bar->stride()[0];
+               }
+       }
+
+       {
+               /* YUV422P10LE input */
+               boost::shared_ptr<Image> foo (new SimpleImage (AV_PIX_FMT_YUV422P10LE, in_size, true));
+               foo->make_black ();
+               boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
+               
+               uint8_t* p = bar->data()[0];
+               for (int y = 0; y < bar->size().height; ++y) {
+                       uint8_t* q = p;
+                       for (int x = 0; x < bar->line_size()[0]; ++x) {
+                               BOOST_CHECK_EQUAL (*q++, 0);
+                       }
+                       p += bar->stride()[0];
+               }
+       }
+}
+
+BOOST_AUTO_TEST_CASE (film_metadata_test)
+{
        setup_test_config ();
 
        string const test_film = "build/test/film_metadata_test";
@@ -99,8 +159,8 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        f_filters.push_back (Filter::from_id ("pphb"));
        f_filters.push_back (Filter::from_id ("unsharp"));
        f->set_filters (f_filters);
-       f->set_dcp_trim_start (42);
-       f->set_dcp_trim_end (99);
+       f->set_trim_start (42);
+       f->set_trim_end (99);
        f->set_dcp_ab (true);
        f->write_metadata ();
 
@@ -121,8 +181,8 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        BOOST_CHECK_EQUAL (g_filters.size(), 2);
        BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb"));
        BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
-       BOOST_CHECK_EQUAL (g->dcp_trim_start(), 42);
-       BOOST_CHECK_EQUAL (g->dcp_trim_end(), 99);
+       BOOST_CHECK_EQUAL (g->trim_start(), 42);
+       BOOST_CHECK_EQUAL (g->trim_end(), 99);
        BOOST_CHECK_EQUAL (g->dcp_ab(), true);
        
        g->write_metadata ();
@@ -325,26 +385,30 @@ do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* descriptio
 
 BOOST_AUTO_TEST_CASE (client_server_test)
 {
-       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), false));
+       shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
        uint8_t* p = image->data()[0];
        
        for (int y = 0; y < 1080; ++y) {
+               uint8_t* q = p;
                for (int x = 0; x < 1998; ++x) {
-                       *p++ = x % 256;
-                       *p++ = y % 256;
-                       *p++ = (x + y) % 256;
+                       *q++ = x % 256;
+                       *q++ = y % 256;
+                       *q++ = (x + y) % 256;
                }
+               p += image->stride()[0];
        }
 
-       shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), false));
+       shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
        p = sub_image->data()[0];
        for (int y = 0; y < 200; ++y) {
+               uint8_t* q = p;
                for (int x = 0; x < 100; ++x) {
-                       *p++ = y % 256;
-                       *p++ = x % 256;
-                       *p++ = (x + y) % 256;
-                       *p++ = 1;
+                       *q++ = y % 256;
+                       *q++ = x % 256;
+                       *q++ = (x + y) % 256;
+                       *q++ = 1;
                }
+               p += sub_image->stride()[0];
        }
 
        shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image));
@@ -419,7 +483,7 @@ BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
        film->examine_content ();
        film->set_format (Format::from_nickname ("Flat"));
        film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
-       film->set_dcp_trim_end (42);
+       film->set_trim_end (42);
        film->make_dcp (true);
 
        while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
@@ -429,6 +493,111 @@ BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
        BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
 }
 
+/* Test the constructor of DCPFrameRate */
+BOOST_AUTO_TEST_CASE (dcp_frame_rate_test)
+{
+       /* Run some tests with a limited range of allowed rates */
+       
+       std::list<int> afr;
+       afr.push_back (24);
+       afr.push_back (25);
+       afr.push_back (30);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       DCPFrameRate dfr = DCPFrameRate (60);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+       BOOST_CHECK_EQUAL (dfr.skip, true);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+       
+       dfr = DCPFrameRate (50);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+       BOOST_CHECK_EQUAL (dfr.skip, true);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+       dfr = DCPFrameRate (48);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+       BOOST_CHECK_EQUAL (dfr.skip, true);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+       
+       dfr = DCPFrameRate (30);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+       dfr = DCPFrameRate (29.97);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, true);
+       
+       dfr = DCPFrameRate (25);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+       dfr = DCPFrameRate (24);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+       dfr = DCPFrameRate (14.5);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+       BOOST_CHECK_EQUAL (dfr.repeat, true);
+       BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+       dfr = DCPFrameRate (12.6);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+       BOOST_CHECK_EQUAL (dfr.repeat, true);
+       BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+       dfr = DCPFrameRate (12.4);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+       BOOST_CHECK_EQUAL (dfr.repeat, true);
+       BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+       dfr = DCPFrameRate (12);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+       BOOST_CHECK_EQUAL (dfr.repeat, true);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+       /* Now add some more rates and see if it will use them
+          in preference to skip/repeat.
+       */
+
+       afr.push_back (48);
+       afr.push_back (50);
+       afr.push_back (60);
+       Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+       dfr = DCPFrameRate (60);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 60);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+       
+       dfr = DCPFrameRate (50);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 50);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+       dfr = DCPFrameRate (48);
+       BOOST_CHECK_EQUAL (dfr.frames_per_second, 48);
+       BOOST_CHECK_EQUAL (dfr.skip, false);
+       BOOST_CHECK_EQUAL (dfr.repeat, false);
+       BOOST_CHECK_EQUAL (dfr.change_speed, false);
+}
+
 BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
 {
        shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");