Basics of multiple captions per content so that DCPContent can
authorCarl Hetherington <cth@carlh.net>
Sat, 21 Jul 2018 14:16:18 +0000 (15:16 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 21 Jul 2018 14:16:18 +0000 (15:16 +0100)
hold subs and closed captions.

59 files changed:
src/lib/active_captions.cc
src/lib/active_captions.h
src/lib/analyse_audio_job.cc
src/lib/caption_content.cc
src/lib/caption_content.h
src/lib/content.cc
src/lib/content.h
src/lib/dcp_content.cc
src/lib/dcp_content.h
src/lib/dcp_decoder.cc
src/lib/dcp_decoder.h
src/lib/dcp_encoder.cc
src/lib/dcp_examiner.cc
src/lib/dcp_examiner.h
src/lib/dcp_subtitle_content.cc
src/lib/dcp_subtitle_decoder.cc
src/lib/decoder.cc
src/lib/decoder.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/hints.cc
src/lib/overlaps.cc
src/lib/overlaps.h
src/lib/player.cc
src/lib/player.h
src/lib/playlist.cc
src/lib/playlist.h
src/lib/text_caption_file_content.cc
src/lib/text_caption_file_decoder.cc
src/lib/types.h
src/tools/dcpomatic.cc
src/tools/dcpomatic_player.cc
src/wx/caption_appearance_dialog.cc
src/wx/caption_appearance_dialog.h
src/wx/caption_panel.cc
src/wx/caption_panel.h
src/wx/caption_view.cc
src/wx/caption_view.h
src/wx/content_panel.cc
src/wx/dcp_panel.cc
src/wx/fonts_dialog.cc
src/wx/fonts_dialog.h
src/wx/player_information.cc
src/wx/timeline.cc
src/wx/timeline_labels_view.cc
src/wx/timeline_labels_view.h
src/wx/timeline_text_content_view.cc
src/wx/timeline_text_content_view.h
src/wx/timing_panel.cc
test/closed_caption_test.cc
test/dcp_subtitle_test.cc
test/ffmpeg_encoder_test.cc
test/player_test.cc
test/remake_with_subtitle_test.cc
test/srt_subtitle_test.cc
test/ssa_subtitle_test.cc
test/subtitle_reel_number_test.cc
test/vf_test.cc

index b84c75a7cda99325f8ed39c3de7df307018db8e7..b4252e0c3ce7ba195a90b39e1a982fbb44d189b3 100644 (file)
@@ -41,7 +41,7 @@ ActiveCaptions::get_burnt (DCPTimePeriod period, bool always_burn_captions) cons
 
        for (Map::const_iterator i = _data.begin(); i != _data.end(); ++i) {
 
-               shared_ptr<CaptionContent> caption = i->first.lock ();
+               shared_ptr<const CaptionContent> caption = i->first.lock ();
                if (!caption) {
                        continue;
                }
@@ -90,7 +90,7 @@ ActiveCaptions::clear_before (DCPTime time)
  *  @param from From time for these subtitles.
  */
 void
-ActiveCaptions::add_from (weak_ptr<CaptionContent> content, PlayerCaption ps, DCPTime from)
+ActiveCaptions::add_from (weak_ptr<const CaptionContent> content, PlayerCaption ps, DCPTime from)
 {
        if (_data.find(content) == _data.end()) {
                _data[content] = list<Period>();
@@ -104,7 +104,7 @@ ActiveCaptions::add_from (weak_ptr<CaptionContent> content, PlayerCaption ps, DC
  *  @return Return the corresponding subtitles and their from time.
  */
 pair<PlayerCaption, DCPTime>
-ActiveCaptions::add_to (weak_ptr<CaptionContent> content, DCPTime to)
+ActiveCaptions::add_to (weak_ptr<const CaptionContent> content, DCPTime to)
 {
        DCPOMATIC_ASSERT (_data.find(content) != _data.end());
 
@@ -121,7 +121,7 @@ ActiveCaptions::add_to (weak_ptr<CaptionContent> content, DCPTime to)
  *  @return true if we have any active subtitles from this content.
  */
 bool
-ActiveCaptions::have (weak_ptr<CaptionContent> content) const
+ActiveCaptions::have (weak_ptr<const CaptionContent> content) const
 {
        Map::const_iterator i = _data.find(content);
        if (i == _data.end()) {
index 28ddcad52c5939867be6cda3fad77b282073e8f7..10b0b5da93cb8399779eba2257cd0d55a57c6498 100644 (file)
@@ -39,9 +39,9 @@ public:
        std::list<PlayerCaption> get_burnt (DCPTimePeriod period, bool always_burn_captions) const;
        void clear_before (DCPTime time);
        void clear ();
-       void add_from (boost::weak_ptr<CaptionContent> content, PlayerCaption ps, DCPTime from);
-       std::pair<PlayerCaption, DCPTime> add_to (boost::weak_ptr<CaptionContent> content, DCPTime to);
-       bool have (boost::weak_ptr<CaptionContent> content) const;
+       void add_from (boost::weak_ptr<const CaptionContent> content, PlayerCaption ps, DCPTime from);
+       std::pair<PlayerCaption, DCPTime> add_to (boost::weak_ptr<const CaptionContent> content, DCPTime to);
+       bool have (boost::weak_ptr<const CaptionContent> content) const;
 
 private:
        class Period
@@ -59,7 +59,7 @@ private:
                boost::optional<DCPTime> to;
        };
 
-       typedef std::map<boost::weak_ptr<CaptionContent>, std::list<Period> > Map;
+       typedef std::map<boost::weak_ptr<const CaptionContent>, std::list<Period> > Map;
 
        Map _data;
 };
index db917301aaac01d9f37a111f1126f6998f862e50..03497b91e4208140864d562b442d7eb563a1bd8a 100644 (file)
@@ -106,7 +106,7 @@ AnalyseAudioJob::run ()
 {
        shared_ptr<Player> player (new Player (_film, _playlist));
        player->set_ignore_video ();
-       player->set_ignore_subtitle ();
+       player->set_ignore_caption ();
        player->set_fast ();
        player->set_play_referenced ();
        player->Audio.connect (bind (&AnalyseAudioJob::analyse, this, _1, _2));
index af534980a47a42d8d21008fc9602f9edcb8ae810..d44fb55c5502a8faabc61fe122e90d20671c0f5e 100644 (file)
@@ -68,11 +68,15 @@ CaptionContent::CaptionContent (Content* parent)
        , _line_spacing (1)
        , _outline_width (2)
        , _type (CAPTION_OPEN)
+       , _original_type (CAPTION_OPEN)
 {
 
 }
 
-shared_ptr<CaptionContent>
+/** @return CaptionContents from node or <Caption> nodes under node (according to version).
+ *  The list could be empty if no CaptionContents are found.
+ */
+list<shared_ptr<CaptionContent> >
 CaptionContent::from_xml (Content* parent, cxml::ConstNodePtr node, int version)
 {
        if (version < 34) {
@@ -80,7 +84,7 @@ CaptionContent::from_xml (Content* parent, cxml::ConstNodePtr node, int version)
                   subtitle streams, so check for that.
                */
                if (node->string_child("Type") == "FFmpeg" && node->node_children("SubtitleStream").empty()) {
-                       return shared_ptr<CaptionContent> ();
+                       return list<shared_ptr<CaptionContent> >();
                }
 
                /* Otherwise we can drop through to the newer logic */
@@ -88,16 +92,22 @@ CaptionContent::from_xml (Content* parent, cxml::ConstNodePtr node, int version)
 
        if (version < 37) {
                if (!node->optional_number_child<double>("SubtitleXOffset") && !node->optional_number_child<double>("SubtitleOffset")) {
-                       return shared_ptr<CaptionContent> ();
+                       return list<shared_ptr<CaptionContent> >();
                }
-               return shared_ptr<CaptionContent> (new CaptionContent (parent, node, version));
+               list<shared_ptr<CaptionContent> > c;
+               c.push_back (shared_ptr<CaptionContent> (new CaptionContent (parent, node, version)));
+               return c;
        }
 
        if (!node->node_child("Caption")) {
-               return shared_ptr<CaptionContent> ();
+               return list<shared_ptr<CaptionContent> >();
        }
 
-       return shared_ptr<CaptionContent> (new CaptionContent (parent, node->node_child("Caption"), version));
+       list<shared_ptr<CaptionContent> > c;
+       BOOST_FOREACH (cxml::ConstNodePtr i, node->node_children("Caption")) {
+               c.push_back (shared_ptr<CaptionContent> (new CaptionContent (parent, i, version)));
+       }
+       return c;
 }
 
 CaptionContent::CaptionContent (Content* parent, cxml::ConstNodePtr node, int version)
@@ -215,6 +225,7 @@ CaptionContent::CaptionContent (Content* parent, cxml::ConstNodePtr node, int ve
        connect_to_fonts ();
 
        _type = string_to_caption_type (node->optional_string_child("Type").get_value_or("open"));
+       _original_type = string_to_caption_type (node->optional_string_child("Type").get_value_or("open"));
 }
 
 
@@ -270,6 +281,7 @@ CaptionContent::as_xml (xmlpp::Node* root) const
        }
 
        caption->add_child("Type")->add_child_text (caption_type_to_string(_type));
+       caption->add_child("OriginalType")->add_child_text (caption_type_to_string(_original_type));
 }
 
 string
index 297289f18ff2be7b99d9677d2d1c76b7de92b868..4152dc533678e195b7cc5be62d01a7f1699e7f26 100644 (file)
@@ -167,7 +167,12 @@ public:
                return _type;
        }
 
-       static boost::shared_ptr<CaptionContent> from_xml (Content* parent, cxml::ConstNodePtr, int version);
+       CaptionType original_type () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _original_type;
+       }
+
+       static std::list<boost::shared_ptr<CaptionContent> > from_xml (Content* parent, cxml::ConstNodePtr, int version);
 
 protected:
        /** subtitle language (e.g. "German") or empty if it is not known */
@@ -205,7 +210,12 @@ private:
        boost::optional<ContentTime> _fade_in;
        boost::optional<ContentTime> _fade_out;
        int _outline_width;
+       /** what these captions will be used for in the output DCP (not necessarily what
+        *  they were originally).
+        */
        CaptionType _type;
+       /** the original type of these captions in their content */
+       CaptionType _original_type;
 };
 
 #endif
index d232f6e49c573a72d10da38a534ee7c1eb750b7f..13c5794fe9eb46d087ba5faebf2881ed5424540a 100644 (file)
@@ -402,7 +402,34 @@ Content::take_settings_from (shared_ptr<const Content> c)
        if (audio && c->audio) {
                audio->take_settings_from (c->audio);
        }
-       if (caption && c->caption) {
-               caption->take_settings_from (c->caption);
+
+       list<shared_ptr<CaptionContent> >::iterator i = caption.begin ();
+       list<shared_ptr<CaptionContent> >::const_iterator j = c->caption.begin ();
+       while (i != caption.end() && j != c->caption.end()) {
+               (*i)->take_settings_from (*j);
+               ++i;
+               ++j;
+       }
+}
+
+shared_ptr<CaptionContent>
+Content::only_caption () const
+{
+       DCPOMATIC_ASSERT (caption.size() < 2);
+       if (caption.empty ()) {
+               return shared_ptr<CaptionContent> ();
+       }
+       return caption.front ();
+}
+
+shared_ptr<CaptionContent>
+Content::caption_of_original_type (CaptionType type) const
+{
+       BOOST_FOREACH (shared_ptr<CaptionContent> i, caption) {
+               if (i->original_type() == type) {
+                       return i;
+               }
        }
+
+       return shared_ptr<CaptionContent> ();
 }
index 1e594e136bd50ca22246c623565f16c98e3de2e4..2a249011a55f4d889383b8a76702d8daaf71893d 100644 (file)
@@ -181,7 +181,10 @@ public:
 
        boost::shared_ptr<VideoContent> video;
        boost::shared_ptr<AudioContent> audio;
-       boost::shared_ptr<CaptionContent> caption;
+       std::list<boost::shared_ptr<CaptionContent> > caption;
+
+       boost::shared_ptr<CaptionContent> only_caption () const;
+       boost::shared_ptr<CaptionContent> caption_of_original_type (CaptionType type) const;
 
        void signal_changed (int);
 
index e02eb7bd90ea6bc66097defc62e88d6352b21d41..e56ad9e21f7e7ccaa086ed646a962a38548e475f 100644 (file)
@@ -58,9 +58,9 @@ int const DCPContentProperty::NEEDS_ASSETS       = 600;
 int const DCPContentProperty::NEEDS_KDM          = 601;
 int const DCPContentProperty::REFERENCE_VIDEO    = 602;
 int const DCPContentProperty::REFERENCE_AUDIO    = 603;
-int const DCPContentProperty::REFERENCE_SUBTITLE = 604;
+int const DCPContentProperty::REFERENCE_CAPTION  = 604;
 int const DCPContentProperty::NAME               = 605;
-int const DCPContentProperty::HAS_SUBTITLES      = 606;
+int const DCPContentProperty::CAPTIONS           = 606;
 
 DCPContent::DCPContent (shared_ptr<const Film> film, boost::filesystem::path p)
        : Content (film)
@@ -69,11 +69,14 @@ DCPContent::DCPContent (shared_ptr<const Film> film, boost::filesystem::path p)
        , _kdm_valid (false)
        , _reference_video (false)
        , _reference_audio (false)
-       , _reference_subtitle (false)
        , _three_d (false)
 {
        read_directory (p);
        set_default_colour_conversion ();
+
+       for (int i = 0; i < CAPTION_COUNT; ++i) {
+               _reference_caption[i] = false;
+       }
 }
 
 DCPContent::DCPContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version)
@@ -83,6 +86,10 @@ DCPContent::DCPContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, in
        audio = AudioContent::from_xml (this, node, version);
        caption = CaptionContent::from_xml (this, node, version);
 
+       for (int i = 0; i < CAPTION_COUNT; ++i) {
+               _reference_caption[i] = false;
+       }
+
        if (video && audio) {
                audio->set_stream (
                        AudioStreamPtr (
@@ -107,7 +114,13 @@ DCPContent::DCPContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, in
        _kdm_valid = node->bool_child ("KDMValid");
        _reference_video = node->optional_bool_child ("ReferenceVideo").get_value_or (false);
        _reference_audio = node->optional_bool_child ("ReferenceAudio").get_value_or (false);
-       _reference_subtitle = node->optional_bool_child ("ReferenceSubtitle").get_value_or (false);
+       if (version >= 37) {
+               _reference_caption[CAPTION_OPEN] = node->optional_bool_child("ReferenceOpenCaption").get_value_or(false);
+               _reference_caption[CAPTION_CLOSED] = node->optional_bool_child("ReferenceClosedCaption").get_value_or(false);
+       } else {
+               _reference_caption[CAPTION_OPEN] = node->optional_bool_child("ReferenceSubtitle").get_value_or(false);
+               _reference_caption[CAPTION_CLOSED] = false;
+       }
        if (node->optional_string_child("Standard")) {
                string const s = node->optional_string_child("Standard").get();
                if (s == "Interop") {
@@ -143,7 +156,7 @@ DCPContent::examine (shared_ptr<Job> job)
        bool const needed_assets = needs_assets ();
        bool const needed_kdm = needs_kdm ();
        string const old_name = name ();
-       bool had_subtitles = static_cast<bool> (caption);
+       int const old_captions = caption.size ();
 
        if (job) {
                job->set_progress_unknown ();
@@ -174,16 +187,14 @@ DCPContent::examine (shared_ptr<Job> job)
                signal_changed (AudioContentProperty::STREAMS);
        }
 
-       bool has_subtitles = false;
+       int captions = 0;
        {
                boost::mutex::scoped_lock lm (_mutex);
                _name = examiner->name ();
-               if (examiner->has_subtitles ()) {
-                       caption.reset (new CaptionContent (this));
-               } else {
-                       caption.reset ();
+               for (int i = 0; i < examiner->captions(); ++i) {
+                       caption.push_back (shared_ptr<CaptionContent> (new CaptionContent (this)));
                }
-               has_subtitles = static_cast<bool> (caption);
+               captions = caption.size ();
                _encrypted = examiner->encrypted ();
                _needs_assets = examiner->needs_assets ();
                _kdm_valid = examiner->kdm_valid ();
@@ -193,8 +204,8 @@ DCPContent::examine (shared_ptr<Job> job)
                _reel_lengths = examiner->reel_lengths ();
        }
 
-       if (had_subtitles != has_subtitles) {
-               signal_changed (DCPContentProperty::HAS_SUBTITLES);
+       if (old_captions != captions) {
+               signal_changed (DCPContentProperty::CAPTIONS);
        }
 
        if (needed_assets != needs_assets ()) {
@@ -254,8 +265,8 @@ DCPContent::as_xml (xmlpp::Node* node, bool with_paths) const
                audio->stream()->mapping().as_xml (node->add_child("AudioMapping"));
        }
 
-       if (caption) {
-               caption->as_xml (node);
+       BOOST_FOREACH (shared_ptr<CaptionContent> i, caption) {
+               i->as_xml (node);
        }
 
        boost::mutex::scoped_lock lm (_mutex);
@@ -268,7 +279,8 @@ DCPContent::as_xml (xmlpp::Node* node, bool with_paths) const
        node->add_child("KDMValid")->add_child_text (_kdm_valid ? "1" : "0");
        node->add_child("ReferenceVideo")->add_child_text (_reference_video ? "1" : "0");
        node->add_child("ReferenceAudio")->add_child_text (_reference_audio ? "1" : "0");
-       node->add_child("ReferenceSubtitle")->add_child_text (_reference_subtitle ? "1" : "0");
+       node->add_child("ReferenceOpenCaption")->add_child_text(_reference_caption[CAPTION_OPEN] ? "1" : "0");
+       node->add_child("ReferenceClosedCaption")->add_child_text(_reference_caption[CAPTION_CLOSED] ? "1" : "0");
        if (_standard) {
                switch (_standard.get ()) {
                case dcp::INTEROP:
@@ -309,11 +321,14 @@ DCPContent::identifier () const
                s += video->identifier() + "_";
        }
 
-       if (caption) {
-               s += caption->identifier () + " ";
+       BOOST_FOREACH (shared_ptr<CaptionContent> i, caption) {
+               s += i->identifier () + " ";
        }
 
-       s += string (_reference_video ? "1" : "0") + string (_reference_subtitle ? "1" : "0");
+       s += string (_reference_video ? "1" : "0");
+       for (int i = 0; i < CAPTION_COUNT; ++i) {
+               s += string (_reference_caption[i] ? "1" : "0");
+       }
        return s;
 }
 
@@ -399,14 +414,14 @@ DCPContent::set_reference_audio (bool r)
 }
 
 void
-DCPContent::set_reference_subtitle (bool r)
+DCPContent::set_reference_caption (CaptionType type, bool r)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
-               _reference_subtitle = r;
+               _reference_caption[type] = r;
        }
 
-       signal_changed (DCPContentProperty::REFERENCE_SUBTITLE);
+       signal_changed (DCPContentProperty::REFERENCE_CAPTION);
 }
 
 list<DCPTimePeriod>
@@ -459,7 +474,7 @@ DCPContent::reel_split_points () const
 }
 
 bool
-DCPContent::can_reference (function<shared_ptr<ContentPart> (shared_ptr<const Content>)> part, string overlapping, string& why_not) const
+DCPContent::can_reference (function<bool (shared_ptr<const Content>)> part, string overlapping, string& why_not) const
 {
        /* We must be using the same standard as the film */
        if (_standard) {
@@ -514,6 +529,12 @@ DCPContent::can_reference (function<shared_ptr<ContentPart> (shared_ptr<const Co
        return true;
 }
 
+static
+bool check_video (shared_ptr<const Content> c)
+{
+       return static_cast<bool>(c->video);
+}
+
 bool
 DCPContent::can_reference_video (string& why_not) const
 {
@@ -529,7 +550,13 @@ DCPContent::can_reference_video (string& why_not) const
        }
 
        /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
-       return can_reference (bind (&Content::video, _1), _("it overlaps other video content; remove the other content."), why_not);
+       return can_reference (bind (&check_video, _1), _("it overlaps other video content; remove the other content."), why_not);
+}
+
+static
+bool check_audio (shared_ptr<const Content> c)
+{
+       return static_cast<bool>(c->audio);
 }
 
 bool
@@ -558,11 +585,16 @@ DCPContent::can_reference_audio (string& why_not) const
         }
 
        /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
-        return can_reference (bind (&Content::audio, _1), _("it overlaps other audio content; remove the other content."), why_not);
+       return can_reference (bind (&check_audio, _1), _("it overlaps other audio content; remove the other content."), why_not);
 }
 
+static
+bool check_caption (shared_ptr<const Content> c)
+{
+       return !c->caption.empty();
+}
 bool
-DCPContent::can_reference_subtitle (string& why_not) const
+DCPContent::can_reference_caption (CaptionType type, string& why_not) const
 {
        shared_ptr<DCPDecoder> decoder;
        try {
@@ -576,15 +608,20 @@ DCPContent::can_reference_subtitle (string& why_not) const
        }
 
         BOOST_FOREACH (shared_ptr<dcp::Reel> i, decoder->reels()) {
-                if (!i->main_subtitle()) {
+                if (type == CAPTION_OPEN && !i->main_subtitle()) {
                        /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
                         why_not = _("it does not have subtitles in all its reels.");
                         return false;
                 }
+               if (type == CAPTION_CLOSED && !i->closed_caption()) {
+                       /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
+                        why_not = _("it does not have closed captions in all its reels.");
+                        return false;
+               }
         }
 
        /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
-       return can_reference (bind (&Content::caption, _1), _("it overlaps other caption content; remove the other content."), why_not);
+       return can_reference (bind (&check_caption, _1), _("it overlaps other caption content; remove the other content."), why_not);
 }
 
 void
@@ -597,7 +634,9 @@ DCPContent::take_settings_from (shared_ptr<const Content> c)
 
        _reference_video = dc->_reference_video;
        _reference_audio = dc->_reference_audio;
-       _reference_subtitle = dc->_reference_subtitle;
+       for (int i = 0; i < CAPTION_COUNT; ++i) {
+               _reference_caption[i] = dc->_reference_caption[i];
+       }
 }
 
 void
index 64642623fe7b6dc8cfaca3278ada168802d836ba..f01790b8915b29a40ab10b3e9fb30272f2467939 100644 (file)
@@ -36,9 +36,9 @@ public:
        static int const NEEDS_ASSETS;
        static int const REFERENCE_VIDEO;
        static int const REFERENCE_AUDIO;
-       static int const REFERENCE_SUBTITLE;
+       static int const REFERENCE_CAPTION;
        static int const NAME;
-       static int const HAS_SUBTITLES;
+       static int const CAPTIONS;
 };
 
 class ContentPart;
@@ -108,14 +108,17 @@ public:
 
        bool can_reference_audio (std::string &) const;
 
-       void set_reference_subtitle (bool r);
+       void set_reference_caption (CaptionType type, bool r);
 
-       bool reference_subtitle () const {
+       /** @param type Original type of captions in the DCP.
+        *  @return true if these captions are to be referenced.
+        */
+       bool reference_caption (CaptionType type) const {
                boost::mutex::scoped_lock lm (_mutex);
-               return _reference_subtitle;
+               return _reference_caption[type];
        }
 
-       bool can_reference_subtitle (std::string &) const;
+       bool can_reference_caption (CaptionType type, std::string &) const;
 
        void set_cpl (std::string id);
 
@@ -142,7 +145,7 @@ private:
        void read_directory (boost::filesystem::path);
        std::list<DCPTimePeriod> reels () const;
        bool can_reference (
-               boost::function <boost::shared_ptr<ContentPart> (boost::shared_ptr<const Content>)>,
+               boost::function <bool (boost::shared_ptr<const Content>)>,
                std::string overlapping,
                std::string& why_not
                ) const;
@@ -163,10 +166,11 @@ private:
         *  rather than by rewrapping.
         */
        bool _reference_audio;
-       /** true if the subtitle in this DCP should be included in the output by reference
-        *  rather than by rewrapping.
+       /** true if the captions in this DCP should be included in the output by reference
+        *  rather than by rewrapping.  The types here are the original caption types,
+        *  not what they are being used for.
         */
-       bool _reference_subtitle;
+       bool _reference_caption[CAPTION_COUNT];
 
        boost::optional<dcp::Standard> _standard;
        bool _three_d;
index cc415629b2c3654a698ed470708d07e6f6cf5839..ab655ebf83b378e7b1855f250787b558e498b7f8 100644 (file)
@@ -37,6 +37,7 @@
 #include <dcp/reel_picture_asset.h>
 #include <dcp/reel_sound_asset.h>
 #include <dcp/reel_subtitle_asset.h>
+#include <dcp/reel_closed_caption_asset.h>
 #include <dcp/mono_picture_frame.h>
 #include <dcp/stereo_picture_frame.h>
 #include <dcp/sound_frame.h>
@@ -62,9 +63,9 @@ DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c, shared_ptr<Log> log, boo
        if (c->audio) {
                audio.reset (new AudioDecoder (this, c->audio, log, fast));
        }
-       if (c->caption) {
+       BOOST_FOREACH (shared_ptr<CaptionContent> i, c->caption) {
                /* XXX: this time here should be the time of the first subtitle, not 0 */
-               caption.reset (new CaptionDecoder (this, c->caption, log, ContentTime()));
+               caption.push_back (shared_ptr<CaptionDecoder> (new CaptionDecoder (this, i, log, ContentTime())));
        }
 
        list<shared_ptr<dcp::CPL> > cpl_list = cpls ();
@@ -109,10 +110,10 @@ DCPDecoder::pass ()
        /* Frame within the (played part of the) reel that is coming up next */
        int64_t const frame = _next.frames_round (vfr);
 
-       /* We must emit subtitles first as when we emit the video for this frame
-          it will expect already to have the subs.
+       /* We must emit captions first as when we emit the video for this frame
+          it will expect already to have the captions.
        */
-       pass_subtitles (_next);
+       pass_captions (_next);
 
        if ((_mono_reader || _stereo_reader) && (_decode_referenced || !_dcp_content->reference_video())) {
                shared_ptr<dcp::PictureAsset> asset = (*_reel)->main_picture()->asset ();
@@ -190,15 +191,32 @@ DCPDecoder::pass ()
 }
 
 void
-DCPDecoder::pass_subtitles (ContentTime next)
+DCPDecoder::pass_captions (ContentTime next)
+{
+       list<shared_ptr<CaptionDecoder> >::const_iterator decoder = caption.begin ();
+       if ((*_reel)->main_subtitle()) {
+               pass_captions (
+                       next, (*_reel)->main_subtitle()->asset(), _dcp_content->reference_caption(CAPTION_OPEN), (*_reel)->main_subtitle()->entry_point(), *decoder
+                       );
+               ++decoder;
+       }
+       if ((*_reel)->closed_caption()) {
+               pass_captions (
+                       next, (*_reel)->closed_caption()->asset(), _dcp_content->reference_caption(CAPTION_CLOSED), (*_reel)->closed_caption()->entry_point(), *decoder
+                       );
+               ++decoder;
+       }
+}
+
+void
+DCPDecoder::pass_captions (ContentTime next, shared_ptr<dcp::SubtitleAsset> asset, bool reference, int64_t entry_point, shared_ptr<CaptionDecoder> decoder)
 {
        double const vfr = _dcp_content->active_video_frame_rate ();
        /* Frame within the (played part of the) reel that is coming up next */
        int64_t const frame = next.frames_round (vfr);
 
-       if ((*_reel)->main_subtitle() && (_decode_referenced || !_dcp_content->reference_subtitle())) {
-               int64_t const entry_point = (*_reel)->main_subtitle()->entry_point ();
-               list<shared_ptr<dcp::Subtitle> > subs = (*_reel)->main_subtitle()->asset()->subtitles_during (
+       if (_decode_referenced || !reference) {
+               list<shared_ptr<dcp::Subtitle> > subs = asset->subtitles_during (
                        dcp::Time (entry_point + frame, vfr, vfr),
                        dcp::Time (entry_point + frame + 1, vfr, vfr),
                        true
@@ -209,7 +227,7 @@ DCPDecoder::pass_subtitles (ContentTime next)
                        if (is) {
                                list<dcp::SubtitleString> s;
                                s.push_back (*is);
-                               caption->emit_plain (
+                               decoder->emit_plain (
                                        ContentTimePeriod (
                                                ContentTime::from_frames (_offset - entry_point, vfr) + ContentTime::from_seconds (i->in().as_seconds ()),
                                                ContentTime::from_frames (_offset - entry_point, vfr) + ContentTime::from_seconds (i->out().as_seconds ())
@@ -296,11 +314,11 @@ DCPDecoder::seek (ContentTime t, bool accurate)
                next_reel ();
        }
 
-       /* Pass subtitles in the pre-roll */
+       /* Pass captions in the pre-roll */
 
        double const vfr = _dcp_content->active_video_frame_rate ();
        for (int i = 0; i < pre_roll_seconds * vfr; ++i) {
-               pass_subtitles (pre);
+               pass_captions (pre);
                pre += ContentTime::from_frames (1, vfr);
        }
 
index 483c057acf76121a849e2506ec2279a49da77142..898b84e5d570de2c79ea0bf6bc4813f5eb699dd1 100644 (file)
@@ -27,6 +27,7 @@
 #include <dcp/mono_picture_asset_reader.h>
 #include <dcp/stereo_picture_asset_reader.h>
 #include <dcp/sound_asset_reader.h>
+#include <dcp/subtitle_asset.h>
 
 namespace dcp {
        class Reel;
@@ -56,7 +57,8 @@ private:
 
        void next_reel ();
        void get_readers ();
-       void pass_subtitles (ContentTime next);
+       void pass_captions (ContentTime next);
+       void pass_captions (ContentTime next, boost::shared_ptr<dcp::SubtitleAsset> asset, bool reference, int64_t entry_point, boost::shared_ptr<CaptionDecoder> decoder);
 
        /** Time of next thing to return from pass relative to the start of _reel */
        ContentTime _next;
index 541c23b6cb4576c86009014014b5b1539df59bfe..f518aefefd4ca1951f8043445331d57aa55f8875 100644 (file)
@@ -64,8 +64,10 @@ DCPEncoder::DCPEncoder (shared_ptr<const Film> film, weak_ptr<Job> job)
        _player_caption_connection = _player->Caption.connect (bind (&DCPEncoder::caption, this, _1, _2, _3));
 
        BOOST_FOREACH (shared_ptr<const Content> c, film->content ()) {
-               if (c->caption && c->caption->use() && !c->caption->burn()) {
-                       _non_burnt_subtitles = true;
+               BOOST_FOREACH (shared_ptr<CaptionContent> i, c->caption) {
+                       if (i->use() && !i->burn()) {
+                               _non_burnt_subtitles = true;
+                       }
                }
        }
 }
index c097877a33816f02760491796598a1088793f1e1..8ce4aee001df26f64ca57cb2cb5d2d76c3e17172 100644 (file)
@@ -39,6 +39,7 @@
 #include <dcp/sound_asset_reader.h>
 #include <dcp/subtitle_asset.h>
 #include <dcp/reel_subtitle_asset.h>
+#include <dcp/reel_closed_caption_asset.h>
 #include <dcp/sound_asset.h>
 #include <boost/foreach.hpp>
 #include <iostream>
@@ -57,7 +58,7 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content)
        , _audio_length (0)
        , _has_video (false)
        , _has_audio (false)
-       , _has_subtitles (false)
+       , _captions (0)
        , _encrypted (false)
        , _needs_assets (false)
        , _kdm_valid (false)
@@ -165,7 +166,17 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content)
                                return;
                        }
 
-                       _has_subtitles = true;
+                       ++_captions;
+               }
+
+               if (i->closed_caption ()) {
+                       if (!i->closed_caption()->asset_ref().resolved()) {
+                               /* We are missing this asset so we can't continue; examination will be repeated later */
+                               _needs_assets = true;
+                               return;
+                       }
+
+                       ++_captions;
                }
 
                if (i->main_picture()) {
@@ -174,6 +185,8 @@ DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content)
                        _reel_lengths.push_back (i->main_sound()->duration());
                } else if (i->main_subtitle()) {
                        _reel_lengths.push_back (i->main_subtitle()->duration());
+               } else if (i->closed_caption()) {
+                       _reel_lengths.push_back (i->closed_caption()->duration());
                }
        }
 
index 0e0b3ef8347c4ae6c389f81ed708507e8c9179cd..0d55d4643e69f80060e7305f34208b47089300ba 100644 (file)
@@ -59,8 +59,8 @@ public:
                return _name;
        }
 
-       bool has_subtitles () const {
-               return _has_subtitles;
+       int captions () const {
+               return _captions;
        }
 
        bool encrypted () const {
@@ -119,7 +119,7 @@ private:
        bool _has_video;
        /** true if this DCP has audio content (but false if it has unresolved references to audio content) */
        bool _has_audio;
-       bool _has_subtitles;
+       int _captions;
        bool _encrypted;
        bool _needs_assets;
        bool _kdm_valid;
index 17477cfb876569ec7e5a8f72166151da47d98181..78e81328a22e130edf05f78c097c210e2a9c2433 100644 (file)
@@ -40,7 +40,7 @@ using dcp::raw_convert;
 DCPSubtitleContent::DCPSubtitleContent (shared_ptr<const Film> film, boost::filesystem::path path)
        : Content (film, path)
 {
-       caption.reset (new CaptionContent (this));
+       caption.push_back (shared_ptr<CaptionContent> (new CaptionContent (this)));
 }
 
 DCPSubtitleContent::DCPSubtitleContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version)
@@ -66,18 +66,18 @@ DCPSubtitleContent::examine (shared_ptr<Job> job)
        boost::mutex::scoped_lock lm (_mutex);
 
        /* Default to turning these subtitles on */
-       caption->set_use (true);
+       only_caption()->set_use (true);
 
        if (iop) {
-               caption->set_language (iop->language ());
+               only_caption()->set_language (iop->language ());
        } else if (smpte) {
-               caption->set_language (smpte->language().get_value_or (""));
+               only_caption()->set_language (smpte->language().get_value_or (""));
        }
 
        _length = ContentTime::from_seconds (sc->latest_subtitle_out().as_seconds ());
 
        BOOST_FOREACH (shared_ptr<dcp::LoadFontNode> i, sc->load_font_nodes ()) {
-               caption->add_font (shared_ptr<Font> (new Font (i->id)));
+               only_caption()->add_font (shared_ptr<Font> (new Font (i->id)));
        }
 }
 
@@ -106,8 +106,8 @@ DCPSubtitleContent::as_xml (xmlpp::Node* node, bool with_paths) const
        node->add_child("Type")->add_child_text ("DCPSubtitle");
        Content::as_xml (node, with_paths);
 
-       if (caption) {
-               caption->as_xml (node);
+       if (only_caption()) {
+               only_caption()->as_xml (node);
        }
 
        node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ()));
index c7a9a863f038b5993688bd5a0b7ccb0695c88dbe..3ed4a6827f98cf41c025bade2c2b54a4712a0710 100644 (file)
@@ -39,7 +39,7 @@ DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr<const DCPSubtitleContent> con
        if (_next != _subtitles.end()) {
                first = content_time_period(*_next).from;
        }
-       caption.reset (new CaptionDecoder (this, content->caption, log, first));
+       caption.push_back (shared_ptr<CaptionDecoder> (new CaptionDecoder (this, content->only_caption(), log, first)));
 }
 
 void
@@ -81,7 +81,7 @@ DCPSubtitleDecoder::pass ()
                /* XXX: image subtitles */
        }
 
-       caption->emit_plain (p, s);
+       only_caption()->emit_plain (p, s);
        return false;
 }
 
index 70eb5b61a3f34b4fad30cc22f82683f448789751..2fddddc914dee09bee221af4c5b1fdc985d61c9b 100644 (file)
@@ -27,6 +27,7 @@
 
 using std::cout;
 using boost::optional;
+using boost::shared_ptr;
 
 /** @return Earliest time of content that the next pass() will emit */
 ContentTime
@@ -42,8 +43,10 @@ Decoder::position () const
                pos = audio->position();
        }
 
-       if (caption && !caption->ignore() && (!pos || caption->position() < *pos)) {
-               pos = caption->position();
+       BOOST_FOREACH (shared_ptr<CaptionDecoder> i, caption) {
+               if (!i->ignore() && (!pos || i->position() < *pos)) {
+                       pos = i->position();
+               }
        }
 
        return pos.get_value_or(ContentTime());
@@ -58,7 +61,17 @@ Decoder::seek (ContentTime, bool)
        if (audio) {
                audio->seek ();
        }
-       if (caption) {
-               caption->seek ();
+       BOOST_FOREACH (shared_ptr<CaptionDecoder> i, caption) {
+               i->seek ();
        }
 }
+
+shared_ptr<CaptionDecoder>
+Decoder::only_caption () const
+{
+       DCPOMATIC_ASSERT (caption.size() < 2);
+       if (caption.empty ()) {
+               return shared_ptr<CaptionDecoder> ();
+       }
+       return caption.front ();
+}
index c3b330cfbcd49bdf376cba575ca52c7e5ab2d258..d48df7517814aeb14de7b499f510615dd186d0dc 100644 (file)
@@ -45,7 +45,9 @@ public:
 
        boost::shared_ptr<VideoDecoder> video;
        boost::shared_ptr<AudioDecoder> audio;
-       boost::shared_ptr<CaptionDecoder> caption;
+       std::list<boost::shared_ptr<CaptionDecoder> > caption;
+
+       boost::shared_ptr<CaptionDecoder> only_caption () const;
 
        /** Do some decoding and perhaps emit video, audio or subtitle data.
         *  @return true if this decoder will emit no more data unless a seek() happens.
index 7a821a04e812c1850313006829a6ffd6e3ad2205..ddf4548b41959fe1c8eac9ea304a9d85da77c201 100644 (file)
@@ -148,8 +148,8 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const
                }
        }
 
-       if (caption) {
-               caption->as_xml (node);
+       if (only_caption()) {
+               only_caption()->as_xml (node);
        }
 
        boost::mutex::scoped_lock lm (_mutex);
@@ -242,7 +242,8 @@ FFmpegContent::examine (shared_ptr<Job> job)
 
                _subtitle_streams = examiner->subtitle_streams ();
                if (!_subtitle_streams.empty ()) {
-                       caption.reset (new CaptionContent (this));
+                       caption.clear ();
+                       caption.push_back (shared_ptr<CaptionContent> (new CaptionContent (this)));
                        _subtitle_stream = _subtitle_streams.front ();
                }
 
@@ -365,8 +366,8 @@ FFmpegContent::identifier () const
                s += "_" + video->identifier();
        }
 
-       if (caption && caption->use() && caption->burn()) {
-               s += "_" + caption->identifier();
+       if (only_caption() && only_caption()->use() && only_caption()->burn()) {
+               s += "_" + only_caption()->identifier();
        }
 
        boost::mutex::scoped_lock lm (_mutex);
index 9478d8816c455ef47104b003c0638b805ad39e45..1f2fcfef86ead651a87c4217055fb0378ea00c64 100644 (file)
@@ -97,9 +97,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
                audio.reset (new AudioDecoder (this, c->audio, log, fast));
        }
 
-       if (c->caption) {
+       if (c->only_caption()) {
                /* XXX: this time here should be the time of the first subtitle, not 0 */
-               caption.reset (new CaptionDecoder (this, c->caption, log, ContentTime()));
+               caption.push_back (shared_ptr<CaptionDecoder> (new CaptionDecoder (this, c->only_caption(), log, ContentTime())));
        }
 
        _next_time.resize (_format_context->nb_streams);
@@ -184,7 +184,7 @@ FFmpegDecoder::pass ()
 
        if (_video_stream && si == _video_stream.get() && !video->ignore()) {
                decode_video_packet ();
-       } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index(_format_context, si) && !caption->ignore()) {
+       } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index(_format_context, si) && !only_caption()->ignore()) {
                decode_subtitle_packet ();
        } else {
                decode_audio_packet ();
@@ -549,9 +549,9 @@ FFmpegDecoder::decode_subtitle_packet ()
        /* Stop any current subtitle, either at the time it was supposed to stop, or now if now is sooner */
        if (_have_current_subtitle) {
                if (_current_subtitle_to) {
-                       caption->emit_stop (min(*_current_subtitle_to, subtitle_period(sub).from + _pts_offset));
+                       only_caption()->emit_stop (min(*_current_subtitle_to, subtitle_period(sub).from + _pts_offset));
                } else {
-                       caption->emit_stop (subtitle_period(sub).from + _pts_offset);
+                       only_caption()->emit_stop (subtitle_period(sub).from + _pts_offset);
                }
                _have_current_subtitle = false;
        }
@@ -593,7 +593,7 @@ FFmpegDecoder::decode_subtitle_packet ()
        }
 
        if (_current_subtitle_to) {
-               caption->emit_stop (*_current_subtitle_to);
+               only_caption()->emit_stop (*_current_subtitle_to);
        }
 
        avsubtitle_free (&sub);
@@ -669,7 +669,7 @@ FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime
                static_cast<double> (rect->h) / target_height
                );
 
-       caption->emit_bitmap_start (from, image, scaled_rect);
+       only_caption()->emit_bitmap_start (from, image, scaled_rect);
 }
 
 void
@@ -702,6 +702,6 @@ FFmpegDecoder::decode_ass_subtitle (string ass, ContentTime from)
                );
 
        BOOST_FOREACH (sub::Subtitle const & i, sub::collect<list<sub::Subtitle> > (raw)) {
-               caption->emit_plain_start (from, i);
+               only_caption()->emit_plain_start (from, i);
        }
 }
