Merge master; specify libdcp-1.0.
authorCarl Hetherington <cth@carlh.net>
Mon, 24 Feb 2014 12:19:50 +0000 (12:19 +0000)
committerCarl Hetherington <cth@carlh.net>
Mon, 24 Feb 2014 12:19:50 +0000 (12:19 +0000)
40 files changed:
ChangeLog
cscript
debian/changelog
run/dcpomatic_batch
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/content.cc
src/lib/content.h
src/lib/examine_content_job.cc
src/lib/examine_content_job.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/film.cc
src/lib/film.h
src/lib/job.cc
src/lib/job.h
src/lib/json_server.cc [new file with mode: 0644]
src/lib/json_server.h [new file with mode: 0644]
src/lib/scp_dcp_job.cc
src/lib/scp_dcp_job.h
src/lib/send_kdm_email_job.cc
src/lib/send_kdm_email_job.h
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/util.cc
src/lib/util.h
src/lib/wscript
src/tools/dcpomatic.cc
src/tools/dcpomatic_cli.cc
src/tools/dcpomatic_create.cc
src/wx/about_dialog.cc
src/wx/audio_mapping_view.cc
src/wx/audio_panel.cc
src/wx/subtitle_panel.cc
test/job_test.cc
test/stream_test.cc
wscript

index a66eb71d7a9d70c09870fc187c1b188aa4f3edb1..ba4cf937712d7bc2bfde244fc0e68f3b5fee184d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+2014-02-23  Carl Hetherington  <cth@carlh.net>
+
+       * Bump ffmpeg library to git head to fix problems with
+       misrecognised frame rates in some MOV files.
+
+2014-02-20  Carl Hetherington  <cth@carlh.net>
+
+       * Version 1.64.15 released.
+
+2014-02-20  Carl Hetherington  <cth@carlh.net>
+
+       * Basic support for 7.1 / HI/VI audio tracks.
+
+2014-02-19  Carl Hetherington  <cth@carlh.net>
+
+       * Add some basic JSON stuff.
+
+2014-02-18  Carl Hetherington  <cth@carlh.net>
+
+       * Version 1.64.14 released.
+
+2014-02-18  Carl Hetherington  <cth@carlh.net>
+
+       * Version 1.64.13 released.
+
 2014-02-12  Carl Hetherington  <cth@carlh.net>
 
        * Make the batch converter remember its last directory
diff --git a/cscript b/cscript
index d6a04aba6e2ae9797a04d75346ee1d9d94d01d19..b8b0580063ee0490652cd2c8e0b0132360bcf349 100644 (file)
--- a/cscript
+++ b/cscript
@@ -129,7 +129,7 @@ def make_control(debian_version, bits, filename, debug):
         print >>f,''
 
 def dependencies(target):
-    return (('ffmpeg-cdist', '5ac3a6af077c10f07c31954c372a8f29e4e18e2a'),
+    return (('ffmpeg-cdist', '08827fa4e1d483511e6135c424d2ca9c56a9ed50'),
             ('libdcp', '1.0'))
 
 def build(target, options):
index ec21747eed55ec567dd73360bd793ef968d5b205..79b1f98c35f0293e8bc48fbfb961dac51bfef0eb 100644 (file)
@@ -1,4 +1,4 @@
-dcpomatic (1.64.12-1) UNRELEASED; urgency=low
+dcpomatic (1.64.15-1) UNRELEASED; urgency=low
 
   * New upstream release.
   * New upstream release.
@@ -93,8 +93,11 @@ dcpomatic (1.64.12-1) UNRELEASED; urgency=low
   * New upstream release.
   * New upstream release.
   * New upstream release.
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
 
- -- Carl Hetherington <carl@d1stkfactory>  Tue, 11 Feb 2014 15:52:44 +0000
+ -- Carl Hetherington <carl@d1stkfactory>  Thu, 20 Feb 2014 10:10:09 +0000
 
 dcpomatic (0.87-1) UNRELEASED; urgency=low
 
index 78ee8f7be0a19a2e6ef5796af1d1e22057136a3d..80cf5291c8a1eee80e2a0af05e2f0eae5b00e438 100755 (executable)
@@ -7,6 +7,9 @@ if [ "$1" == "--debug" ]; then
 elif [ "$1" == "--valgrind" ]; then
     shift
     valgrind --tool="memcheck" --suppressions=valgrind.supp --leak-check=full --show-reachable=yes build/src/tools/dcpomatic_batch $*
+elif [ "$1" == "--massif" ]; then
+    shift
+    valgrind --tool="massif" build/src/tools/dcpomatic_batch $*
 elif [ "$1" == "--callgrind" ]; then
     shift
     valgrind --tool="callgrind" build/src/tools/dcpomatic_batch $*
index 872947b55d3d56cac4a957ca561bc5944a81d34c..a6926bc14a02a4a465cd01b37496eb5e97bdd6ea 100644 (file)
@@ -48,6 +48,12 @@ AnalyseAudioJob::name () const
        return _("Analyse audio");
 }
 
+string
+AnalyseAudioJob::json_name () const
+{
+       return N_("analyse_audio");
+}
+
 void
 AnalyseAudioJob::run ()
 {
index 6ed236d85ca57854a7a4f4691f4082bf4c1c059e..3d3900a5112b5f621e01a81a81b80c55a132271a 100644 (file)
@@ -30,6 +30,7 @@ public:
        AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
 
        std::string name () const;
+       std::string json_name () const;
        void run ();
 
 private:
index ea1c19acdc1cc620cffeee582e85a35bd4d10acc..8e3b99da89b43027ede972e2b3200d2f63754475 100644 (file)
@@ -195,7 +195,7 @@ Content::clone () const
        xmlpp::Document doc;
        xmlpp::Node* node = doc.create_root_node ("Content");
        as_xml (node);
-       return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::state_version);
+       return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::current_state_version);
 }
 
 string
index 3172b9c8d1c77b3ad19a3d4b034b88f744181311..78a41e306010cf38828e0090151b81065ef263e7 100644 (file)
@@ -57,6 +57,9 @@ public:
        
        virtual void examine (boost::shared_ptr<Job>);
        virtual std::string summary () const = 0;
+       /** @return Technical details of this content; these are written to logs to
+        *  help with debugging.
+        */
        virtual std::string technical_summary () const;
        virtual std::string information () const = 0;
        virtual void as_xml (xmlpp::Node *) const;
index cbf180ffcb3bf451379e410039d5d28dfed75d80..8f16e2e5c0c069e2ab601c4bf91cec39c1e3cebc 100644 (file)
@@ -46,6 +46,12 @@ ExamineContentJob::name () const
        return _("Examine content");
 }
 
