BOOST_FOREACH.
[dcpomatic.git] / src / lib / film.cc
index 79ee20cfb791f03b2b402bebb398013bbd4d8ac7..8bcbaebe5e044c500f2617940419b55715295815 100644 (file)
@@ -69,7 +69,6 @@
 #include <libxml++/libxml++.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
-#include <boost/foreach.hpp>
 #include <boost/regex.hpp>
 #include <unistd.h>
 #include <stdexcept>
@@ -97,11 +96,14 @@ using std::back_inserter;
 using std::map;
 using std::exception;
 using std::find;
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::dynamic_pointer_cast;
+using std::shared_ptr;
+using std::weak_ptr;
+using std::dynamic_pointer_cast;
 using boost::optional;
 using boost::is_any_of;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
 using dcp::raw_convert;
 using namespace dcpomatic;
 
@@ -166,6 +168,7 @@ Film::Film (optional<boost::filesystem::path> dir)
        , _user_explicit_container (false)
        , _user_explicit_resolution (false)
        , _name_language (dcp::LanguageTag("en-US"))
+       , _audio_language (dcp::LanguageTag("en-US"))
        , _release_territory (dcp::LanguageTag::RegionSubtag("US"))
        , _version_number (1)
        , _status (dcp::FINAL)
@@ -216,11 +219,11 @@ Film::Film (optional<boost::filesystem::path> dir)
 
 Film::~Film ()
 {
-       BOOST_FOREACH (boost::signals2::connection& i, _job_connections) {
+       for (auto& i: _job_connections) {
                i.disconnect ();
        }
 
-       BOOST_FOREACH (boost::signals2::connection& i, _audio_analysis_connections) {
+       for (auto& i: _audio_analysis_connections) {
                i.disconnect ();
        }
 }
@@ -284,7 +287,7 @@ Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
        boost::filesystem::path p = dir ("analysis");
 
        Digester digester;
-       BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
+       for (auto i: playlist->content ()) {
                if (!i->audio) {
                        continue;
                }
@@ -325,7 +328,7 @@ Film::subtitle_analysis_path (shared_ptr<const Content> content) const
                shared_ptr<TextContent> tc = content->text.front();
                digester.add (tc->x_scale());
                digester.add (tc->y_scale());
-               BOOST_FOREACH (shared_ptr<dcpomatic::Font> i, tc->fonts()) {
+               for (auto i: tc->fonts()) {
                        digester.add (i->id());
                }
                if (tc->effect()) {
@@ -376,7 +379,7 @@ Film::make_dcp (bool gui, bool check)
                set_name ("DCP");
        }
 
-       BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
+       for (auto i: content ()) {
                if (!i->paths_valid()) {
                        throw runtime_error (_("some of your content is missing"));
                }
@@ -391,11 +394,11 @@ Film::make_dcp (bool gui, bool check)
 
        set_isdcf_date_today ();
 
-       BOOST_FOREACH (string i, environment_info ()) {
+       for (auto i: environment_info ()) {
                LOG_GENERAL_NC (i);
        }
 
-       BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
+       for (auto i: content ()) {
                LOG_GENERAL ("Content: %1", i->technical_summary());
        }
        LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
@@ -466,13 +469,14 @@ Film::metadata (bool with_content_paths) const
                m->set_attribute("Type", dcp::marker_to_string(i->first));
                m->add_child_text(raw_convert<string>(i->second.get()));
        }
-       BOOST_FOREACH (dcp::Rating i, _ratings) {
+       for (auto i: _ratings) {
                i.as_xml (root->add_child("Rating"));
        }
-       BOOST_FOREACH (string i, _content_versions) {
+       for (auto i: _content_versions) {
                root->add_child("ContentVersion")->add_child_text(i);
        }
        root->add_child("NameLanguage")->add_child_text(_name_language.to_string());
+       root->add_child("AudioLanguage")->add_child_text(_audio_language.to_string());
        root->add_child("ReleaseTerritory")->add_child_text(_release_territory.subtag());
        root->add_child("VersionNumber")->add_child_text(raw_convert<string>(_version_number));
        root->add_child("Status")->add_child_text(dcp::status_to_string(_status));
@@ -483,6 +487,9 @@ Film::metadata (bool with_content_paths) const
        root->add_child("LuminanceUnit")->add_child_text(dcp::Luminance::unit_to_string(_luminance.unit()));
        root->add_child("UserExplicitContainer")->add_child_text(_user_explicit_container ? "1" : "0");
        root->add_child("UserExplicitResolution")->add_child_text(_user_explicit_resolution ? "1" : "0");
+       for (auto i: _subtitle_languages) {
+               root->add_child("SubtitleLanguage")->add_child_text(i.to_string());
+       }
        _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
 
        return doc;
@@ -620,15 +627,15 @@ Film::read_metadata (optional<boost::filesystem::path> path)
        _reencode_j2k = f.optional_bool_child("ReencodeJ2K").get_value_or(false);
        _user_explicit_video_frame_rate = f.optional_bool_child("UserExplicitVideoFrameRate").get_value_or(false);
 
-       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("Marker")) {
+       for (auto i: f.node_children("Marker")) {
                _markers[dcp::marker_from_string(i->string_attribute("Type"))] = DCPTime(dcp::raw_convert<DCPTime::Type>(i->content()));
        }
 
-       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("Rating")) {
+       for (auto i: f.node_children("Rating")) {
                _ratings.push_back (dcp::Rating(i));
        }
 
-       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("ContentVersion")) {
+       for (auto i: f.node_children("ContentVersion")) {
                _content_versions.push_back (i->content());
        }
 
@@ -636,6 +643,10 @@ Film::read_metadata (optional<boost::filesystem::path> path)
        if (name_language) {
                _name_language = dcp::LanguageTag (*name_language);
        }
+       optional<string> audio_language = f.optional_string_child("AudioLanguage");
+       if (audio_language) {
+               _audio_language = dcp::LanguageTag (*audio_language);
+       }
        optional<string> release_territory = f.optional_string_child("ReleaseTerritory");
        if (release_territory) {
                _release_territory = dcp::LanguageTag::RegionSubtag (*release_territory);
@@ -662,6 +673,10 @@ Film::read_metadata (optional<boost::filesystem::path> path)
        _user_explicit_container = f.optional_bool_child("UserExplicitContainer").get_value_or(true);
        _user_explicit_resolution = f.optional_bool_child("UserExplicitResolution").get_value_or(true);
 
+       for (auto i: f.node_children("SubtitleLanguage")) {
+               _subtitle_languages.push_back (dcp::LanguageTag(i->content()));
+       }
+
        list<string> notes;
        _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
 
@@ -670,6 +685,41 @@ Film::read_metadata (optional<boost::filesystem::path> path)
                set_backtrace_file (file ("backtrace.txt"));
        }
 
+       /* Around 2.15.108 we removed subtitle language state from the text content and the ISDCF
+        * metadata and put it into the Film instead.  If we've loaded an old Film let's try and fish
+        * out the settings from where they were so that they don't get lost.
+        */
+
+       optional<dcp::LanguageTag> found_language;
+
+       for (auto i: f.node_child("Playlist")->node_children("Content")) {
+               cxml::ConstNodePtr text = i->optional_node_child("Text");
+               if (text && text->optional_string_child("Language") && !found_language) {
+                       try {
+                               found_language = dcp::LanguageTag(text->string_child("Language"));
+                       } catch (...) {}
+               }
+       }
+
+       if (_state_version >= 9) {
+               optional<string> isdcf_language = f.node_child("ISDCFMetadata")->optional_string_child("SubtitleLanguage");
+               if (isdcf_language && !found_language) {
+                       try {
+                               found_language = dcp::LanguageTag(*isdcf_language);
+                       } catch (...) {
+                               try {
+                                       found_language = dcp::LanguageTag(boost::algorithm::to_lower_copy(*isdcf_language));
+                               } catch (...) {
+
+                               }
+                       }
+               }
+       }
+
+       if (found_language) {
+               _subtitle_languages.push_back (*found_language);
+       }
+
        _dirty = false;
        return notes;
 }
@@ -722,7 +772,7 @@ Film::mapped_audio_channels () const
                        mapped.push_back (i);
                }
        } else {
-               BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+               for (auto i: content ()) {
                        if (i->audio) {
                                list<int> c = i->audio->mapping().mapped_output_channels ();
                                copy (c.begin(), c.end(), back_inserter (mapped));
@@ -833,7 +883,7 @@ Film::isdcf_name (bool if_created_now) const
        /* Interior aspect ratio.  The standard says we don't do this for trailers, for some strange reason */
        if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
                Ratio const * content_ratio = 0;
-               BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+               for (auto i: content ()) {
                        if (i->video) {
                                /* Here's the first piece of video content */
                                content_ratio = Ratio::nearest_from_ratio(i->video->scaled_size(frame_size()).ratio());
@@ -854,39 +904,27 @@ Film::isdcf_name (bool if_created_now) const
                   for now I'm just appending -CCAP if we have any closed captions.
                */
 
-               optional<string> subtitle_language;
                bool burnt_in = true;
                bool ccap = false;
-               BOOST_FOREACH (shared_ptr<Content> i, content()) {
-                       BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
-                               if (j->type() == TEXT_OPEN_SUBTITLE && j->use()) {
-                                       subtitle_language = j->language ();
-                                       if (!j->burn()) {
-                                               burnt_in = false;
-                                       }
+               for (auto i: content()) {
+                       for (auto j: i->text) {
+                               if (j->type() == TEXT_OPEN_SUBTITLE && j->use() && !j->burn()) {
+                                       burnt_in = false;
                                } else if (j->type() == TEXT_CLOSED_CAPTION && j->use()) {
                                        ccap = true;
                                }
                        }
                }
 
-               if (dm.subtitle_language) {
-                       /* Subtitle language is overridden in ISDCF metadata, primarily to handle
-                          content with pre-burnt subtitles.
-                       */
-                       d += "-" + *dm.subtitle_language;
-                       if (ccap) {
-                               d += "-CCAP";
-                       }
-               } else if (subtitle_language) {
-                       /* Language is worked out from the content */
-                       if (burnt_in && *subtitle_language != "XX") {
-                               transform (subtitle_language->begin(), subtitle_language->end(), subtitle_language->begin(), ::tolower);
+               if (!_subtitle_languages.empty()) {
+                       string lang = _subtitle_languages.front().language().get_value_or("en").subtag();
+                       if (burnt_in) {
+                               transform (lang.begin(), lang.end(), lang.begin(), ::tolower);
                        } else {
-                               transform (subtitle_language->begin(), subtitle_language->end(), subtitle_language->begin(), ::toupper);
+                               transform (lang.begin(), lang.end(), lang.begin(), ::toupper);
                        }
 
-                       d += "-" + *subtitle_language;
+                       d += "-" + lang;
                        if (ccap) {
                                d += "-CCAP";
                        }
@@ -950,7 +988,7 @@ Film::isdcf_name (bool if_created_now) const
        }
 
        bool vf = false;
-       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+       for (auto i: content()) {
                shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
                if (!dc) {
                        continue;
@@ -1306,7 +1344,7 @@ Film::add_content (shared_ptr<Content> c)
 
        if (_template_film) {
                /* Take settings from the first piece of content of c's type in _template */
-               BOOST_FOREACH (shared_ptr<Content> i, _template_film->content()) {
+               for (auto i: _template_film->content()) {
                        c->take_settings_from (i);
                }
        }
@@ -1326,7 +1364,7 @@ Film::maybe_set_container_and_resolution ()
 {
        /* Get the only piece of video content, if there is only one */
        shared_ptr<VideoContent> video;
-       BOOST_FOREACH (shared_ptr<const Content> i, _playlist->content()) {
+       for (auto i: _playlist->content()) {
                if (i->video) {
                        if (!video) {
                                video = i->video;
@@ -1451,7 +1489,7 @@ void
 Film::check_settings_consistency ()
 {
        optional<int> atmos_rate;
-       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+       for (auto i: content()) {
 
                if (i->atmos) {
                        int rate = lrintf (i->atmos->edit_rate().as_float());
@@ -1466,7 +1504,7 @@ Film::check_settings_consistency ()
        }
 
        bool change_made = false;
-       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+       for (auto i: content()) {
                shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent>(i);
                if (!d) {
                        continue;
@@ -1481,6 +1519,14 @@ Film::check_settings_consistency ()
                        d->set_reference_audio(false);
                        change_made = true;
                }
+               if (d->reference_text(TEXT_OPEN_SUBTITLE) && !d->can_reference_text(shared_from_this(), TEXT_OPEN_SUBTITLE, why_not)) {
+                       d->set_reference_text(TEXT_OPEN_SUBTITLE, false);
+                       change_made = true;
+               }
+               if (d->reference_text(TEXT_CLOSED_CAPTION) && !d->can_reference_text(shared_from_this(), TEXT_CLOSED_CAPTION, why_not)) {
+                       d->set_reference_text(TEXT_CLOSED_CAPTION, false);
+                       change_made = true;
+               }
        }
 
        if (change_made) {
@@ -1546,7 +1592,7 @@ Film::active_area () const
        dcp::Size const frame = frame_size ();
        dcp::Size active;
 
-       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+       for (auto i: content()) {
                if (i->video) {
                        dcp::Size s = i->video->scaled_size (frame);
                        active.width = max(active.width, s.width);
@@ -1592,7 +1638,7 @@ Film::make_kdm (
 
        /* Find keys that have been added to imported, encrypted DCP content */
        list<dcp::DecryptedKDMKey> imported_keys;
-       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+       for (auto i: content()) {
                shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
                if (d && d->kdm()) {
                        dcp::DecryptedKDM kdm (d->kdm().get(), Config::instance()->decryption_chain()->key().get());
@@ -1603,14 +1649,14 @@ Film::make_kdm (
 
        map<shared_ptr<const dcp::ReelMXF>, dcp::Key> keys;
 
-       BOOST_FOREACH(shared_ptr<const dcp::ReelMXF> i, cpl->reel_mxfs()) {
+       for (auto i: cpl->reel_mxfs()) {
                if (!i->key_id()) {
                        continue;
                }
 
                /* Get any imported key for this ID */
                bool done = false;
-               BOOST_FOREACH (dcp::DecryptedKDMKey j, imported_keys) {
+               for (auto j: imported_keys) {
                        if (j.id() == i->key_id().get()) {
                                LOG_GENERAL ("Using imported key for %1", i->key_id().get());
                                keys[i] = j.key();
@@ -1675,33 +1721,10 @@ Film::should_be_enough_disk_space (double& required, double& available, bool& ca
        return (available - required) > 1;
 }
 
-string
-Film::subtitle_language () const
-{
-       set<string> languages;
-
-       BOOST_FOREACH (shared_ptr<Content> i, content()) {
-               BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
-                       languages.insert (j->language ());
-               }
-       }
-
-       string all;
-       BOOST_FOREACH (string s, languages) {
-               if (!all.empty ()) {
-                       all += "/" + s;
-               } else {
-                       all += s;
-               }
-       }
-
-       return all;
-}
-
 /** @return The names of the channels that audio contents' outputs are passed into;
  *  this is either the DCP or a AudioProcessor.
  */
-vector<string>
+vector<NamedChannel>
 Film::audio_output_names () const
 {
        if (audio_processor ()) {
@@ -1710,10 +1733,12 @@ Film::audio_output_names () const
 
        DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
 
-       vector<string> n;
+       vector<NamedChannel> n;
 
        for (int i = 0; i < audio_channels(); ++i) {
-               n.push_back (short_audio_channel_name (i));
+               if (i != 8 && i != 9 && i != 15) {
+                       n.push_back (NamedChannel(short_audio_channel_name(i), i));
+               }
        }
 
        return n;
@@ -1753,9 +1778,9 @@ Film::reels () const
                list<DCPTime> split_points;
                split_points.push_back (DCPTime());
                split_points.push_back (len);
-               BOOST_FOREACH (shared_ptr<Content> c, content()) {
+               for (auto c: content()) {
                        if (c->video) {
-                               BOOST_FOREACH (DCPTime t, c->reel_split_points(shared_from_this())) {
+                               for (auto t: c->reel_split_points(shared_from_this())) {
                                        split_points.push_back (t);
                                }
                                split_points.push_back (c->end(shared_from_this()));
@@ -1767,7 +1792,7 @@ Film::reels () const
 
                /* Make them into periods, coalescing any that are less than 1 second long */
                optional<DCPTime> last;
-               BOOST_FOREACH (DCPTime t, split_points) {
+               for (auto t: split_points) {
                        if (last && (t - *last) >= DCPTime::from_seconds(1)) {
                                /* Period from *last to t is long enough; use it and start a new one */
                                p.push_back (DCPTimePeriod(*last, t));
@@ -1844,7 +1869,7 @@ Film::copy_from (shared_ptr<const Film> film)
 bool
 Film::references_dcp_video () const
 {
-       BOOST_FOREACH (shared_ptr<Content> i, _playlist->content()) {
+       for (auto i: _playlist->content()) {
                shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent>(i);
                if (d && d->reference_video()) {
                        return true;
@@ -1857,7 +1882,7 @@ Film::references_dcp_video () const
 bool
 Film::references_dcp_audio () const
 {
-       BOOST_FOREACH (shared_ptr<Content> i, _playlist->content()) {
+       for (auto i: _playlist->content()) {
                shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent>(i);
                if (d && d->reference_audio()) {
                        return true;
@@ -1871,7 +1896,7 @@ Film::references_dcp_audio () const
 bool
 Film::contains_atmos_content () const
 {
-       BOOST_FOREACH (shared_ptr<Content> i, _playlist->content()) {
+       for (auto i: _playlist->content()) {
                if (i->atmos) {
                        return true;
                }
@@ -1885,8 +1910,8 @@ list<DCPTextTrack>
 Film::closed_caption_tracks () const
 {
        list<DCPTextTrack> tt;
-       BOOST_FOREACH (shared_ptr<Content> i, content()) {
-               BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
+       for (auto i: content()) {
+               for (auto j: i->text) {
                        /* XXX: Empty DCPTextTrack ends up being a magic value here - the "unknown" or "not specified" track */
                        DCPTextTrack dtt = j->dcp_track().get_value_or(DCPTextTrack());
                        if (j->type() == TEXT_CLOSED_CAPTION && find(tt.begin(), tt.end(), dtt) == tt.end()) {
@@ -1944,6 +1969,14 @@ Film::set_name_language (dcp::LanguageTag lang)
 }
 
 
+void
+Film::set_audio_language (dcp::LanguageTag lang)
+{
+       ChangeSignaller<Film> ch (this, AUDIO_LANGUAGE);
+       _audio_language = lang;
+}
+
+
 void
 Film::set_release_territory (dcp::LanguageTag::RegionSubtag region)
 {
@@ -1992,6 +2025,31 @@ Film::set_luminance (dcp::Luminance l)
 }
 
 
+void
+Film::set_subtitle_language (dcp::LanguageTag language)
+{
+       vector<dcp::LanguageTag> lang;
+       lang.push_back (language);
+       set_subtitle_languages (lang);
+}
+
+
+void
+Film::unset_subtitle_language ()
+{
+       ChangeSignaller<Film> ch (this, SUBTITLE_LANGUAGES);
+       _subtitle_languages.clear();
+}
+
+
+void
+Film::set_subtitle_languages (vector<dcp::LanguageTag> languages)
+{
+       ChangeSignaller<Film> ch (this, SUBTITLE_LANGUAGES);
+       _subtitle_languages = languages;
+}
+
+
 void
 Film::set_facility (string f)
 {
@@ -2043,3 +2101,17 @@ InfoFileHandle::~InfoFileHandle ()
 {
        fclose (_handle);
 }
+
+
+/** Add FFOC and LFOC markers to a list if they are not already there */
+void
+Film::add_ffoc_lfoc (Markers& markers) const
+{
+       if (markers.find(dcp::Marker::FFOC) == markers.end()) {
+               markers[dcp::Marker::FFOC] = dcpomatic::DCPTime(0);
+       }
+
+       if (markers.find(dcp::Marker::LFOC) == markers.end()) {
+               markers[dcp::Marker::LFOC] = length() - DCPTime::from_frames(1, video_frame_rate());
+       }
+}