index 08dd8bdc2eb34d0f23ca170681e0efa6714ba7f5..fe7fcbfaee4375f8d3bacb0bb999fc90031d6c74 100644 (file)
@@ -697,14 +697,19 @@ Film::isdcf_name (bool if_created_now) const
                d += "_" + dm.audio_language;
                if (!dm.subtitle_language.empty()) {
 
-                       bool burnt_in = true;
-                       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
-                               if (!i->caption) {
-                                       continue;
-                               }
+                       /* I'm not clear on the precise details of the convention for CCAP labelling;
+                          for now I'm just appending -CCAP if we have any closed captions.
+                       */
 
-                               if (i->caption->use() && !i->caption->burn()) {
-                                       burnt_in = false;
+                       bool burnt_in = true;
+                       bool ccap = false;
+                       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+                               BOOST_FOREACH (shared_ptr<CaptionContent> j, i->caption) {
+                                       if (j->type() == CAPTION_OPEN && j->use() && !j->burn()) {
+                                               burnt_in = false;
+                                       } else if (j->type() == CAPTION_CLOSED) {
+                                               ccap = true;
+                                       }
                                }
                        }
 
@@ -716,6 +721,9 @@ Film::isdcf_name (bool if_created_now) const
                        }
 
                        d += "-" + language;