+string
+ExamineContentJob::json_name () const
+{
+       return N_("examine_content");
+}
+
 void
 ExamineContentJob::run ()
 {
index b6903b86bebf0ed9e80970a7382c0f07628301cc..c8037224faa708737e6739d5c29bc544ee74ad0f 100644 (file)
@@ -30,6 +30,7 @@ public:
        ~ExamineContentJob ();
 
        std::string name () const;
+       std::string json_name () const;
        void run ();
 
 private:
index 3bee49146a5304024d083c6138b00e8332012390..3df1ba57ee78ddd1422f0cedc74c64ba696951c9 100644 (file)
@@ -66,7 +66,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::N
 {
        list<cxml::NodePtr> c = node->node_children ("SubtitleStream");
        for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
-               _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i, version)));
+               _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
                if ((*i)->optional_number_child<int> ("Selected")) {
                        _subtitle_stream = _subtitle_streams.back ();
                }
@@ -207,12 +207,12 @@ FFmpegContent::technical_summary () const
 {
        string as = "none";
        if (_audio_stream) {
-               as = String::compose ("id %1", _audio_stream->id);
+               as = _audio_stream->technical_summary ();
        }
 
        string ss = "none";
        if (_subtitle_stream) {
-               ss = String::compose ("id %1", _subtitle_stream->id);
+               ss = _subtitle_stream->technical_summary ();
        }
 
        pair<string, string> filt = Filter::ffmpeg_strings (_filters);
@@ -325,54 +325,33 @@ FFmpegContent::output_audio_frame_rate () const
 }
 
 bool
-operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+operator== (FFmpegStream const & a, FFmpegStream const & b)
 {
-       return a.id == b.id;
+       return a._id == b._id;
 }
 
 bool
-operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+operator!= (FFmpegStream const & a, FFmpegStream const & b)
 {
-       return a.id != b.id;
+       return a._id != b._id;
 }
 
-bool
-operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
-{
-       return a.id == b.id;
-}
-
-bool
-operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node)
+       : name (node->string_child ("Name"))
+       , _id (node->number_child<int> ("Id"))
 {
-       return a.id != b.id;
-}
 
-FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node, int version)
-       : _legacy_id (false)
-{
-       name = node->string_child ("Name");
-       id = node->number_child<int> ("Id");
-       if (version == 4 || node->optional_bool_child ("LegacyId")) {
-               _legacy_id = true;
-       }
 }
 
 void
 FFmpegStream::as_xml (xmlpp::Node* root) const
 {
        root->add_child("Name")->add_child_text (name);
-       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
-       if (_legacy_id) {
-               /* Write this so that version > 4 files are read in correctly
-                  if the Id came originally from a version <= 4 file.
-               */
-               root->add_child("LegacyId")->add_child_text ("1");
-       }
+       root->add_child("Id")->add_child_text (lexical_cast<string> (_id));
 }
 
 FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
-       : FFmpegStream (node, version)
+       : FFmpegStream (node)
        , mapping (node->node_child ("Mapping"), version)
 {
        frame_rate = node->number_child<int> ("FrameRate");
@@ -392,34 +371,26 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const
        mapping.as_xml (root->add_child("Mapping"));
 }
 
-int
-FFmpegStream::index (AVFormatContext const * fc) const
+bool
+FFmpegStream::uses_index (AVFormatContext const * fc, int index) const
 {
-       if (_legacy_id) {
-               return id;
-       }
-       
        size_t i = 0;
        while (i < fc->nb_streams) {
-               if (fc->streams[i]->id == id) {
-                       return i;
+               if (fc->streams[i]->id == _id) {
+                       return int (i) == index;
                }
                ++i;
        }
 
-       assert (false);
+       return false;
 }
 
 AVStream *
 FFmpegStream::stream (AVFormatContext const * fc) const
 {
-       if (_legacy_id) {
-               return fc->streams[id];
-       }
-       
        size_t i = 0;
        while (i < fc->nb_streams) {
-               if (fc->streams[i]->id == id) {
+               if (fc->streams[i]->id == _id) {
                        return fc->streams[i];
                }
                ++i;
@@ -433,8 +404,8 @@ FFmpegStream::stream (AVFormatContext const * fc) const
  *  @param t String returned from to_string().
  *  @param v State file version.
  */
-FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node, int version)
-       : FFmpegStream (node, version)
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+       : FFmpegStream (node)
 {
        
 }
@@ -495,7 +466,7 @@ FFmpegContent::identifier () const
        boost::mutex::scoped_lock lm (_mutex);
 
        if (_subtitle_stream) {
-               s << "_" << _subtitle_stream->id;
+               s << "_" << _subtitle_stream->identifier ();
        }
 
        for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
index e637faf47643be48b0f3353bad5d440cb5231b62..d588fd2ee2d820d4495861ad43210406c767158c 100644 (file)
@@ -37,26 +37,35 @@ class FFmpegStream
 public:
        FFmpegStream (std::string n, int i)
                : name (n)
-               , id (i)
-               , _legacy_id (false)
+               , _id (i)
        {}
                                
-       FFmpegStream (boost::shared_ptr<const cxml::Node>, int);
+       FFmpegStream (boost::shared_ptr<const cxml::Node>);
 
        void as_xml (xmlpp::Node *) const;
 
        /** @param c An AVFormatContext.
-        *  @return Stream index within the AVFormatContext.
+        *  @param index A stream index within the AVFormatContext.
+        *  @return true if this FFmpegStream uses the given stream index.
         */
-       int index (AVFormatContext const * c) const;
+       bool uses_index (AVFormatContext const * c, int index) const;
        AVStream* stream (AVFormatContext const * c) const;
 
+       std::string technical_summary () const {
+               return "id " + boost::lexical_cast<std::string> (_id);
+       }
+
+       std::string identifier () const {
+               return boost::lexical_cast<std::string> (_id);
+       }
+
        std::string name;
-       int id;
+
+       friend bool operator== (FFmpegStream const & a, FFmpegStream const & b);
+       friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b);
        
 private:
-       /** If this is true, id is in fact the index */
-       bool _legacy_id;
+       int _id;
 };
 
 class FFmpegAudioStream : public FFmpegStream
@@ -92,9 +101,6 @@ private:
        {}
 };
 
-extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
-extern bool operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
-
 class FFmpegSubtitleStream : public FFmpegStream
 {
 public:
@@ -102,14 +108,11 @@ public:
                : FFmpegStream (n, i)
        {}
        
-       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>, int);
+       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
 
        void as_xml (xmlpp::Node *) const;
 };
 
-extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
-extern bool operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
-
 class FFmpegContentProperty : public VideoContentProperty
 {
 public:
index fff9824895565bcc854129424ce8c40133344966..26b713dd56b0f0be8234c5b125c4bd86128b51f0 100644 (file)
@@ -167,9 +167,9 @@ FFmpegDecoder::pass ()
        
        if (si == _video_stream && _decode_video) {
                decode_video_packet ();
-       } else if (_ffmpeg_content->audio_stream() && si == _ffmpeg_content->audio_stream()->index (_format_context) && _decode_audio) {
+       } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
                decode_audio_packet ();
-       } else if (_ffmpeg_content->subtitle_stream() && si == _ffmpeg_content->subtitle_stream()->index (_format_context) && film->with_subtitles ()) {
+       } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
                decode_subtitle_packet ();
        }
 
@@ -521,15 +521,19 @@ FFmpegDecoder::setup_subtitle ()
 {
        boost::mutex::scoped_lock lm (_mutex);
        
-       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->index (_format_context) >= int (_format_context->nb_streams)) {
+       if (!_ffmpeg_content->subtitle_stream()) {
                return;
        }
 
        _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
+       if (_subtitle_codec_context == 0) {
+               throw DecodeError (N_("could not find subtitle stream"));
+       }
+
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 
        if (_subtitle_codec == 0) {
-               throw DecodeError (_("could not find subtitle decoder"));
+               throw DecodeError (N_("could not find subtitle decoder"));
        }
        
        if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
index 86dec9a8fe40be642de5ce881f1bdd0fa55e29b5..e439566a10367ee105f9bbc5311098191931f38a 100644 (file)
@@ -75,13 +75,13 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
 
                if (_packet.stream_index == _video_stream && !_first_video) {
                        if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                               _first_video = frame_time (_video_stream);
+                               _first_video = frame_time (_format_context->streams[_video_stream]);
                        }
                } else {
                        for (size_t i = 0; i < _audio_streams.size(); ++i) {
-                               if (_packet.stream_index == _audio_streams[i]->index (_format_context) && !_audio_streams[i]->first_audio) {
+                               if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index) && !_audio_streams[i]->first_audio) {
                                        if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                                               _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->index (_format_context));
+                                               _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->stream (_format_context));
                                        }
                                }
                        }
@@ -103,13 +103,13 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
 }
 
 optional<double>
-FFmpegExaminer::frame_time (int stream) const
+FFmpegExaminer::frame_time (AVStream* s) const
 {
        optional<double> t;
        
        int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
        if (bet != AV_NOPTS_VALUE) {
-               t = bet * av_q2d (_format_context->streams[stream]->time_base);
+               t = bet * av_q2d (s->time_base);
        }
 
        return t;
index 40d7dbf1d8358ff2a8eabfe71a7b58187464de3b..81275a9e1733c4223f9a94610b1a2a6a1748ae7f 100644 (file)
@@ -49,7 +49,7 @@ private:
        std::string stream_name (AVStream* s) const;
        std::string audio_stream_name (AVStream* s) const;
        std::string subtitle_stream_name (AVStream* s) const;
-       boost::optional<double> frame_time (int) const;
+       boost::optional<double> frame_time (AVStream* s) const;
        
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
index 774c1b392c715c7da834d6b0dd0dce1886477cd1..901c512844c9da3585c74fe5149c3a2e1df2b702 100644 (file)
@@ -86,14 +86,14 @@ using dcp::Signer;
  * 6 -> 7
  * Subtitle offset changed to subtitle y offset, and subtitle x offset added.
  */
-int const Film::state_version = 7;
+int const Film::current_state_version = 7;
 
 /** Construct a Film object in a given directory.
  *
  *  @param dir Film directory.
  */
 
-Film::Film (boost::filesystem::path dir)
+Film::Film (boost::filesystem::path dir, bool log)
        : _playlist (new Playlist)
        , _use_dci_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
@@ -106,10 +106,11 @@ Film::Film (boost::filesystem::path dir)
        , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
        , _dci_metadata (Config::instance()->default_dci_metadata ())
        , _video_frame_rate (24)
-       , _audio_channels (MAX_AUDIO_CHANNELS)
+       , _audio_channels (6)
        , _three_d (false)
        , _sequence_video (true)
        , _interop (false)
+       , _state_version (current_state_version)
        , _dirty (false)
 {
        set_dci_date_today ();
@@ -136,7 +137,11 @@ Film::Film (boost::filesystem::path dir)
        }
 
        set_directory (result);
-       _log.reset (new FileLog (file ("log")));
+       if (log) {
+               _log.reset (new FileLog (file ("log")));
+       } else {
+               _log.reset (new NullLog);
+       }
 
        _playlist->set_sequence_video (_sequence_video);
 }
@@ -327,22 +332,15 @@ Film::encoded_frames () const
        return N;
 }
 
-/** Write state to our `metadata' file */
-void
-Film::write_metadata () const
+shared_ptr<xmlpp::Document>
+Film::metadata () const
 {
-       if (!boost::filesystem::exists (directory ())) {
-               boost::filesystem::create_directory (directory ());
-       }
-       
        LocaleGuard lg;
 
-       boost::filesystem::create_directories (directory ());
-
-       xmlpp::Document doc;
-       xmlpp::Element* root = doc.create_root_node ("Metadata");
+       shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
+       xmlpp::Element* root = doc->create_root_node ("Metadata");
 
-       root->add_child("Version")->add_child_text (lexical_cast<string> (state_version));
+       root->add_child("Version")->add_child_text (lexical_cast<string> (current_state_version));
        root->add_child("Name")->add_child_text (_name);
        root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
 
@@ -370,8 +368,16 @@ Film::write_metadata () const
        root->add_child("Key")->add_child_text (_key.hex ());
        _playlist->as_xml (root->add_child ("Playlist"));
 
-       doc.write_to_file_formatted (file("metadata.xml").string ());
-       
+       return doc;
+}
+
+/** Write state to our `metadata' file */
+void
+Film::write_metadata () const
+{
+       boost::filesystem::create_directories (directory ());
+       shared_ptr<xmlpp::Document> doc = metadata ();
+       doc->write_to_file_formatted (file("metadata.xml").string ());
        _dirty = false;
 }
 
@@ -388,7 +394,7 @@ Film::read_metadata ()
        cxml::Document f ("Metadata");
        f.read_file (file ("metadata.xml"));
 
-       int const version = f.number_child<int> ("Version");
+       _state_version = f.number_child<int> ("Version");
        
        _name = f.string_child ("Name");
        _use_dci_name = f.bool_child ("UseDCIName");
@@ -421,7 +427,7 @@ Film::read_metadata ()
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
        _key = dcp::Key (f.string_child ("Key"));
-       _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), version);
+       _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version);
 
        _dirty = false;
 }