+                       if (ccap) {
+                               d += "-CCAP";
+                       }
                } else {
                        d += "-XX";
                }
@@ -770,7 +778,13 @@ Film::isdcf_name (bool if_created_now) const
        bool vf = false;
        BOOST_FOREACH (shared_ptr<Content> i, content ()) {
                shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
-               if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) {
+               bool any_caption = false;
+               for (int i = 0; i < CAPTION_COUNT; ++i) {
+                       if (dc->reference_caption(static_cast<CaptionType>(i))) {
+                               any_caption = true;
+                       }
+               }
+               if (dc && (dc->reference_video() || dc->reference_audio() || any_caption)) {
                        vf = true;
                }
        }
@@ -1083,9 +1097,9 @@ Film::add_content (shared_ptr<Content> c)
 {
        /* Add {video,subtitle} content after any existing {video,subtitle} content */
        if (c->video) {
-               c->set_position (_playlist->video_end ());
-       } else if (c->caption) {
-               c->set_position (_playlist->subtitle_end ());
+               c->set_position (_playlist->video_end());
+       } else if (!c->caption.empty()) {
+               c->set_position (_playlist->caption_end());
        }
 
        if (_template_film) {
@@ -1372,10 +1386,9 @@ Film::subtitle_language () const
 {
        set<string> languages;
 
-       ContentList cl = content ();
-       BOOST_FOREACH (shared_ptr<Content>& c, cl) {
-               if (c->caption) {
-                       languages.insert (c->caption->language ());
+       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+               BOOST_FOREACH (shared_ptr<CaptionContent> j, i->caption) {
+                       languages.insert (j->language ());
                }
        }
 
index eb3ea1d02c542752e277aff62f3258d4c6e2a475..33c2faba5938a371a10888d290621a3408bdca0a 100644 (file)
@@ -56,10 +56,10 @@ get_hints (shared_ptr<const Film> film)
        bool big_font_files = false;
        if (film->interop ()) {
                BOOST_FOREACH (shared_ptr<Content> i, content) {
-                       if (i->caption) {
-                               BOOST_FOREACH (shared_ptr<Font> j, i->caption->fonts ()) {
-                                       for (int k = 0; k < FontFiles::VARIANTS; ++k) {
-                                               optional<boost::filesystem::path> const p = j->file (static_cast<FontFiles::Variant> (k));
+                       BOOST_FOREACH (shared_ptr<CaptionContent> j, i->caption) {
+                               BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
+                                       for (int l = 0; l < FontFiles::VARIANTS; ++l) {
+                                               optional<boost::filesystem::path> const p = k->file (static_cast<FontFiles::Variant>(l));
                                                if (p && boost::filesystem::file_size (p.get()) >= (640 * 1024)) {
                                                        big_font_files = true;
                                                }
index ccef4cef8ba0cd074b152dd371e7ee40d5ea4466..54077d96b0c5777bf63fafd7d4ece2e3c7275749 100644 (file)
@@ -26,7 +26,7 @@
 using boost::shared_ptr;
 using boost::function;
 
-ContentList overlaps (ContentList cl, function<shared_ptr<ContentPart> (shared_ptr<const Content>)> part, DCPTime from, DCPTime to)
+ContentList overlaps (ContentList cl, function<bool (shared_ptr<const Content>)> part, DCPTime from, DCPTime to)
 {
        ContentList overlaps;
        DCPTimePeriod period (from, to);
index e5b3fc38e0b67e0813809afa5e3b8ce141575cc6..7dd9802c3827ab66bb85fa23bab8d6d95e54b5ba 100644 (file)
@@ -28,5 +28,5 @@ class ContentPart;
  *  ContentList
  */
 ContentList overlaps (
-       ContentList cl, boost::function<boost::shared_ptr<ContentPart> (boost::shared_ptr<const Content>)> part, DCPTime from, DCPTime to
+       ContentList cl, boost::function<bool (boost::shared_ptr<const Content>)> part, DCPTime from, DCPTime to
        );
index 78928af26bc0954288952014c902e573c711fb74..580c3e6d43946230e85ac3001f42ce7bb9f24555 100644 (file)
@@ -52,6 +52,7 @@
 #include <dcp/reel_sound_asset.h>
 #include <dcp/reel_subtitle_asset.h>
 #include <dcp/reel_picture_asset.h>
+#include <dcp/reel_closed_caption_asset.h>
 #include <boost/foreach.hpp>
 #include <stdint.h>
 #include <algorithm>
@@ -88,7 +89,7 @@ Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist
        , _playlist (playlist)
        , _have_valid_pieces (false)
        , _ignore_video (false)
-       , _ignore_subtitle (false)
+       , _ignore_caption (false)
        , _fast (false)
        , _play_referenced (false)
        , _audio_merger (_film->audio_frame_rate())
@@ -136,8 +137,10 @@ Player::setup_pieces ()
                        decoder->video->set_ignore (true);
                }
 
-               if (decoder->caption && _ignore_subtitle) {
-                       decoder->caption->set_ignore (true);
+               if (_ignore_caption) {
+                       BOOST_FOREACH (shared_ptr<CaptionDecoder> i, decoder->caption) {
+                               i->set_ignore (true);
+                       }
                }
 
                shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
@@ -164,16 +167,20 @@ Player::setup_pieces ()
                        decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr<Piece> (piece), _1, _2));
                }
 
-               if (decoder->caption) {
-                       decoder->caption->BitmapStart.connect (
-                               bind(&Player::bitmap_text_start, this, weak_ptr<Piece>(piece), weak_ptr<CaptionContent>(piece->content->caption), _1)
+               list<shared_ptr<CaptionDecoder> >::const_iterator j = decoder->caption.begin();
+
+               while (j != decoder->caption.end()) {
+                       (*j)->BitmapStart.connect (
+                               bind(&Player::bitmap_text_start, this, weak_ptr<Piece>(piece), weak_ptr<const CaptionContent>((*j)->content()), _1)
                                );
-                       decoder->caption->PlainStart.connect (
-                               bind(&Player::plain_text_start, this, weak_ptr<Piece>(piece), weak_ptr<CaptionContent>(piece->content->caption), _1)
+                       (*j)->PlainStart.connect (
+                               bind(&Player::plain_text_start, this, weak_ptr<Piece>(piece), weak_ptr<const CaptionContent>((*j)->content()), _1)
                                );
-                       decoder->caption->Stop.connect (
-                               bind(&Player::subtitle_stop, this, weak_ptr<Piece>(piece), weak_ptr<CaptionContent>(piece->content->caption), _1, _2)
+                       (*j)->Stop.connect (
+                               bind(&Player::subtitle_stop, this, weak_ptr<Piece>(piece), weak_ptr<const CaptionContent>((*j)->content()), _1, _2)
                                );
+
+                       ++j;
                }
        }
 
@@ -411,12 +418,12 @@ Player::get_subtitle_fonts ()
        }
 
        list<shared_ptr<Font> > fonts;
-       BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
-               if (p->content->caption) {
+       BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
+               BOOST_FOREACH (shared_ptr<CaptionContent> j, i->content->caption) {
                        /* XXX: things may go wrong if there are duplicate font IDs
                           with different font files.
                        */
-                       list<shared_ptr<Font> > f = p->content->caption->fonts ();
+                       list<shared_ptr<Font> > f = j->fonts ();
                        copy (f.begin(), f.end(), back_inserter (fonts));
                }
        }
@@ -432,9 +439,9 @@ Player::set_ignore_video ()
 }
 
 void
-Player::set_ignore_subtitle ()
+Player::set_ignore_caption ()
 {
-       _ignore_subtitle = true;
+       _ignore_caption = true;
 }
 
 /** Set a type of caption that this player should always burn into the image,
@@ -510,7 +517,7 @@ Player::get_reel_assets ()
                                        );
                        }
 
-                       if (j->reference_subtitle ()) {
+                       if (j->reference_caption (CAPTION_OPEN)) {
                                shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
                                DCPOMATIC_ASSERT (ra);
                                ra->set_entry_point (ra->entry_point() + trim_start);
@@ -520,6 +527,16 @@ Player::get_reel_assets ()
                                        );
                        }
 
+                       if (j->reference_caption (CAPTION_CLOSED)) {
+                               shared_ptr<dcp::ReelAsset> ra = k->closed_caption ();
+                               DCPOMATIC_ASSERT (ra);
+                               ra->set_entry_point (ra->entry_point() + trim_start);
+                               ra->set_duration (ra->duration() - trim_start - trim_end);
+                               a.push_back (
+                                       ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
+                                       );
+                       }
+
                        /* Assume that main picture duration is the length of the reel */
                        offset += k->main_picture()->duration ();
                }
@@ -556,10 +573,10 @@ Player::pass ()
                        i->done = true;
                } else {
 
-                       /* Given two choices at the same time, pick the one with a subtitle so we see it before
+                       /* Given two choices at the same time, pick the one with captions so we see it before
                           the video.
                        */
-                       if (!earliest_time || t < *earliest_time || (t == *earliest_time && i->decoder->caption)) {
+                       if (!earliest_time || t < *earliest_time || (t == *earliest_time && !i->decoder->caption.empty())) {
                                earliest_time = t;
                                earliest_content = i;
                        }
@@ -851,10 +868,10 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
 }
 
 void
-Player::bitmap_text_start (weak_ptr<Piece> wp, weak_ptr<CaptionContent> wc, ContentBitmapCaption subtitle)
+Player::bitmap_text_start (weak_ptr<Piece> wp, weak_ptr<const CaptionContent> wc, ContentBitmapCaption subtitle)
 {
        shared_ptr<Piece> piece = wp.lock ();
-       shared_ptr<CaptionContent> caption = wc.lock ();
+       shared_ptr<const CaptionContent> caption = wc.lock ();
        if (!piece || !caption) {
                return;
        }
@@ -879,10 +896,10 @@ Player::bitmap_text_start (weak_ptr<Piece> wp, weak_ptr<CaptionContent> wc, Cont
 }
 
 void
-Player::plain_text_start (weak_ptr<Piece> wp, weak_ptr<CaptionContent> wc, ContentTextCaption subtitle)
+Player::plain_text_start (weak_ptr<Piece> wp, weak_ptr<const CaptionContent> wc, ContentTextCaption subtitle)
 {
        shared_ptr<Piece> piece = wp.lock ();
-       shared_ptr<CaptionContent> caption = wc.lock ();
+       shared_ptr<const CaptionContent> caption = wc.lock ();
        if (!piece || !caption) {
                return;
        }
@@ -923,14 +940,14 @@ Player::plain_text_start (weak_ptr<Piece> wp, weak_ptr<CaptionContent> wc, Conte
 }
 
 void
-Player::subtitle_stop (weak_ptr<Piece> wp, weak_ptr<CaptionContent> wc, ContentTime to, CaptionType type)
+Player::subtitle_stop (weak_ptr<Piece> wp, weak_ptr<const CaptionContent> wc, ContentTime to, CaptionType type)
 {
        if (!_active_captions[type].have (wc)) {
                return;
        }
 
        shared_ptr<Piece> piece = wp.lock ();
-       shared_ptr<CaptionContent> caption = wc.lock ();
+       shared_ptr<const CaptionContent> caption = wc.lock ();
        if (!piece || !caption) {
                return;
        }
index d0f1eec6d4d31b0bb5de76498a8ed875d58aef20..eda2d7eb0c68753d9300e101b9a17f76adcc0a16 100644 (file)
@@ -78,7 +78,7 @@ public:
 
        void set_video_container_size (dcp::Size);
        void set_ignore_video ();
-       void set_ignore_subtitle ();
+       void set_ignore_caption ();
        void set_always_burn_captions (CaptionType type);
        void set_fast ();
        void set_play_referenced ();
@@ -126,9 +126,9 @@ private:
        boost::shared_ptr<PlayerVideo> black_player_video_frame (Eyes eyes) const;
        void video (boost::weak_ptr<Piece>, ContentVideo);
        void audio (boost::weak_ptr<Piece>, AudioStreamPtr, ContentAudio);
-       void bitmap_text_start (boost::weak_ptr<Piece>, boost::weak_ptr<CaptionContent>, ContentBitmapCaption);
-       void plain_text_start (boost::weak_ptr<Piece>, boost::weak_ptr<CaptionContent>, ContentTextCaption);
-       void subtitle_stop (boost::weak_ptr<Piece>, boost::weak_ptr<CaptionContent>, ContentTime, CaptionType);
+       void bitmap_text_start (boost::weak_ptr<Piece>, boost::weak_ptr<const CaptionContent>, ContentBitmapCaption);
+       void plain_text_start (boost::weak_ptr<Piece>, boost::weak_ptr<const CaptionContent>, ContentTextCaption);
+       void subtitle_stop (boost::weak_ptr<Piece>, boost::weak_ptr<const CaptionContent>, ContentTime, CaptionType);
        DCPTime one_video_frame () const;
        void fill_audio (DCPTimePeriod period);
        std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> discard_audio (
@@ -152,8 +152,8 @@ private:
 
        /** true if the player should ignore all video; i.e. never produce any */
        bool _ignore_video;
-       /** true if the player should ignore all audio; i.e. never produce any */
-       bool _ignore_subtitle;
+       /** true if the player should ignore all captions; i.e. never produce any */
+       bool _ignore_caption;
        /** Type of captions that the player should always burn into the video regardless
            of content settings.
        */
index a5451bafa418e21fedacb48eb319f8dfb8d33bcc..6c7fd7f4e3bf96ca34489b04a453535da3549961 100644 (file)
@@ -131,11 +131,11 @@ Playlist::maybe_sequence ()
                placed.push_back (i);
        }
 
-       /* Subtitles */
+       /* Captions */
 
        DCPTime next;
        BOOST_FOREACH (shared_ptr<Content> i, _content) {
-               if (!i->caption || find (placed.begin(), placed.end(), i) != placed.end()) {
+               if (i->caption.empty() || find (placed.begin(), placed.end(), i) != placed.end()) {
                        continue;
                }
 
@@ -155,7 +155,13 @@ Playlist::video_identifier () const
        string t;
 
        BOOST_FOREACH (shared_ptr<const Content> i, _content) {
-               if (i->video || (i->caption && i->caption->burn())) {
+               bool burn = false;
+               BOOST_FOREACH (shared_ptr<CaptionContent> j, i->caption) {
+                       if (j->burn()) {
+                               burn = true;
+                       }
+               }
+               if (i->video || burn) {
                        t += i->identifier ();
                }
        }
@@ -362,11 +368,11 @@ Playlist::video_end () const
 }
 
 DCPTime
-Playlist::subtitle_end () const
+Playlist::caption_end () const
 {
        DCPTime end;
        BOOST_FOREACH (shared_ptr<Content> i, _content) {
-               if (i->caption) {
+               if (!i->caption.empty ()) {
                        end = max (end, i->end ());
                }
        }
index fe19c93c97dc6848e05362db4c34dfdd7659f20a..073e53de71c44dd42d3f0ddfb939071588b1a006 100644 (file)
@@ -64,7 +64,7 @@ public:
 
        int best_video_frame_rate () const;
        DCPTime video_end () const;
-       DCPTime subtitle_end () const;
+       DCPTime caption_end () const;
        FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
        std::string content_summary (DCPTimePeriod period) const;
        std::pair<double, double> speed_up_range (int dcp_video_frame_rate) const;
index cb7d1511ba001a7f75a1b2e7b4ea1552910cd296..c8eb2390a096e79498b5443da2ce13796f26221b 100644 (file)
@@ -38,7 +38,7 @@ using dcp::raw_convert;
 TextCaptionFileContent::TextCaptionFileContent (shared_ptr<const Film> film, boost::filesystem::path path)
        : Content (film, path)
 {
-       caption.reset (new CaptionContent (this));
+       caption.push_back (shared_ptr<CaptionContent> (new CaptionContent (this)));
 }
 
 TextCaptionFileContent::TextCaptionFileContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version)
@@ -55,11 +55,11 @@ TextCaptionFileContent::examine (boost::shared_ptr<Job> job)
        TextCaptionFile s (shared_from_this ());
 
        /* Default to turning these subtitles on */
-       caption->set_use (true);
+       only_caption()->set_use (true);
 
        boost::mutex::scoped_lock lm (_mutex);
        _length = s.length ();
-       caption->add_font (shared_ptr<Font> (new Font (TEXT_FONT_ID)));
+       only_caption()->add_font (shared_ptr<Font> (new Font (TEXT_FONT_ID)));
 }
 
 string
@@ -80,8 +80,8 @@ TextCaptionFileContent::as_xml (xmlpp::Node* node, bool with_paths) const
        node->add_child("Type")->add_child_text ("TextSubtitle");
        Content::as_xml (node, with_paths);
 
-       if (caption) {
-               caption->as_xml (node);
+       if (only_caption()) {
+               only_caption()->as_xml (node);
        }
 
        node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ()));
index 46217e49b550b2a4421afab0febc21449081e945..65de6a562a114255b5b17bb50d4ffce534da24e2 100644 (file)
@@ -43,7 +43,7 @@ TextCaptionFileDecoder::TextCaptionFileDecoder (shared_ptr<const TextCaptionFile
        if (!_subtitles.empty()) {
                first = content_time_period(_subtitles[0]).from;
        }
-       caption.reset (new CaptionDecoder (this, content->caption, log, first));
+       caption.push_back (shared_ptr<CaptionDecoder> (new CaptionDecoder (this, content->only_caption(), log, first)));
 }
 
 void
@@ -73,7 +73,7 @@ TextCaptionFileDecoder::pass ()
        }
 
        ContentTimePeriod const p = content_time_period (_subtitles[_next]);
-       caption->emit_plain (p, _subtitles[_next]);
+       only_caption()->emit_plain (p, _subtitles[_next]);
 
        ++_next;
        return false;
index 3337087ebfb71e630f30fd0d723629d857548fb8..b2bff78fa67f23a6e4c70e05ffe6315cacb02321 100644 (file)
@@ -129,6 +129,16 @@ enum ReelType
        REELTYPE_BY_LENGTH
 };
 
+/** Type of captions.
+ *  For better or worse DoM has uses two names for text that appears
+ *  with the DCP:
+ *
+ *  open captions:   text that is shown to everybody on-screen (aka subtitles).
+ *  closed captions: text that is shown to some viewers using some other method.
+ *
+ *  There is also still use of the word `subtitle' in the code; these are the
+ *  same as open captions in DoM.
+ */
 enum CaptionType
 {
        CAPTION_OPEN,
index 9262be1b5f71a5123df1311f62ab3ccacc5b0e73..416bdf6c4168afa0d733f0e7384fffae5a9cd129 100644 (file)
@@ -563,7 +563,7 @@ private:
        {
                DCPOMATIC_ASSERT (_clipboard);
 
-               PasteDialog* d = new PasteDialog (this, static_cast<bool>(_clipboard->video), static_cast<bool>(_clipboard->audio), static_cast<bool>(_clipboard->caption));
+               PasteDialog* d = new PasteDialog (this, static_cast<bool>(_clipboard->video), static_cast<bool>(_clipboard->audio), !_clipboard->caption.empty());
                if (d->ShowModal() == wxID_OK) {
                        BOOST_FOREACH (shared_ptr<Content> i, _film_editor->content_panel()->selected()) {
                                if (d->video() && i->video) {
@@ -574,9 +574,15 @@ private:
                                        DCPOMATIC_ASSERT (_clipboard->audio);
                                        i->audio->take_settings_from (_clipboard->audio);
                                }
-                               if (d->caption() && i->caption) {
-                                       DCPOMATIC_ASSERT (_clipboard->caption);
-                                       i->caption->take_settings_from (_clipboard->caption);
+
+                               if (d->caption()) {
+                                       list<shared_ptr<CaptionContent> >::iterator j = i->caption.begin ();
+                                       list<shared_ptr<CaptionContent> >::const_iterator k = _clipboard->caption.begin ();
+                                       while (j != i->caption.end() && k != _clipboard->caption.end()) {
+                                               (*j)->take_settings_from (*k);
+                                               ++j;
+                                               ++k;
+                                       }
                                }
                        }
                }
index 0af44fd05d2dd27a89800c13c057a43f6d48d1cd..d357e566b4aecd2fae832c2bb0bedfe044408663 100644 (file)
@@ -618,8 +618,11 @@ private:
 
        void setup_from_dcp (shared_ptr<DCPContent> dcp)
        {
-               if (dcp->caption) {
-                       dcp->caption->set_use (true);
+               BOOST_FOREACH (shared_ptr<CaptionContent> i, dcp->caption) {
+                       /* XXX: we should offer the option to view closed captions */
+                       if (i->type() == CAPTION_OPEN) {
+                               i->set_use (true);
+                       }
                }
 
                if (dcp->video) {
index 9377fa6ef263e4d6e7b2945e8b29906eae305008..af01dcbe8484988cc7d73c4712bb965b2c8d37cf 100644 (file)
@@ -39,9 +39,10 @@ int const CaptionAppearanceDialog::NONE = 0;
 int const CaptionAppearanceDialog::OUTLINE = 1;
 int const CaptionAppearanceDialog::SHADOW = 2;
 
-CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<Content> content)
+CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<Content> content, shared_ptr<CaptionContent> caption)
        : wxDialog (parent, wxID_ANY, _("Caption appearance"))
        , _content (content)
+       , _caption (caption)
 {
        shared_ptr<FFmpegContent> ff = dynamic_pointer_cast<FFmpegContent> (content);
        if (ff) {
@@ -126,7 +127,7 @@ CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<C
        _effect->Append (_("Outline"));
        _effect->Append (_("Shadow"));;
 
-       optional<dcp::Colour> colour = _content->caption->colour();
+       optional<dcp::Colour> colour = _caption->colour();
        _force_colour->SetValue (static_cast<bool>(colour));
        if (colour) {
                _colour->SetColour (wxColour (colour->r, colour->g, colour->b));
@@ -134,7 +135,7 @@ CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<C
                _colour->SetColour (wxColour (255, 255, 255));
        }
 
-       optional<dcp::Effect> effect = _content->caption->effect();
+       optional<dcp::Effect> effect = _caption->effect();
        _force_effect->SetValue (static_cast<bool>(effect));
        if (effect) {
                switch (*effect) {
@@ -152,7 +153,7 @@ CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<C
                _effect->SetSelection (NONE);
        }
 
-       optional<dcp::Colour> effect_colour = _content->caption->effect_colour();
+       optional<dcp::Colour> effect_colour = _caption->effect_colour();
        _force_effect_colour->SetValue (static_cast<bool>(effect_colour));
        if (effect_colour) {
                _effect_colour->SetColour (wxColour (effect_colour->r, effect_colour->g, effect_colour->b));
@@ -160,7 +161,7 @@ CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<C
                _effect_colour->SetColour (wxColour (0, 0, 0));
        }
 
-       optional<ContentTime> fade_in = _content->caption->fade_in();
+       optional<ContentTime> fade_in = _caption->fade_in();
        _force_fade_in->SetValue (static_cast<bool>(fade_in));
        if (fade_in) {
                _fade_in->set (*fade_in, _content->active_video_frame_rate());
@@ -168,7 +169,7 @@ CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<C
                _fade_in->set (ContentTime(), _content->active_video_frame_rate());
        }
 
-       optional<ContentTime> fade_out = _content->caption->fade_out();
+       optional<ContentTime> fade_out = _caption->fade_out();
        _force_fade_out->SetValue (static_cast<bool>(fade_out));
        if (fade_out) {
                _fade_out->set (*fade_out, _content->active_video_frame_rate ());
@@ -176,7 +177,7 @@ CaptionAppearanceDialog::CaptionAppearanceDialog (wxWindow* parent, shared_ptr<C
                _fade_out->set (ContentTime(), _content->active_video_frame_rate ());
        }
 
-       _outline_width->SetValue (_content->caption->outline_width ());
+       _outline_width->SetValue (_caption->outline_width ());
 
        _force_colour->Bind (wxEVT_CHECKBOX, bind (&CaptionAppearanceDialog::setup_sensitivity, this));
        _force_effect_colour->Bind (wxEVT_CHECKBOX, bind (&CaptionAppearanceDialog::setup_sensitivity, this));
@@ -206,42 +207,42 @@ CaptionAppearanceDialog::apply ()
 {
        if (_force_colour->GetValue ()) {
                wxColour const c = _colour->GetColour ();
-               _content->caption->set_colour (dcp::Colour (c.Red(), c.Green(), c.Blue()));
+               _caption->set_colour (dcp::Colour (c.Red(), c.Green(), c.Blue()));
        } else {
-               _content->caption->unset_colour ();
+               _caption->unset_colour ();
        }
        if (_force_effect->GetValue()) {
                switch (_effect->GetSelection()) {
                case NONE:
-                       _content->caption->set_effect (dcp::NONE);
+                       _caption->set_effect (dcp::NONE);
                        break;
                case OUTLINE:
-                       _content->caption->set_effect (dcp::BORDER);
+                       _caption->set_effect (dcp::BORDER);
                        break;
                case SHADOW:
-                       _content->caption->set_effect (dcp::SHADOW);
+                       _caption->set_effect (dcp::SHADOW);
                        break;
                }
        } else {
-               _content->caption->unset_effect ();
+               _caption->unset_effect ();
        }
        if (_force_effect_colour->GetValue ()) {
                wxColour const ec = _effect_colour->GetColour ();
-               _content->caption->set_effect_colour (dcp::Colour (ec.Red(), ec.Green(), ec.Blue()));
+               _caption->set_effect_colour (dcp::Colour (ec.Red(), ec.Green(), ec.Blue()));
        } else {
-               _content->caption->unset_effect_colour ();
+               _caption->unset_effect_colour ();
        }
        if (_force_fade_in->GetValue ()) {
-               _content->caption->set_fade_in (_fade_in->get (_content->active_video_frame_rate ()));
+               _caption->set_fade_in (_fade_in->get (_content->active_video_frame_rate ()));
        } else {
-               _content->caption->unset_fade_in ();
+               _caption->unset_fade_in ();
        }
        if (_force_fade_out->GetValue ()) {
-               _content->caption->set_fade_out (_fade_out->get (_content->active_video_frame_rate ()));
+               _caption->set_fade_out (_fade_out->get (_content->active_video_frame_rate ()));
        } else {
-               _content->caption->unset_fade_out ();
+               _caption->unset_fade_out ();
        }
-       _content->caption->set_outline_width (_outline_width->GetValue ());
+       _caption->set_outline_width (_outline_width->GetValue ());
 
        if (_stream) {
                for (map<RGBA, RGBAColourPicker*>::const_iterator i = _pickers.begin(); i != _pickers.end(); ++i) {
@@ -272,7 +273,7 @@ CaptionAppearanceDialog::setup_sensitivity ()
        _fade_in->Enable (_force_fade_in->GetValue ());
        _fade_out->Enable (_force_fade_out->GetValue ());
 
-       bool const can_outline_width = _effect->GetSelection() == OUTLINE && _content->caption->burn ();
+       bool const can_outline_width = _effect->GetSelection() == OUTLINE && _caption->burn ();
        _outline_width->Enable (can_outline_width);
        if (can_outline_width) {
                _outline_width->UnsetToolTip ();
index ebb70047e00c1e7a9ce027983f4ab563e2f2a9f2..3fb9930997f87edf027eb11a67d4ab3d6c68a6b8 100644 (file)
@@ -36,7 +36,7 @@ class wxWidget;
 class CaptionAppearanceDialog : public wxDialog
 {
 public:
-       CaptionAppearanceDialog (wxWindow* parent, boost::shared_ptr<Content> content);
+       CaptionAppearanceDialog (wxWindow* parent, boost::shared_ptr<Content> content, boost::shared_ptr<CaptionContent> caption);
 
        void apply ();
 
@@ -60,6 +60,7 @@ private:
        std::map<RGBA, RGBAColourPicker*> _pickers;
 
        boost::shared_ptr<Content> _content;
+       boost::shared_ptr<CaptionContent> _caption;
        boost::shared_ptr<FFmpegSubtitleStream> _stream;
 
        boost::signals2::scoped_connection _content_connection;
index ca341167dc857e3d35c924696533d94fdf65eea2..5a3ac39eda2ec3884391f3c10e63eacc76eb09fe 100644 (file)
@@ -47,6 +47,7 @@ CaptionPanel::CaptionPanel (ContentPanel* p)
        : ContentSubPanel (p, _("Captions"))
        , _caption_view (0)
        , _fonts_dialog (0)
+       , _original_type (CAPTION_OPEN)
 {
        wxBoxSizer* reference_sizer = new wxBoxSizer (wxVERTICAL);
 
@@ -163,7 +164,7 @@ CaptionPanel::CaptionPanel (ContentPanel* p)
 
        _reference->Bind                (wxEVT_CHECKBOX, boost::bind (&CaptionPanel::reference_clicked, this));
        _use->Bind                      (wxEVT_CHECKBOX, boost::bind (&CaptionPanel::use_toggled, this));
-       _type->Bind                     (wxEVT_CHOICE,   boost::bind (&CaptionPanel::type_changed, this));
+       _type->Bind              (wxEVT_CHOICE,   boost::bind (&CaptionPanel::type_changed, this));
        _burn->Bind                     (wxEVT_CHECKBOX, boost::bind (&CaptionPanel::burn_toggled, this));
        _x_offset->Bind                 (wxEVT_SPINCTRL, boost::bind (&CaptionPanel::x_offset_changed, this));
        _y_offset->Bind                 (wxEVT_SPINCTRL, boost::bind (&CaptionPanel::y_offset_changed, this));
@@ -217,11 +218,11 @@ CaptionPanel::film_content_changed (int property)
                }
                setup_sensitivity ();
        } else if (property == CaptionContentProperty::USE) {
-               checked_set (_use, scs ? scs->caption->use() : false);
+               checked_set (_use, scs ? scs->caption_of_original_type(_original_type)->use() : false);
                setup_sensitivity ();
        } else if (property == CaptionContentProperty::TYPE) {
                if (scs) {
-                       switch (scs->caption->type()) {
+                       switch (scs->caption_of_original_type(_original_type)->type()) {
                        case CAPTION_OPEN:
                                _type->SetSelection (0);
                                break;
@@ -236,29 +237,29 @@ CaptionPanel::film_content_changed (int property)
                }
                setup_sensitivity ();
        } else if (property == CaptionContentProperty::BURN) {
-               checked_set (_burn, scs ? scs->caption->burn() : false);
+               checked_set (_burn, scs ? scs->caption_of_original_type(_original_type)->burn() : false);
        } else if (property == CaptionContentProperty::X_OFFSET) {
-               checked_set (_x_offset, scs ? lrint (scs->caption->x_offset() * 100) : 0);
+               checked_set (_x_offset, scs ? lrint (scs->caption_of_original_type(_original_type)->x_offset() * 100) : 0);
        } else if (property == CaptionContentProperty::Y_OFFSET) {
-               checked_set (_y_offset, scs ? lrint (scs->caption->y_offset() * 100) : 0);
+               checked_set (_y_offset, scs ? lrint (scs->caption_of_original_type(_original_type)->y_offset() * 100) : 0);
        } else if (property == CaptionContentProperty::X_SCALE) {
-               checked_set (_x_scale, scs ? lrint (scs->caption->x_scale() * 100) : 100);
+               checked_set (_x_scale, scs ? lrint (scs->caption_of_original_type(_original_type)->x_scale() * 100) : 100);
        } else if (property == CaptionContentProperty::Y_SCALE) {
-               checked_set (_y_scale, scs ? lrint (scs->caption->y_scale() * 100) : 100);
+               checked_set (_y_scale, scs ? lrint (scs->caption_of_original_type(_original_type)->y_scale() * 100) : 100);
        } else if (property == CaptionContentProperty::LINE_SPACING) {
-               checked_set (_line_spacing, scs ? lrint (scs->caption->line_spacing() * 100) : 100);
+               checked_set (_line_spacing, scs ? lrint (scs->caption_of_original_type(_original_type)->line_spacing() * 100) : 100);
        } else if (property == CaptionContentProperty::LANGUAGE) {
-               checked_set (_language, scs ? scs->caption->language() : "");
-       } else if (property == DCPContentProperty::REFERENCE_SUBTITLE) {
+               checked_set (_language, scs ? scs->caption_of_original_type(_original_type)->language() : "");
+       } else if (property == DCPContentProperty::REFERENCE_CAPTION) {
                if (scs) {
                        shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (scs);
-                       checked_set (_reference, dcp ? dcp->reference_subtitle () : false);
+                       checked_set (_reference, dcp ? dcp->reference_caption(_original_type) : false);
                } else {
                        checked_set (_reference, false);
                }
 
                setup_sensitivity ();
-       } else if (property == DCPContentProperty::HAS_SUBTITLES) {
+       } else if (property == DCPContentProperty::CAPTIONS) {
                setup_sensitivity ();
        }
 }
@@ -267,7 +268,7 @@ void
 CaptionPanel::use_toggled ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption()) {
-               i->caption->set_use (_use->GetValue());
+               i->caption_of_original_type(_original_type)->set_use (_use->GetValue());
        }
 }
 
@@ -277,10 +278,10 @@ CaptionPanel::type_changed ()
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption()) {
                switch (_type->GetSelection()) {
                case 0:
-                       i->caption->set_type (CAPTION_OPEN);
+                       i->caption_of_original_type(_original_type)->set_type (CAPTION_OPEN);
                        break;
                case 1:
-                       i->caption->set_type (CAPTION_CLOSED);
+                       i->caption_of_original_type(_original_type)->set_type (CAPTION_CLOSED);
                        break;
                }
        }
@@ -290,7 +291,7 @@ void
 CaptionPanel::burn_toggled ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption ()) {
-               i->caption->set_burn (_burn->GetValue());
+               i->caption_of_original_type(_original_type)->set_burn (_burn->GetValue());
        }
 }
 
@@ -307,7 +308,7 @@ CaptionPanel::setup_sensitivity ()
                shared_ptr<const DCPContent> dc = boost::dynamic_pointer_cast<const DCPContent> (i);
                shared_ptr<const DCPSubtitleContent> dsc = boost::dynamic_pointer_cast<const DCPSubtitleContent> (i);
                if (fc) {
-                       if (fc->caption) {
+                       if (!fc->caption.empty()) {
                                ++ffmpeg_subs;
                                ++any_subs;
                        }
@@ -325,7 +326,7 @@ CaptionPanel::setup_sensitivity ()
        }
 
        string why_not;
-       bool const can_reference = dcp && dcp->can_reference_subtitle (why_not);
+       bool const can_reference = dcp && dcp->can_reference_caption (_original_type, why_not);
        setup_refer_button (_reference, _reference_note, dcp, can_reference, why_not);
 
        bool const reference = _reference->GetValue ();
@@ -373,7 +374,7 @@ void
 CaptionPanel::x_offset_changed ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption ()) {
-               i->caption->set_x_offset (_x_offset->GetValue() / 100.0);
+               i->caption_of_original_type(_original_type)->set_x_offset (_x_offset->GetValue() / 100.0);
        }
 }
 
@@ -381,7 +382,7 @@ void
 CaptionPanel::y_offset_changed ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption ()) {
-               i->caption->set_y_offset (_y_offset->GetValue() / 100.0);
+               i->caption_of_original_type(_original_type)->set_y_offset (_y_offset->GetValue() / 100.0);
        }
 }
 
@@ -390,7 +391,7 @@ CaptionPanel::x_scale_changed ()
 {
        ContentList c = _parent->selected_caption ();
        if (c.size() == 1) {
-               c.front()->caption->set_x_scale (_x_scale->GetValue() / 100.0);
+               c.front()->caption_of_original_type(_original_type)->set_x_scale (_x_scale->GetValue() / 100.0);
        }
 }
 
@@ -398,7 +399,7 @@ void
 CaptionPanel::y_scale_changed ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption ()) {
-               i->caption->set_y_scale (_y_scale->GetValue() / 100.0);
+               i->caption_of_original_type(_original_type)->set_y_scale (_y_scale->GetValue() / 100.0);
        }
 }
 
@@ -406,7 +407,7 @@ void
 CaptionPanel::line_spacing_changed ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption ()) {
-               i->caption->set_line_spacing (_line_spacing->GetValue() / 100.0);
+               i->caption_of_original_type(_original_type)->set_line_spacing (_line_spacing->GetValue() / 100.0);
        }
 }
 
@@ -414,7 +415,7 @@ void
 CaptionPanel::language_changed ()
 {
        BOOST_FOREACH (shared_ptr<Content> i, _parent->selected_caption ()) {
-               i->caption->set_language (wx_to_std (_language->GetValue()));
+               i->caption_of_original_type(_original_type)->set_language (wx_to_std (_language->GetValue()));
        }
 }
 
@@ -432,7 +433,7 @@ CaptionPanel::content_selection_changed ()
        film_content_changed (CaptionContentProperty::LANGUAGE);
        film_content_changed (CaptionContentProperty::FONTS);
        film_content_changed (CaptionContentProperty::TYPE);
-       film_content_changed (DCPContentProperty::REFERENCE_SUBTITLE);
+       film_content_changed (DCPContentProperty::REFERENCE_CAPTION);
 }
 
 void
@@ -449,7 +450,7 @@ CaptionPanel::caption_view_clicked ()
        shared_ptr<Decoder> decoder = decoder_factory (c.front(), _parent->film()->log(), false);
 
        if (decoder) {
-               _caption_view = new CaptionView (this, _parent->film(), c.front(), decoder, _parent->film_viewer());
+               _caption_view = new CaptionView (this, _parent->film(), c.front(), c.front()->caption_of_original_type(_original_type), decoder, _parent->film_viewer());
                _caption_view->Show ();
        }
 }
@@ -465,7 +466,7 @@ CaptionPanel::fonts_dialog_clicked ()
        ContentList c = _parent->selected_caption ();
        DCPOMATIC_ASSERT (c.size() == 1);
 
-       _fonts_dialog = new FontsDialog (this, c.front ());
+       _fonts_dialog = new FontsDialog (this, c.front(), c.front()->caption_of_original_type(_original_type));
        _fonts_dialog->Show ();
 }
 
@@ -482,7 +483,7 @@ CaptionPanel::reference_clicked ()
                return;
        }
 
-       d->set_reference_subtitle (_reference->GetValue ());
+       d->set_reference_caption (_original_type, _reference->GetValue ());
 }
 
 void
@@ -491,7 +492,7 @@ CaptionPanel::appearance_dialog_clicked ()
        ContentList c = _parent->selected_caption ();
        DCPOMATIC_ASSERT (c.size() == 1);
 
-       CaptionAppearanceDialog* d = new CaptionAppearanceDialog (this, c.front());
+       CaptionAppearanceDialog* d = new CaptionAppearanceDialog (this, c.front(), c.front()->caption_of_original_type(_original_type));
        if (d->ShowModal () == wxID_OK) {
                d->apply ();
        }
index 38501e4d4301a46d87f333ca4d9af83cb527bb66..8adb85e15ecaa010dcbfafe84eca6ab48e2fc369 100644 (file)
@@ -69,4 +69,5 @@ private:
        wxButton* _fonts_dialog_button;
        FontsDialog* _fonts_dialog;
        wxButton* _appearance_dialog_button;
+       CaptionType _original_type;
 };
index c57f9161e5cce2e4b16a38635403644a3983e9a6..e6e63efffdcf862be274b590ed6b4100ee715612 100644 (file)
@@ -35,7 +35,7 @@ using boost::shared_ptr;
 using boost::bind;
 using boost::dynamic_pointer_cast;
 
-CaptionView::CaptionView (wxWindow* parent, shared_ptr<Film> film, shared_ptr<Content> content, shared_ptr<Decoder> decoder, FilmViewer* viewer)
+CaptionView::CaptionView (wxWindow* parent, shared_ptr<Film> film, shared_ptr<Content> content, shared_ptr<CaptionContent> caption, shared_ptr<Decoder> decoder, FilmViewer* viewer)
        : wxDialog (parent, wxID_ANY, _("Captions"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
        , _content (content)
        , _film_viewer (viewer)
@@ -85,8 +85,14 @@ CaptionView::CaptionView (wxWindow* parent, shared_ptr<Film> film, shared_ptr<Co
 
        _subs = 0;
        _frc = film->active_frame_rate_change (content->position());
-       decoder->caption->PlainStart.connect (bind (&CaptionView::data_start, this, _1));
-       decoder->caption->Stop.connect (bind (&CaptionView::data_stop, this, _1));
+
+       /* Find the decoder that is being used for our CaptionContent and attach to it */
+       BOOST_FOREACH (shared_ptr<CaptionDecoder> i, decoder->caption) {
+               if (i->content() == caption) {
+                       i->PlainStart.connect (bind (&CaptionView::data_start, this, _1));
+                       i->Stop.connect (bind (&CaptionView::data_stop, this, _1));
+               }
+       }
        while (!decoder->pass ()) {}
        SetSizerAndFit (sizer);
 }
index 34fec19053b4ac8aecc7d68287d987fc91398332..71c492ad6b8e5c59ff55d2d1fb9002202201b8b8 100644 (file)
@@ -29,7 +29,9 @@ class FilmViewer;
 class CaptionView : public wxDialog
 {
 public:
-       CaptionView (wxWindow *, boost::shared_ptr<Film>, boost::shared_ptr<Content> content, boost::shared_ptr<Decoder>, FilmViewer* viewer);
+       CaptionView (
+               wxWindow *, boost::shared_ptr<Film>, boost::shared_ptr<Content> content, boost::shared_ptr<CaptionContent> caption, boost::shared_ptr<Decoder>, FilmViewer* viewer
+               );
 
 private:
        void data_start (ContentTextCaption cts);
index 5a4873716e7098f609b9465e42df405d2d9057c1..40501f7178a1c8cf249d16b63a59f843282990f8 100644 (file)
@@ -196,7 +196,7 @@ ContentPanel::selected_caption ()
        ContentList sc;
 
        BOOST_FOREACH (shared_ptr<Content> i, selected ()) {
-               if (i->caption) {
+               if (!i->caption.empty()) {
                        sc.push_back (i);
                }
        }
@@ -447,7 +447,7 @@ ContentPanel::setup_sensitivity ()
 
        _video_panel->Enable    (_generally_sensitive && video_selection.size() > 0);
        _audio_panel->Enable    (_generally_sensitive && audio_selection.size() > 0);
-       _caption_panel->Enable  (_generally_sensitive && selection.size() == 1 && selection.front()->caption);
+       _caption_panel->Enable  (_generally_sensitive && selection.size() == 1 && !selection.front()->caption.empty());
        _timing_panel->Enable   (_generally_sensitive);
 }
 
index fcec93a31f09c91898ea8dd225c955fb0d266179..51881030fca2e550905d73eaf696f530b81cb72b 100644 (file)
@@ -438,7 +438,7 @@ DCPPanel::film_content_changed (int property)
            property == VideoContentProperty::SCALE ||
            property == DCPContentProperty::REFERENCE_VIDEO ||
            property == DCPContentProperty::REFERENCE_AUDIO ||
-           property == DCPContentProperty::REFERENCE_SUBTITLE) {
+           property == DCPContentProperty::REFERENCE_CAPTION) {
                setup_dcp_name ();
                setup_sensitivity ();
        }
index 528b3999bd036c529e90ba1bbffe7b19394fde3b..269dad6780fdb058465fff2e69f1d5f39ac24cf1 100644 (file)
@@ -34,9 +34,10 @@ using std::string;
 using std::cout;
 using boost::shared_ptr;
 
-FontsDialog::FontsDialog (wxWindow* parent, shared_ptr<Content> content)
+FontsDialog::FontsDialog (wxWindow* parent, shared_ptr<Content> content, shared_ptr<CaptionContent> caption)
        : wxDialog (parent, wxID_ANY, _("Fonts"))
        , _content (content)
+       , _caption (caption)
 {
        _fonts = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (550, 200), wxLC_REPORT | wxLC_SINGLE_SEL);
 
@@ -99,13 +100,14 @@ void
 FontsDialog::setup ()
 {
        shared_ptr<Content> content = _content.lock ();
-       if (!content) {
+       shared_ptr<CaptionContent> caption = _caption.lock ();
+       if (!content || !caption) {
                return;
        }
 
        _fonts->DeleteAllItems ();
        size_t n = 0;
-       BOOST_FOREACH (shared_ptr<Font> i, content->caption->fonts ()) {
+       BOOST_FOREACH (shared_ptr<Font> i, caption->fonts ()) {
                wxListItem item;
                item.SetId (n);
                _fonts->InsertItem (item);
@@ -138,14 +140,15 @@ void
 FontsDialog::edit_clicked ()
 {
        shared_ptr<Content> content = _content.lock ();
-       if (!content) {
+       shared_ptr<CaptionContent> caption = _caption.lock ();
+       if (!content || !caption) {
                return;
        }
 
        int const item = _fonts->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
        string const id = wx_to_std (_fonts->GetItemText (item, 0));
        shared_ptr<Font> font;
-       BOOST_FOREACH (shared_ptr<Font> i, content->caption->fonts()) {
+       BOOST_FOREACH (shared_ptr<Font> i, caption->fonts()) {
                if (i->id() == id) {
                        font = i;
                }
index e251ddab183d9412d958126c81bab8ae50b156f2..6c6873ea34f5629b2a448633026ca80041795bad 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2018 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 #include <boost/filesystem.hpp>
 
 class Content;
+class CaptionContent;
 
 class FontsDialog : public wxDialog
 {
 public:
-       FontsDialog (wxWindow* parent, boost::shared_ptr<Content>);
+       FontsDialog (wxWindow* parent, boost::shared_ptr<Content>, boost::shared_ptr<CaptionContent> caption);
 
 private:
        void setup ();
@@ -38,6 +39,7 @@ private:
        void edit_clicked ();
 
        boost::weak_ptr<Content> _content;
+       boost::weak_ptr<CaptionContent> _caption;
        wxListCtrl* _fonts;
        wxButton* _edit;
 };
index aab8c2cd576d2d45aea01fe3b55a6e9fb58d5bd4..c2bf77259c021efb798cbeee58052ab3e0d351c6 100644 (file)
@@ -131,7 +131,7 @@ PlayerInformation::triggered_update ()
        if (dcp->audio && !dcp->audio->streams().empty()) {
                checked_set (_dcp[r++], wxString::Format(_("Audio channels: %d"), dcp->audio->streams().front()->channels()));
        }
-       if (dcp->caption) {
+       if (!dcp->caption.empty()) {
                checked_set (_dcp[r++], _("Subtitles: yes"));
        } else {
                checked_set (_dcp[r++], _("Subtitles: no"));
index d3b291a9b04a9edd208cc1e77fb99f12cf8b88e2..f7a31117bd9ecfe21cee2d32d1ea7c7f39efbaec 100644 (file)
@@ -228,8 +228,8 @@ Timeline::recreate_views ()
                        _views.push_back (shared_ptr<TimelineView> (new TimelineAudioContentView (*this, i)));
                }
 
-               if (i->caption) {
-                       _views.push_back (shared_ptr<TimelineView> (new TimelineTextContentView (*this, i)));
+               BOOST_FOREACH (shared_ptr<CaptionContent> j, i->caption) {
+                       _views.push_back (shared_ptr<TimelineView> (new TimelineTextContentView (*this, i, j)));
                }
 
                if (dynamic_pointer_cast<AtmosMXFContent> (i)) {
@@ -334,9 +334,9 @@ Timeline::assign_tracks ()
        /* Tracks are:
           Video (mono or left-eye)
           Video (right-eye)
-          Subtitle 1
-          Subtitle 2
-          Subtitle N
+          Caption 1
+          Caption 2
+          Caption N
           Atmos
           Audio 1
           Audio 2
@@ -373,9 +373,9 @@ Timeline::assign_tracks ()
 
        _tracks = max (_tracks, 1);
 
-       /* Subtitle */
+       /* Captions */
 
-       int const subtitle_tracks = place<TimelineTextContentView> (_views, _tracks);
+       int const caption_tracks = place<TimelineTextContentView> (_views, _tracks);
 
        /* Atmos */
 
@@ -405,7 +405,7 @@ Timeline::assign_tracks ()
 
        _labels_view->set_3d (have_3d);
        _labels_view->set_audio_tracks (audio_tracks);
-       _labels_view->set_subtitle_tracks (subtitle_tracks);
+       _labels_view->set_caption_tracks (caption_tracks);
        _labels_view->set_atmos (have_atmos);
 
        _time_axis_view->set_y (tracks());
index 43adfc936a41820b1659ff38eb2c2a1554b4442f..af9cb771e29273b8c088d5c02e028940b464c42f 100644 (file)
@@ -32,13 +32,13 @@ TimelineLabelsView::TimelineLabelsView (Timeline& tl)
        : TimelineView (tl)
        , _threed (true)
        , _audio_tracks (0)
-       , _subtitle_tracks (0)
+       , _caption_tracks (0)
        , _atmos (true)
 {
        wxString labels[] = {
                _("Video"),
                _("Audio"),
-               _("Subtitles"),
+               _("Captions"),
                _("Atmos")
        };
 
@@ -70,9 +70,9 @@ TimelineLabelsView::do_paint (wxGraphicsContext* gc, list<dcpomatic::Rect<int> >
        gc->DrawText (_("Video"), 0, (ty + fy) / 2 - 8);
        fy = ty;
 
-       if (_subtitle_tracks) {
-               ty = fy + _subtitle_tracks * h;
-               gc->DrawText (_("Subtitles"), 0, (ty + fy) / 2 - 8);
+       if (_caption_tracks) {
+               ty = fy + _caption_tracks * h;
+               gc->DrawText (_("Captions"), 0, (ty + fy) / 2 - 8);
                fy = ty;
        }
 
@@ -101,9 +101,9 @@ TimelineLabelsView::set_audio_tracks (int n)
 }
 
 void
-TimelineLabelsView::set_subtitle_tracks (int n)
+TimelineLabelsView::set_caption_tracks (int n)
 {
-       _subtitle_tracks = n;
+       _caption_tracks = n;
 }
 
 void
index 25302cc33eabb370f9b8ebc2dc82f49f8da3a721..646d93a588dda155f1126e6091bc70289150f8b3 100644 (file)
@@ -31,7 +31,7 @@ public:
 
        void set_3d (bool s);
        void set_audio_tracks (int n);
-       void set_subtitle_tracks (int n);
+       void set_caption_tracks (int n);
        void set_atmos (bool s);
 
 private:
@@ -40,6 +40,6 @@ private:
        int _width;
        bool _threed;
        int _audio_tracks;
-       int _subtitle_tracks;
+       int _caption_tracks;
        bool _atmos;
 };
index bb874449ef7321c9795e53a294edaa6bcbf18d0d..30158941c2fe711536ebde5ed9d7feafd8675e12 100644 (file)
@@ -24,8 +24,9 @@
 
 using boost::shared_ptr;
 
-TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Content> c)
+TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Content> c, shared_ptr<CaptionContent> caption)
        : TimelineContentView (tl, c)
+       , _caption (caption)
 {
 
 }
@@ -55,5 +56,5 @@ TimelineTextContentView::active () const
 {
        shared_ptr<Content> c = _content.lock ();
        DCPOMATIC_ASSERT (c);
-       return c->caption && c->caption->use();
+       return _caption->use();
 }
index 540a90607ba15ff4f54cf6b3ffef8e7957caecf9..4f69f2aefa40d6b2d70ee0075c965f7ec90bbfba 100644 (file)
@@ -21,6 +21,7 @@
 #include "timeline_content_view.h"
 
 class TextContent;
+class CaptionContent;
 
 /** @class TimelineTextContentView
  *  @brief Timeline view for TextContent.
@@ -28,10 +29,12 @@ class TextContent;
 class TimelineTextContentView : public TimelineContentView
 {
 public:
-       TimelineTextContentView (Timeline& tl, boost::shared_ptr<Content> c);
+       TimelineTextContentView (Timeline& tl, boost::shared_ptr<Content>, boost::shared_ptr<CaptionContent>);
 
 private:
        bool active () const;
        wxColour background_colour () const;
        wxColour foreground_colour () const;
+
+       boost::shared_ptr<CaptionContent> _caption;
 };
index 9b516ccfa8a7ba9533444e511d067d8dfff56518..9bb608cb2637b8f66b5e3b5fcca327267247f49d 100644 (file)
@@ -282,7 +282,7 @@ TimingPanel::film_content_changed (int property)
                                ++count_ac;
                                content = i;
                        }
-                       if (i->caption && i->video_frame_rate()) {
+                       if (!i->caption.empty() && i->video_frame_rate()) {
                                ++count_sc;
                                content = i;
                        }
index a837c1a079c87bc4fb1eaaf8065437c2f29c9415..75768eca88a66080ee322c1b07a709c95c0dbffa 100644 (file)
@@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE (closed_caption_test1)
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs ());
 
-       content->caption->set_type (CAPTION_CLOSED);
+       content->only_caption()->set_type (CAPTION_CLOSED);
 
        film->make_dcp ();
        BOOST_REQUIRE (!wait_for_jobs ());
index 48f88f8dcbbe534ba1bb6b599a42607003421925..3d4b57e3c4c6212d2ca08102be190454e0145b06 100644 (file)
@@ -71,8 +71,8 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test)
 
        BOOST_CHECK_EQUAL (content->full_length().get(), DCPTime::from_seconds(2).get());
 
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        film->make_dcp ();
        BOOST_REQUIRE (!wait_for_jobs ());
 
@@ -91,7 +91,7 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_within_dcp_test)
        BOOST_REQUIRE (!wait_for_jobs ());
 
        shared_ptr<DCPDecoder> decoder (new DCPDecoder (content, film->log(), false));
-       decoder->caption->PlainStart.connect (bind (store, _1));
+       decoder->only_caption()->PlainStart.connect (bind (store, _1));
 
        stored = optional<ContentTextCaption> ();
        while (!decoder->pass() && !stored) {}
@@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test2)
        BOOST_REQUIRE (!wait_for_jobs ());
 
        shared_ptr<DCPSubtitleDecoder> decoder (new DCPSubtitleDecoder (content, film->log()));
-       decoder->caption->PlainStart.connect (bind (store, _1));
+       decoder->only_caption()->PlainStart.connect (bind (store, _1));
 
        stored = optional<ContentTextCaption> ();
        while (!decoder->pass ()) {
@@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test3)
        shared_ptr<DCPSubtitleDecoder> decoder (new DCPSubtitleDecoder (content, film->log()));
        stored = optional<ContentTextCaption> ();
        while (!decoder->pass ()) {
-               decoder->caption->PlainStart.connect (bind (store, _1));
+               decoder->only_caption()->PlainStart.connect (bind (store, _1));
                if (stored && stored->from() == ContentTime::from_seconds(0.08)) {
                        list<dcp::SubtitleString> s = stored->subs;
                        list<dcp::SubtitleString>::const_iterator i = s.begin ();
@@ -171,8 +171,8 @@ BOOST_AUTO_TEST_CASE (dcp_subtitle_test4)
        film->examine_and_add_content (content2);
        BOOST_REQUIRE (!wait_for_jobs ());
 
-       content->caption->add_font (shared_ptr<Font> (new Font ("font1")));
-       content2->caption->add_font (shared_ptr<Font> (new Font ("font2")));
+       content->only_caption()->add_font (shared_ptr<Font> (new Font ("font1")));
+       content2->only_caption()->add_font (shared_ptr<Font> (new Font ("font2")));
 
        film->make_dcp ();
        BOOST_REQUIRE (!wait_for_jobs ());
index d9dd0f3831bacf6a5bc3f55f71b49f72b8bd4118..837541f9db2cdf6909126bbfec7ba136dfea942f 100644 (file)
@@ -124,9 +124,9 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test6)
        shared_ptr<TextCaptionFileContent> s (new TextCaptionFileContent (film, "test/data/subrip2.srt"));
        film->examine_and_add_content (s);
        BOOST_REQUIRE (!wait_for_jobs ());
-       s->caption->set_colour (dcp::Colour (255, 255, 0));
-       s->caption->set_effect (dcp::SHADOW);
-       s->caption->set_effect_colour (dcp::Colour (0, 255, 255));
+       s->only_caption()->set_colour (dcp::Colour (255, 255, 0));
+       s->only_caption()->set_effect (dcp::SHADOW);
+       s->only_caption()->set_effect_colour (dcp::Colour (0, 255, 255));
        film->write_metadata();
 
        shared_ptr<Job> job (new TranscodeJob (film));
@@ -149,9 +149,9 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_prores_test7)
        shared_ptr<TextCaptionFileContent> s (new TextCaptionFileContent (film, "test/data/subrip.srt"));
        film->examine_and_add_content (s);
        BOOST_REQUIRE (!wait_for_jobs ());
-       s->caption->set_colour (dcp::Colour (255, 255, 0));
-       s->caption->set_effect (dcp::SHADOW);
-       s->caption->set_effect_colour (dcp::Colour (0, 255, 255));
+       s->only_caption()->set_colour (dcp::Colour (255, 255, 0));
+       s->only_caption()->set_effect (dcp::SHADOW);
+       s->only_caption()->set_effect_colour (dcp::Colour (0, 255, 255));
 
        shared_ptr<Job> job (new TranscodeJob (film));
        FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_prores_test7.mov", FFmpegEncoder::FORMAT_PRORES, false);
@@ -175,9 +175,9 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test2)
        shared_ptr<TextCaptionFileContent> s (new TextCaptionFileContent (film, "test/data/subrip2.srt"));
        film->examine_and_add_content (s);
        BOOST_REQUIRE (!wait_for_jobs ());
-       s->caption->set_colour (dcp::Colour (255, 255, 0));
-       s->caption->set_effect (dcp::SHADOW);
-       s->caption->set_effect_colour (dcp::Colour (0, 255, 255));
+       s->only_caption()->set_colour (dcp::Colour (255, 255, 0));
+       s->only_caption()->set_effect (dcp::SHADOW);
+       s->only_caption()->set_effect_colour (dcp::Colour (0, 255, 255));
        film->write_metadata();
 
        shared_ptr<Job> job (new TranscodeJob (film));
@@ -200,9 +200,9 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_h264_test3)
        shared_ptr<TextCaptionFileContent> s (new TextCaptionFileContent (film, "test/data/subrip.srt"));
        film->examine_and_add_content (s);
        BOOST_REQUIRE (!wait_for_jobs ());
-       s->caption->set_colour (dcp::Colour (255, 255, 0));
-       s->caption->set_effect (dcp::SHADOW);
-       s->caption->set_effect_colour (dcp::Colour (0, 255, 255));
+       s->only_caption()->set_colour (dcp::Colour (255, 255, 0));
+       s->only_caption()->set_effect (dcp::SHADOW);
+       s->only_caption()->set_effect_colour (dcp::Colour (0, 255, 255));
        film->write_metadata();
 
        shared_ptr<Job> job (new TranscodeJob (film));
index 510083236f294cc1e56a0ec4dc5de7715d6b0959..bc4af970ea2c1122f6d04c3410dc1ea0e9e9fb39 100644 (file)
@@ -211,7 +211,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test)
        shared_ptr<DCPContent> dcp (new DCPContent (film, private_data / "awkward_subs"));
        film->examine_and_add_content (dcp, true);
        BOOST_REQUIRE (!wait_for_jobs ());
-       dcp->caption->set_use (true);
+       dcp->only_caption()->set_use (true);
 
        shared_ptr<Player> player (new Player (film, film->playlist()));
        player->set_fast ();
@@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test2)
        shared_ptr<DCPContent> dcp (new DCPContent (film, private_data / "awkward_subs2"));
        film->examine_and_add_content (dcp, true);
        BOOST_REQUIRE (!wait_for_jobs ());
-       dcp->caption->set_use (true);
+       dcp->only_caption()->set_use (true);
 
        shared_ptr<Player> player (new Player (film, film->playlist()));
        player->set_fast ();
index 5c371e855b03b580c353509464bdc3f0683047d5..bef1bc36a27f367d5312a163ebcc6334b1591ef5 100644 (file)
@@ -37,14 +37,14 @@ BOOST_AUTO_TEST_CASE (remake_with_subtitle_test)
        shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory(film, private_data / "prophet_short_clip.mkv").front());
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs ());
-       content->caption->set_burn (true);
-       content->caption->set_use (true);
+       content->only_caption()->set_burn (true);
+       content->only_caption()->set_use (true);
        film->make_dcp ();
        BOOST_REQUIRE (!wait_for_jobs ());
 
        boost::filesystem::remove_all (film->dir (film->dcp_name(), false));
 
-       content->caption->set_use (false);
+       content->only_caption()->set_use (false);
        film->make_dcp ();
        BOOST_REQUIRE (!wait_for_jobs ());
 
index 28ef9606fc487df2a5cc499da34200e3e3beecf8..6975403baaa6085ee58f66639ad5b3403ec54fd1 100644 (file)
@@ -51,8 +51,8 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test)
        film->examine_and_add_content (content);
        wait_for_jobs ();
 
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        film->make_dcp ();
        wait_for_jobs ();
 
@@ -73,10 +73,10 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test2)
        film->examine_and_add_content (content);
        wait_for_jobs ();
 
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        /* Use test/data/subrip2.srt as if it were a font file  */
-       content->caption->fonts().front()->set_file (FontFiles::NORMAL, "test/data/subrip2.srt");
+       content->only_caption()->fonts().front()->set_file (FontFiles::NORMAL, "test/data/subrip2.srt");
 
        film->make_dcp ();
        wait_for_jobs ();
@@ -108,8 +108,8 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test3)
        film->examine_and_add_content (content);
        wait_for_jobs ();
 
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
 
        film->make_dcp ();
        wait_for_jobs ();
@@ -126,8 +126,8 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test4)
        film->set_name ("frobozz");
        film->set_interop (false);
        shared_ptr<TextCaptionFileContent> content (new TextCaptionFileContent (film, "test/data/subrip2.srt"));
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        film->examine_and_add_content (content);
        wait_for_jobs ();
        film->make_dcp ();
@@ -147,8 +147,8 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test5)
        film->set_interop (true);
        film->set_sequence (false);
        shared_ptr<TextCaptionFileContent> content (new TextCaptionFileContent (film, "test/data/subrip2.srt"));
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        film->examine_and_add_content (content);
        film->examine_and_add_content (content);
        wait_for_jobs ();
@@ -165,8 +165,8 @@ BOOST_AUTO_TEST_CASE (srt_subtitle_test6)
        shared_ptr<Film> film = new_test_film2 ("srt_subtitle_test6");
        film->set_interop (false);
        shared_ptr<TextCaptionFileContent> content (new TextCaptionFileContent (film, "test/data/frames.srt"));
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs ());
        film->make_dcp ();
index 6d4eecd92411e247b965540a834aadbb89940b7a..57813b944bf8d9c7370dcbfd577382802d6cbaea 100644 (file)
@@ -51,8 +51,8 @@ BOOST_AUTO_TEST_CASE (ssa_subtitle_test1)
        film->examine_and_add_content (content);
        wait_for_jobs ();
 
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
 
        film->make_dcp ();
        wait_for_jobs ();
index a82094a7351e253bb1c7d9904f340c28c9ad0fc9..7cb184477e525d28a96cf908fbaa041dc7b6e75d 100644 (file)
@@ -46,8 +46,8 @@ BOOST_AUTO_TEST_CASE (subtitle_reel_number_test)
        shared_ptr<TextCaptionFileContent> content (new TextCaptionFileContent (film, "test/data/subrip5.srt"));
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs ());
-       content->caption->set_use (true);
-       content->caption->set_burn (false);
+       content->only_caption()->set_use (true);
+       content->only_caption()->set_burn (false);
        film->set_reel_type (REELTYPE_BY_LENGTH);
        film->set_interop (true);
        film->set_reel_length (1024 * 1024 * 512);
index 3b3077242ad57c48cac0e6196deb57e20dc66093..5cded6e256d8ca91b6f2f6e5b81aef4331226eee 100644 (file)
@@ -57,14 +57,16 @@ BOOST_AUTO_TEST_CASE (vf_test1)
        string why_not;
        BOOST_CHECK (!dcp->can_reference_video(why_not));
        BOOST_CHECK (!dcp->can_reference_audio(why_not));
-       BOOST_CHECK (!dcp->can_reference_subtitle(why_not));
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_OPEN, why_not));
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_CLOSED, why_not));
 
        /* Multi-reel DCP can be referenced if we are using by-video-content */
        film->set_reel_type (REELTYPE_BY_VIDEO_CONTENT);
        BOOST_CHECK (dcp->can_reference_video(why_not));
        BOOST_CHECK (dcp->can_reference_audio(why_not));
-       /* (but reels_test2 has no subtitles to reference) */
-       BOOST_CHECK (!dcp->can_reference_subtitle(why_not));
+       /* (but reels_test2 has no captions to reference) */
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_OPEN, why_not));
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_CLOSED, why_not));
 
        shared_ptr<FFmpegContent> other (new FFmpegContent (film, "test/data/test.mp4"));
        film->examine_and_add_content (other);
@@ -74,14 +76,16 @@ BOOST_AUTO_TEST_CASE (vf_test1)
        other->set_position (DCPTime (0));
        BOOST_CHECK (!dcp->can_reference_video(why_not));
        BOOST_CHECK (!dcp->can_reference_audio(why_not));
-       BOOST_CHECK (!dcp->can_reference_subtitle(why_not));
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_OPEN, why_not));
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_CLOSED, why_not));
 
        /* This should not be considered an overlap */
        other->set_position (dcp->end ());
        BOOST_CHECK (dcp->can_reference_video(why_not));
        BOOST_CHECK (dcp->can_reference_audio(why_not));
-       /* (reels_test2 has no subtitles to reference) */
-       BOOST_CHECK (!dcp->can_reference_subtitle(why_not));
+       /* (reels_test2 has no captions to reference) */
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_OPEN, why_not));
+       BOOST_CHECK (!dcp->can_reference_caption(CAPTION_CLOSED, why_not));
 }
 
 /** Make a OV with video and audio and a VF referencing the OV and adding subs */