index 68916c7b097202509ab77e8f832d416b7ff42c72..0a474bab7f91b52b530cf12cb261da437e22f545 100644 (file)
@@ -56,7 +56,7 @@ class Screen;
 class Film : public boost::enable_shared_from_this<Film>, public boost::noncopyable
 {
 public:
-       Film (boost::filesystem::path);
+       Film (boost::filesystem::path, bool log = true);
 
        boost::filesystem::path info_dir () const;
        boost::filesystem::path j2c_path (int, Eyes, bool) const;
@@ -85,6 +85,7 @@ public:
 
        void read_metadata ();
        void write_metadata () const;
+       boost::shared_ptr<xmlpp::Document> metadata () const;
 
        std::string dci_name (bool if_created_now) const;
        std::string dcp_name (bool if_created_now = false) const;
@@ -138,6 +139,10 @@ public:
                return _key;
        }
 
+       int state_version () const {
+               return _state_version;
+       }
+
        /** Identifiers for the parts of our state;
            used for signalling changes.
        */
@@ -271,7 +276,7 @@ public:
        mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
 
        /** Current version number of the state file */
-       static int const state_version;
+       static int const current_state_version;
 
 private:
 
@@ -325,6 +330,8 @@ private:
        bool _interop;
        dcp::Key _key;
 
+       int _state_version;
+
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
 
index ce97ba2b2f13c6e2de3a8b69346fd752893995db..a312e738124d93b2e6aa518df0ce0db790f68a17 100644 (file)
@@ -239,7 +239,7 @@ Job::set_progress (float p, bool force)
        }
 }
 
-/** @return fractional progress of this sub-job, or -1 if not known */
+/** @return fractional progress of the current sub-job, or -1 if not known */
 float
 Job::progress () const
 {
@@ -325,6 +325,29 @@ Job::status () const
        return s.str ();
 }
 
+string
+Job::json_status () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+
+       switch (_state) {
+       case NEW:
+               return N_("new");
+       case RUNNING:
+               return N_("running");
+       case PAUSED:
+               return N_("paused");
+       case FINISHED_OK:
+               return N_("finished_ok");
+       case FINISHED_ERROR:
+               return N_("finished_error");
+       case FINISHED_CANCELLED:
+               return N_("finished_cancelled");
+       }
+
+       return "";
+}
+
 /** @return An estimate of the remaining time for this sub-job, in seconds */
 int
 Job::remaining_time () const
index 6310da32ae69a2b87db7bdbc8cb29688b447a418..5e3127dc158a432522590ffec566e5cfa1e21bbf 100644 (file)
@@ -43,6 +43,7 @@ public:
 
        /** @return user-readable name of this job */
        virtual std::string name () const = 0;
+       virtual std::string json_name () const = 0;
        /** Run this job in the current thread. */
        virtual void run () = 0;
        
@@ -64,6 +65,7 @@ public:
 
        int elapsed_time () const;
        virtual std::string status () const;
+       std::string json_status () const;
        std::string sub_name () const {
                return _sub_name;
        }
@@ -76,6 +78,10 @@ public:
                return !_progress;
        }
 
+       boost::shared_ptr<const Film> film () const {
+               return _film;
+       }
+
        boost::signals2::signal<void()> Progress;
        /** Emitted from the UI thread when the job is finished */
        boost::signals2::signal<void()> Finished;
diff --git a/src/lib/json_server.cc b/src/lib/json_server.cc
new file mode 100644 (file)
index 0000000..f4aa292
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+#include <boost/thread.hpp>
+#include "json_server.h"
+#include "job_manager.h"
+#include "job.h"
+#include "util.h"
+#include "film.h"
+#include "transcode_job.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using std::map;
+using std::list;
+using boost::thread;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::asio::ip::tcp;
+
+#define MAX_LENGTH 512
+
+enum State {
+       AWAITING_G,
+       AWAITING_E,
+       AWAITING_T,
+       AWAITING_SPACE,
+       READING_URL,
+};
+
+JSONServer::JSONServer (int port)
+{
+       new thread (boost::bind (&JSONServer::run, this, port));
+}
+
+void
+JSONServer::run (int port)
+try
+{
+       boost::asio::io_service io_service;
+       tcp::acceptor a (io_service, tcp::endpoint (tcp::v4 (), port));
+       while (1) {
+               try {
+                       shared_ptr<tcp::socket> s (new tcp::socket (io_service));
+                       a.accept (*s);
+                       handle (s);
+               }
+               catch (...) {
+
+               }
+       }
+}
+catch (...)
+{
+
+}
+
+void
+JSONServer::handle (shared_ptr<tcp::socket> socket)
+{
+       string url;
+       State state = AWAITING_G;
+
+       while (1) {
+               char data[MAX_LENGTH];
+               boost::system::error_code error;
+               size_t len = socket->read_some (boost::asio::buffer (data), error);
+               if (error) {
+                       cout << "error.\n";
+                       break;
+               }
+
+               char* p = data;
+               char* e = data + len;
+               while (p != e) {
+
+                       State old_state = state;
+                       switch (state) {
+                       case AWAITING_G:
+                               if (*p == 'G') {
+                                       state = AWAITING_E;
+                               }
+                               break;
+                       case AWAITING_E:
+                               if (*p == 'E') {
+                                       state = AWAITING_T;
+                               }
+                               break;
+                       case AWAITING_T:
+                               if (*p == 'T') {
+                                       state = AWAITING_SPACE;
+                               }
+                               break;
+                       case AWAITING_SPACE:
+                               if (*p == ' ') {
+                                       state = READING_URL;
+                               }
+                               break;
+                       case READING_URL:
+                               if (*p == ' ') {
+                                       request (url, socket);
+                                       state = AWAITING_G;
+                                       url = "";
+                               } else {
+                                       url += *p;
+                               }
+                               break;
+                       }
+
+                       if (state == old_state && state != READING_URL) {
+                               state = AWAITING_G;
+                       }
+
+                       ++p;
+               }
+       }
+}
+
+void
+JSONServer::request (string url, shared_ptr<tcp::socket> socket)
+{
+       cout << "request: " << url << "\n";
+       
+       map<string, string> r = split_get_request (url);
+       for (map<string, string>::iterator i = r.begin(); i != r.end(); ++i) {
+               cout << i->first << " => " << i->second << "\n";
+       }
+       
+       string action;
+       if (r.find ("action") != r.end ()) {
+               action = r["action"];
+       }
+       
+       stringstream json;
+       if (action == "status") {
+               
+               list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+               
+               json << "{ \"jobs\": [";
+               for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
+
+                       json << "{ ";
+
+                       if ((*i)->film()) {
+                               json << "\"dcp\": \"" << (*i)->film()->dcp_name() << "\", ";
+                       }
+                       
+                       json << "\"name\": \""   << (*i)->json_name() << "\", "
+                            << "\"progress\": " << (*i)->progress () << ", "
+                            << "\"status\": \"" << (*i)->json_status() << "\"";
+                       json << " }";
+                       
+                       list<shared_ptr<Job> >::iterator j = i;
+                       ++j;
+                       if (j != jobs.end ()) {
+                               json << ", ";
+                       }
+               }
+               json << "] }";
+               
+               if (json.str().empty ()) {
+                       json << "{ }";
+               }
+       }
+       
+       stringstream reply;
+       reply << "HTTP/1.1 200 OK\r\n"
+             << "Content-Length: " << json.str().length() << "\r\n"
+             << "Content-Type: application/json\r\n"
+                             << "\r\n"
+             << json.str () << "\r\n";
+       cout << "reply: " << json.str() << "\n";
+       boost::asio::write (*socket, boost::asio::buffer (reply.str().c_str(), reply.str().length()));
+}
diff --git a/src/lib/json_server.h b/src/lib/json_server.h
new file mode 100644 (file)
index 0000000..6230675
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+class JSONServer
+{
+public:
+       JSONServer (int port);
+
+private:
+       void run (int port);
+       void handle (boost::shared_ptr<boost::asio::ip::tcp::socket> socket);
+       void request (std::string url, boost::shared_ptr<boost::asio::ip::tcp::socket> socket);
+};
+
+       
index 22715978a7040ba279843c8e2ab0488a8f98f7a3..0b713b0427b15c62cdda2e39455c0a5e3dab1bc3 100644 (file)
@@ -110,6 +110,12 @@ SCPDCPJob::name () const
        return _("Copy DCP to TMS");
 }
 
+string
+SCPDCPJob::json_name () const
+{
+       return N_("scp_dcp");
+}
+
 void
 SCPDCPJob::run ()
 {
@@ -223,4 +229,3 @@ SCPDCPJob::set_status (string s)
        boost::mutex::scoped_lock lm (_status_mutex);
        _status = s;
 }
-       
index bdc83af187f85a0091d31e107d554ee9dd07ef4c..e3960d73b512dac54d25124be28740d15dfd67cf 100644 (file)
@@ -29,6 +29,7 @@ public:
        SCPDCPJob (boost::shared_ptr<const Film>);
 
        std::string name () const;
+       std::string json_name () const;
        void run ();
        std::string status () const;
 
index 89bce9a1490c84735ee7d7b4725c7352cefbf3ca..8af0b556a13e7f1a5ceffcc066e9c02d0038c04d 100644 (file)
@@ -50,6 +50,12 @@ SendKDMEmailJob::name () const
        return String::compose (_("Email KDMs for %1"), _film->name());
 }
 
+string
+SendKDMEmailJob::json_name () const
+{
+       return N_("send_kdm_email");
+}
+
 void
 SendKDMEmailJob::run ()
 {
index fcab56ce524590fdf17fa21d72f6e6c7340ea517..f4d154a9183b227dc897c97c5660f9f04aef3114 100644 (file)
@@ -34,6 +34,7 @@ public:
                );
 
        std::string name () const;
+       std::string json_name () const;
        void run ();
 
 private:
index 46fc97fb31b74bdf14f6093dd0d973e34fa78709..a0537cd428b343f2190bd07a57bdaee8ad65953c 100644 (file)
@@ -50,6 +50,12 @@ TranscodeJob::name () const
        return String::compose (_("Transcode %1"), _film->name());
 }
 
+string
+TranscodeJob::json_name () const
+{
+       return N_("transcode");
+}
+
 void
 TranscodeJob::run ()
 {
index 9128206d29dc9c9e42460cafecfa4748f0a1c0db..6e3c1ead9314cc5af7bb2877f208c38ae4358fa3 100644 (file)
@@ -35,6 +35,7 @@ public:
        TranscodeJob (boost::shared_ptr<const Film> f);
        
        std::string name () const;
+       std::string json_name () const;
        void run ();
        std::string status () const;
 
index fd3a318b03419c043787db235f990efc638f3729..63b1a5395f23ac6036cc35a93b55ce0849451f2f 100644 (file)
@@ -90,6 +90,7 @@ using std::min;
 using std::max;
 using std::list;
 using std::multimap;
+using std::map;
 using std::istream;
 using std::numeric_limits;
 using std::pair;
@@ -791,10 +792,11 @@ video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frame
 string
 audio_channel_name (int c)
 {
-       assert (MAX_AUDIO_CHANNELS == 6);
+       assert (MAX_AUDIO_CHANNELS == 8);
 
        /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
-          enhancement channel (sub-woofer).
+          enhancement channel (sub-woofer).  HI is the hearing-impaired audio track and
+          VI is the visually-impaired audio track (audio describe).
        */
        string const channels[] = {
                _("Left"),
@@ -803,6 +805,8 @@ audio_channel_name (int c)
                _("Lfe (sub)"),
                _("Left surround"),
                _("Right surround"),
+               _("HI"),
+               _("VI")
        };
 
        return channels[c];
@@ -942,8 +946,54 @@ make_signer ()
        return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
 }
 
-dcp::Size
-fit_ratio_within (float ratio, dcp::Size full_frame)
+map<string, string>
+split_get_request (string url)
+{
+       enum {
+               AWAITING_QUESTION_MARK,
+               KEY,
+               VALUE
+       } state = AWAITING_QUESTION_MARK;
+       
+       map<string, string> r;
+       string k;
+       string v;
+       for (size_t i = 0; i < url.length(); ++i) {
+               switch (state) {
+               case AWAITING_QUESTION_MARK:
+                       if (url[i] == '?') {
+                               state = KEY;
+                       }
+                       break;
+               case KEY:
+                       if (url[i] == '=') {
+                               v.clear ();
+                               state = VALUE;
+                       } else {
+                               k += url[i];
+                       }
+                       break;
+               case VALUE:
+                       if (url[i] == '&') {
+                               r.insert (make_pair (k, v));
+                               k.clear ();
+                               state = KEY;
+                       } else {
+                               v += url[i];
+                       }
+                       break;
+               }
+       }
+
+       if (state == VALUE) {
+               r.insert (make_pair (k, v));
+       }
+
+       return r;
+}
+
+libdcp::Size
+fit_ratio_within (float ratio, libdcp::Size full_frame)
 {
        if (ratio < full_frame.ratio ()) {
                return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
@@ -968,3 +1018,11 @@ wrapped_av_malloc (size_t s)
        }
        return p;
 }
+               
+string
+entities_to_text (string e)
+{
+       boost::algorithm::replace_all (e, "%3A", ":");
+       boost::algorithm::replace_all (e, "%2F", "/");
+       return e;
+}
index b89c71eee096de3092ef5b7217892f271088a863..76dbda190d7b2960c335014cc56626085c816f15 100644 (file)
@@ -50,7 +50,7 @@ extern "C" {
 #undef check
 
 /** The maximum number of audio channels that we can cope with */
-#define MAX_AUDIO_CHANNELS 6
+#define MAX_AUDIO_CHANNELS 8
 
 #define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way"
 
@@ -79,6 +79,8 @@ extern boost::filesystem::path mo_path ();
 extern std::string tidy_for_filename (std::string);
 extern boost::shared_ptr<const dcp::Signer> make_signer ();
 extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
+extern std::string entities_to_text (std::string e);
+extern std::map<std::string, std::string> split_get_request (std::string url);
 
 struct FrameRateChange
 {
index a5b069184024ba981c09f8a2e4e65f26749af194..688f4047d6b564ca6d03a4535cd6b58762daa678 100644 (file)
@@ -37,6 +37,7 @@ sources = """
           job.cc
           job_manager.cc
           kdm.cc
+          json_server.cc
           log.cc
           player.cc
           playlist.cc
index 70e675e4099f52ea15bc2ff80b0370cb35b6e17b..715d420872e0838b0ea3a0d0416270bf90445c77 100644 (file)
@@ -57,6 +57,7 @@
 #include "lib/send_kdm_email_job.h"
 #include "lib/server_finder.h"
 #include "lib/update.h"
+#include "lib/content_factory.h"
 
 using std::cout;
 using std::string;
@@ -75,6 +76,7 @@ static shared_ptr<Film> film;
 static std::string log_level;
 static std::string film_to_load;
 static std::string film_to_create;
+static std::string content_to_add;
 static wxMenu* jobs_menu = 0;
 
 static void set_menu_sensitivity ();
@@ -322,6 +324,17 @@ public:
                overall_panel->SetSizer (main_sizer);
        }
 
+       void check_film_state_version (int v)
+       {
+               if (v == 4) {
+                       error_dialog (
+                               this,
+                               _("This film was created with an old version of DVD-o-matic and may not load correctly "
+                                 "in this version.  Please check the film's settings carefully.")
+                               );
+               }
+       }
+
 private:
 
        void set_film ()
@@ -403,6 +416,7 @@ private:
                        try {
                                film.reset (new Film (wx_to_std (c->GetPath ())));
                                film->read_metadata ();
+                               check_film_state_version (film->state_version ());
                                film->log()->set_level (log_level);
                                set_film ();
                        } catch (std::exception& e) {
@@ -581,7 +595,8 @@ private:
 static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
        { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
-       { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_OPTION, "c", "content", "add content file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 };
 
@@ -643,6 +658,10 @@ class App : public wxApp
                        film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
                }
 
+               if (!content_to_add.empty ()) {
+                       film->examine_and_add_content (content_factory (film, content_to_add));
+               }
+
                _frame = new Frame (_("DCP-o-matic"));
                SetTopWindow (_frame);
                _frame->Maximize ();
@@ -655,6 +674,10 @@ class App : public wxApp
                _timer.reset (new wxTimer (this));
                _timer->Start (1000);
 
+               if (film) {
+                       _frame->check_film_state_version (film->state_version ());
+               }
+
                UpdateChecker::instance()->StateChanged.connect (boost::bind (&App::update_checker_state_changed, this));
                if (Config::instance()->check_for_updates ()) {
                        UpdateChecker::instance()->run ();
@@ -680,10 +703,15 @@ class App : public wxApp
                        if (parser.Found (wxT ("new"))) {
                                film_to_create = wx_to_std (parser.GetParam (0));
                        } else {
-                               film_to_load = wx_to_std (parser.GetParam(0));
+                               film_to_load = wx_to_std (parser.GetParam (0));
                        }
                }
 
+               wxString content;
+               if (parser.Found (wxT ("content"), &content)) {
+                       content_to_add = wx_to_std (content);
+               }
+
                wxString log;
                if (parser.Found (wxT ("log"), &log)) {
                        log_level = wx_to_std (log);
@@ -734,7 +762,7 @@ class App : public wxApp
                }
        }
 
-       wxFrame* _frame;
+       Frame* _frame;
        shared_ptr<wxTimer> _timer;
 };
 
index c40ea6662b8f0c7b0b1b476933cb770a03406fcd..a74ece149af2b9e96e7cd46103917dec5bfe98f1 100644 (file)
@@ -33,6 +33,7 @@
 #include "lib/log.h"
 #include "lib/ui_signaller.h"
 #include "lib/server_finder.h"
+#include "lib/json_server.h"
 
 using std::string;
 using std::cerr;
@@ -52,6 +53,8 @@ help (string n)
             << "  -f, --flags        show flags passed to C++ compiler on build\n"
             << "  -n, --no-progress  do not print progress to stdout\n"
             << "  -r, --no-remote    do not use any remote servers\n"
+            << "  -j, --json <port>  run a JSON server on the specified port\n"
+            << "  -k, --keep-going   keep running even when the job is complete\n"
             << "\n"
             << "<FILM> is the film directory.\n";
 }
@@ -63,6 +66,8 @@ main (int argc, char* argv[])
        bool progress = true;
        bool no_remote = false;
        int log_level = 0;
+       int json_port = 0;
+       bool keep_going = false;
 
        int option_index = 0;
        while (1) {
@@ -74,10 +79,12 @@ main (int argc, char* argv[])
                        { "no-progress", no_argument, 0, 'n'},
                        { "no-remote", no_argument, 0, 'r'},
                        { "log-level", required_argument, 0, 'l' },
+                       { "json", required_argument, 0, 'j' },
+                       { "keep-going", no_argument, 0, 'k' },
                        { 0, 0, 0, 0 }
                };
 
-               int c = getopt_long (argc, argv, "vhdfnrl:", long_options, &option_index);
+               int c = getopt_long (argc, argv, "vhdfnrl:j:k", long_options, &option_index);
 
                if (c == -1) {
                        break;
@@ -105,6 +112,12 @@ main (int argc, char* argv[])
                case 'l':
                        log_level = atoi (optarg);
                        break;
+               case 'j':
+                       json_port = atoi (optarg);
+                       break;
+               case 'k':
+                       keep_going = true;
+                       break;
                }
        }
 
@@ -122,6 +135,10 @@ main (int argc, char* argv[])
                ServerFinder::instance()->disable ();
        }
 
+       if (json_port) {
+               new JSONServer (json_port);
+       }
+
        cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
        char buf[256];
        if (gethostname (buf, 256) == 0) {
@@ -201,6 +218,12 @@ main (int argc, char* argv[])
                }
        }
 
+       if (keep_going) {
+               while (1) {
+                       dcpomatic_sleep (3600);
+               }
+       }
+
        /* This is just to stop valgrind reporting leaks due to JobManager
           indirectly holding onto codecs.
        */
index 8dc4de50ef278dc66a02b9964a9fd146607086a9..05121652aa7148312affcf820a189d978d9cf08c 100644 (file)
@@ -30,6 +30,8 @@
 #include "lib/job_manager.h"
 #include "lib/ui_signaller.h"
 #include "lib/job.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
 
 using std::string;
 using std::cout;
@@ -37,22 +39,33 @@ using std::cerr;
 using std::list;
 using std::exception;
 using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
 
 static void
 help (string n)
 {
-       cerr << "Create a film directory (ready for making a DCP) from some content files.\n"
+       cerr << "Create a film directory (ready for making a DCP) or metadata file from some content files.\n"
+            << "A film directory will be created if -o or --output is specified, otherwise a metadata file\n"
+            << "will be written to stdout.\n"
             << "Syntax: " << n << " [OPTION] <CONTENT> [<CONTENT> ...]\n"
-            << "  -v, --version   show DCP-o-matic version\n"
-            << "  -h, --help      show this help\n"
-            << "  -n, --name      film name\n"
-            << "  -o, --output    output directory (required)\n";
+            << "  -v, --version                 show DCP-o-matic version\n"
+            << "  -h, --help                    show this help\n"
+            << "  -n, --name <name>             film name\n"
+            << "  -c, --dcp-content-type <type> FTR, SHR, TLR, TST, XSN, RTG, TSR, POL, PSA or ADV\n"
+            << "      --container-ratio         119, 133, 137, 138, 166, 178, 185 or 239\n"
+            << "      --content-ratio           119, 133, 137, 138, 166, 178, 185 or 239\n"
+            << "  -o, --output <dir>            output directory\n";
 }
 
 int
 main (int argc, char* argv[])
 {
+       dcpomatic_setup ();
+
        string name;
+       DCPContentType const * dcp_content_type = DCPContentType::from_dci_name ("TST");
+       Ratio const * container_ratio = 0;
+       Ratio const * content_ratio = 0;
        boost::filesystem::path output;
        
        int option_index = 0;
@@ -61,11 +74,14 @@ main (int argc, char* argv[])
                        { "version", no_argument, 0, 'v'},
                        { "help", no_argument, 0, 'h'},
                        { "name", required_argument, 0, 'n'},
+                       { "dcp-content-type", required_argument, 0, 'c'},
+                       { "container-ratio", required_argument, 0, 'A'},
+                       { "content-ratio", required_argument, 0, 'B'},
                        { "output", required_argument, 0, 'o'},
                        { 0, 0, 0, 0}
                };
 
-               int c = getopt_long (argc, argv, "vhn:o:", long_options, &option_index);
+               int c = getopt_long (argc, argv, "vhn:c:A:B:o:", long_options, &option_index);
                if (c == -1) {
                        break;
                }
@@ -80,6 +96,30 @@ main (int argc, char* argv[])
                case 'n':
                        name = optarg;
                        break;
+               case 'c':
+                       dcp_content_type = DCPContentType::from_dci_name (optarg);
+                       if (dcp_content_type == 0) {
+                               cerr << "Bad DCP content type.\n";
+                               help (argv[0]);
+                               exit (EXIT_FAILURE);
+                       }
+                       break;
+               case 'A':
+                       container_ratio = Ratio::from_id (optarg);
+                       if (container_ratio == 0) {
+                               cerr << "Bad container ratio.\n";
+                               help (argv[0]);
+                               exit (EXIT_FAILURE);
+                       }
+                       break;
+               case 'B':
+                       content_ratio = Ratio::from_id (optarg);
+                       if (content_ratio == 0) {
+                               cerr << "Bad content ratio " << optarg << ".\n";
+                               help (argv[0]);
+                               exit (EXIT_FAILURE);
+                       }
+                       break;
                case 'o':
                        output = optarg;
                        break;
@@ -91,23 +131,33 @@ main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
-       if (output.empty ()) {
-               cerr << "Missing required option -o or --output.\n"
-                    << "Use " << argv[0] << " --help for help.\n";
+       if (!content_ratio) {
+               cerr << "Missing required option --content-ratio.\n";
+               help (argv[0]);
                exit (EXIT_FAILURE);
        }
 
-       dcpomatic_setup ();
+       if (!container_ratio) {
+               container_ratio = content_ratio;
+       }
+
        ui_signaller = new UISignaller ();
 
        try {
-               shared_ptr<Film> film (new Film (output));
+               shared_ptr<Film> film (new Film (output, false));
                if (!name.empty ()) {
                        film->set_name (name);
                }
+
+               film->set_container (container_ratio);
                
                for (int i = optind; i < argc; ++i) {
-                       film->examine_and_add_content (content_factory (film, argv[i]));
+                       shared_ptr<Content> c = content_factory (film, argv[i]);
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
+                       if (vc) {
+                               vc->set_ratio (content_ratio);
+                       }
+                       film->examine_and_add_content (c);
                }
                
                JobManager* jm = JobManager::instance ();
@@ -125,8 +175,12 @@ main (int argc, char* argv[])
                        }
                        exit (EXIT_FAILURE);
                }
-               
-               film->write_metadata ();
+
+               if (!output.empty ()) {
+                       film->write_metadata ();
+               } else {
+                       film->metadata()->write_to_stream_formatted (cout);
+               }
        } catch (exception& e) {
                cerr << argv[0] << ": " << e.what() << "\n";
                exit (EXIT_FAILURE);
index 5fa5014f080a1e3723c58e11fcc1f40a05619ecd..d240328494c45cb2c0fe3337d6b1465ed30d11a3 100644 (file)
@@ -110,6 +110,7 @@ AboutDialog::AboutDialog (wxWindow* parent)
        supported_by.Add (wxT ("Mattias Mattsson"));
        supported_by.Add (wxT ("Andrä Steiner"));
        supported_by.Add (wxT ("Jonathan Jensen"));
+       supported_by.Add (wxT ("Mike Stiebing"));
        supported_by.Add (wxT ("Kjarten Michaelsen"));
        supported_by.Add (wxT ("Jussi Siponen"));
        supported_by.Add (wxT ("Cinema Clarici"));
@@ -130,8 +131,41 @@ AboutDialog::AboutDialog (wxWindow* parent)
        supported_by.Add (wxT ("Sylvain Mielle"));
        supported_by.Add (wxT ("Ivan Pullman"));
        supported_by.Add (wxT ("Aldo Midali"));
+       supported_by.Add (wxT ("Jeff Boot"));
+       supported_by.Add (wxT ("Filip Kovcin"));
+       supported_by.Add (wxT ("Adam Colt"));
        add_section (_("Supported by"), supported_by);
 
+       wxArrayString testers;
+       testers.Add (wxT ("Greg Rooke"));
+       testers.Add (wxT ("Olivier Lemaire"));
+       testers.Add (wxT ("Trever Anderson"));
+       testers.Add (wxT ("Wolfgang Woehl"));
+       testers.Add (wxT ("Jonathan Jensen"));
+       testers.Add (wxT ("Anders Nordentoft-Madsen"));
+       testers.Add (wxT ("Lilian Lefranc"));
+       testers.Add (wxT ("Gérald Maruccia"));
+       testers.Add (wxT ("John Convertino"));
+       testers.Add (wxT ("Mike Blakesley"));
+       testers.Add (wxT ("Simon Kesselman"));
+       testers.Add (wxT ("Gavin Lewarne"));
+       testers.Add (wxT ("Thierry Journet"));
+       testers.Add (wxT ("Carsten Kurz"));
+       testers.Add (wxT ("Karim Senoucci"));
+       testers.Add (wxT ("Paul Willmott"));
+       testers.Add (wxT ("Mattias Mattsson"));
+       testers.Add (wxT ("Andreas Eli"));
+       testers.Add (wxT ("Roop Chand"));
+       testers.Add (wxT ("Peter Puchner"));
+       testers.Add (wxT ("David Booty"));
+       testers.Add (wxT ("Maurizio Giampà"));
+       testers.Add (wxT ("Bill Lam"));
+       testers.Add (wxT ("Pepijn Klijs"));
+       testers.Add (wxT ("Will Meadows"));
+       testers.Add (wxT ("Adam Colt"));
+       testers.Add (wxT ("Markus Raab"));
+       add_section (_("Tested by"), testers);
+       
        sizer->Add (_notebook, wxSizerFlags().Centre().Border(wxALL, 16).Expand());
 
        overall_sizer->Add (sizer);
index 75c8ea5de0fcf2d78b5a9be421019badc69809bb..86f0399003787643955d7f66cf5df7ec9acb1f9c 100644 (file)
@@ -271,6 +271,10 @@ AudioMappingView::set_column_labels ()
        int const c = _grid->GetNumberCols ();
        
        _grid->SetColLabelValue (0, _("Content channel"));
+
+#if MAX_AUDIO_CHANNELS != 8
+#warning AudioMappingView::set_column_labels() is expecting the wrong MAX_AUDIO_CHANNELS
+#endif 
        
        if (c > 0) {
                _grid->SetColLabelValue (1, _("L"));
@@ -296,6 +300,14 @@ AudioMappingView::set_column_labels ()
                _grid->SetColLabelValue (6, _("Rs"));
        }
 
+       if (c > 6) {
+               _grid->SetColLabelValue (7, _("HI"));
+       }
+
+       if (c > 7) {
+               _grid->SetColLabelValue (8, _("VI"));
+       }
+       
        _grid->AutoSize ();
 }
 
index ba458f1ff813f58dc596a850884b7b7c666df874..0838fa8abb9a7a9241470db621091a3297169d1a 100644 (file)
@@ -134,11 +134,11 @@ AudioPanel::film_content_changed (int property)
                if (fcs) {
                        vector<shared_ptr<FFmpegAudioStream> > a = fcs->audio_streams ();
                        for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
-                               _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+                               _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx ((*i)->identifier ())));
                        }
                        
                        if (fcs->audio_stream()) {
-                               checked_set (_stream, lexical_cast<string> (fcs->audio_stream()->id));
+                               checked_set (_stream, fcs->audio_stream()->identifier ());
                                setup_stream_description ();
                        }
                }
@@ -206,7 +206,7 @@ AudioPanel::stream_changed ()
        vector<shared_ptr<FFmpegAudioStream> > a = fcs->audio_streams ();
        vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
        string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
-       while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+       while (i != a.end() && (*i)->identifier () != s) {
                ++i;
        }
 
index 02c8776d6f8c5ea3a57e50e4885cf34bd6196d72..63a18b0ce874ce5daea97ded5204ef92287ac4ba 100644 (file)
@@ -120,11 +120,11 @@ SubtitlePanel::film_content_changed (int property)
                if (fcs) {
                        vector<shared_ptr<FFmpegSubtitleStream> > s = fcs->subtitle_streams ();
                        for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
-                               _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+                               _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx ((*i)->identifier ())));
                        }
                        
                        if (fcs->subtitle_stream()) {
-                               checked_set (_stream, lexical_cast<string> (fcs->subtitle_stream()->id));
+                               checked_set (_stream, fcs->subtitle_stream()->identifier ());
                        } else {
                                _stream->SetSelection (wxNOT_FOUND);
                        }
@@ -179,7 +179,7 @@ SubtitlePanel::stream_changed ()
        vector<shared_ptr<FFmpegSubtitleStream> > a = fcs->subtitle_streams ();
        vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
        string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
-       while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+       while (i != a.end() && (*i)->identifier () != s) {
                ++i;
        }
 
index a3ad1bb6c5936589e475d1d2cf4078d38fc1e2e7..4d32b1e0ccf8692497f5be9778b594d2485b98d2 100644 (file)
@@ -54,6 +54,10 @@ public:
        string name () const {
                return "";
        }
+
+       string json_name () const {
+               return "";
+       }
 };
 
 BOOST_AUTO_TEST_CASE (job_manager_test)
index c8073c1c8032342a2c4b807c3ad774860b6ce48e..1cd7e4a4270f60144ebd706cc15468c9193710e6 100644 (file)
@@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE (stream_test)
                
        FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)), 5);
 
-       BOOST_CHECK_EQUAL (a.id, 4);
+       BOOST_CHECK_EQUAL (a.identifier(), "4");
        BOOST_CHECK_EQUAL (a.frame_rate, 44100);
        BOOST_CHECK_EQUAL (a.channels, 2);
        BOOST_CHECK_EQUAL (a.name, "hello there world");
diff --git a/wscript b/wscript
index ebf444c8f6dc1566b7f51ab6c58bd91a4c66b483..708e48910977ceb7c68017281e735b5293bb4eb9 100644 (file)
--- a/wscript
+++ b/wscript
@@ -55,9 +55,9 @@ def dynamic_openjpeg(conf):
     conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.1', mandatory=True)
 
 def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
-    conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
+    conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
     conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
-    conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+    conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp', 'kumu-libdcp']
     conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
 
     if static_boost:
@@ -81,7 +81,7 @@ def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
         conf.env.LIB_DCP.append('ssh')
 
 def dynamic_dcp(conf):
-    conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+    conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
     conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
 
 def dynamic_ssh(conf):