+
+void
+Film::set_marker (dcp::Marker type, DCPTime time)
+{
+ FilmChangeSignaller ch(this, FilmProperty::MARKERS);
+ _markers[type] = time;
+}
+
+
+void
+Film::unset_marker (dcp::Marker type)
+{
+ FilmChangeSignaller ch(this, FilmProperty::MARKERS);
+ _markers.erase (type);
+}
+
+
+void
+Film::clear_markers ()
+{
+ FilmChangeSignaller ch(this, FilmProperty::MARKERS);
+ _markers.clear ();
+}
+
+
+void
+Film::set_ratings (vector<dcp::Rating> r)
+{
+ FilmChangeSignaller ch(this, FilmProperty::RATINGS);
+ _ratings = r;
+}
+
+void
+Film::set_content_versions (vector<string> v)
+{
+ FilmChangeSignaller ch(this, FilmProperty::CONTENT_VERSIONS);
+ _content_versions = v;
+}
+
+
+void
+Film::set_name_language (dcp::LanguageTag lang)
+{
+ FilmChangeSignaller ch(this, FilmProperty::NAME_LANGUAGE);
+ _name_language = lang;
+}
+
+
+void
+Film::set_release_territory (optional<dcp::LanguageTag::RegionSubtag> region)
+{
+ FilmChangeSignaller ch(this, FilmProperty::RELEASE_TERRITORY);
+ _release_territory = region;
+}
+
+
+void
+Film::set_status (dcp::Status s)
+{
+ FilmChangeSignaller ch(this, FilmProperty::STATUS);
+ _status = s;
+}
+
+
+void
+Film::set_version_number (int v)
+{
+ FilmChangeSignaller ch(this, FilmProperty::VERSION_NUMBER);
+ _version_number = v;
+}
+
+
+void
+Film::set_chain (optional<string> c)
+{
+ FilmChangeSignaller ch(this, FilmProperty::CHAIN);
+ _chain = c;
+}
+
+
+void
+Film::set_distributor (optional<string> d)
+{
+ FilmChangeSignaller ch(this, FilmProperty::DISTRIBUTOR);
+ _distributor = d;
+}
+
+
+void
+Film::set_luminance (optional<dcp::Luminance> l)
+{
+ FilmChangeSignaller ch(this, FilmProperty::LUMINANCE);
+ _luminance = l;
+}
+
+
+void
+Film::set_facility (optional<string> f)
+{
+ FilmChangeSignaller ch(this, FilmProperty::FACILITY);
+ _facility = f;
+}
+
+
+void
+Film::set_studio (optional<string> s)
+{
+ FilmChangeSignaller ch(this, FilmProperty::STUDIO);
+ _studio = s;
+}
+
+
+optional<DCPTime>
+Film::marker (dcp::Marker type) const
+{
+ auto i = _markers.find (type);
+ if (i == _markers.end()) {
+ return {};
+ }
+ return i->second;
+}
+
+shared_ptr<InfoFileHandle>
+Film::info_file_handle (DCPTimePeriod period, bool read) const
+{
+ return std::make_shared<InfoFileHandle>(_info_file_mutex, info_file(period), read);
+}
+
+InfoFileHandle::InfoFileHandle (boost::mutex& mutex, boost::filesystem::path path, bool read)
+ : _lock (mutex)
+ , _file(path, read ? "rb" : (dcp::filesystem::exists(path) ? "r+b" : "wb"))
+{
+ if (!_file) {
+ throw OpenFileError(path, errno, read ? OpenFileError::READ : (dcp::filesystem::exists(path) ? OpenFileError::READ_WRITE : OpenFileError::WRITE));
+ }
+}
+
+
+/** 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::from_frames(1, video_frame_rate());
+ }
+
+ if (markers.find(dcp::Marker::LFOC) == markers.end()) {
+ markers[dcp::Marker::LFOC] = length() - DCPTime::from_frames(1, video_frame_rate());
+ }
+}
+
+
+void
+Film::set_temp_version (bool t)
+{
+ FilmChangeSignaller ch(this, FilmProperty::TEMP_VERSION);
+ _temp_version = t;
+}
+
+
+void
+Film::set_pre_release (bool p)
+{
+ FilmChangeSignaller ch(this, FilmProperty::PRE_RELEASE);
+ _pre_release = p;
+}
+
+
+void
+Film::set_red_band (bool r)
+{
+ FilmChangeSignaller ch(this, FilmProperty::RED_BAND);
+ _red_band = r;
+}
+
+
+void
+Film::set_two_d_version_of_three_d (bool t)
+{
+ FilmChangeSignaller ch(this, FilmProperty::TWO_D_VERSION_OF_THREE_D);
+ _two_d_version_of_three_d = t;
+}
+
+
+void
+Film::set_audio_language (optional<dcp::LanguageTag> language)
+{
+ FilmChangeSignaller ch(this, FilmProperty::AUDIO_LANGUAGE);
+ _audio_language = language;
+}
+
+
+void
+Film::set_audio_frame_rate (int rate)
+{
+ FilmChangeSignaller ch(this, FilmProperty::AUDIO_FRAME_RATE);
+ _audio_frame_rate = rate;
+}
+
+
+bool
+Film::has_sign_language_video_channel () const
+{
+ return _audio_channels >= static_cast<int>(dcp::Channel::SIGN_LANGUAGE);
+}
+
+
+void
+Film::set_sign_language_video_language (optional<dcp::LanguageTag> lang)
+{
+ FilmChangeSignaller ch(this, FilmProperty::SIGN_LANGUAGE_VIDEO_LANGUAGE);
+ _sign_language_video_language = lang;
+}
+
+
+void
+Film::set_dirty (bool dirty)
+{
+ auto const changed = dirty != _dirty;
+ _dirty = dirty;
+ if (changed) {
+ emit (boost::bind(boost::ref(DirtyChange), _dirty));
+ }
+}
+
+
+/** @return true if the metadata was (probably) last written by a version earlier
+ * than the given one; false if it definitely was not.
+ */
+bool
+Film::last_written_by_earlier_than(int major, int minor, int micro) const
+{
+ if (!_last_written_by) {
+ return true;
+ }
+
+ vector<string> parts;
+ boost::split(parts, *_last_written_by, boost::is_any_of("."));
+
+ if (parts.size() != 3) {
+ /* Not sure what's going on, so let's say it was written by an old version */
+ return true;
+ }
+
+ if (boost::ends_with(parts[2], "pre")) {
+ parts[2] = parts[2].substr(0, parts[2].length() - 3);
+ }
+
+ int our_major = dcp::raw_convert<int>(parts[0]);
+ int our_minor = dcp::raw_convert<int>(parts[1]);
+ int our_micro = dcp::raw_convert<int>(parts[2]);
+
+ if (our_major != major) {
+ return our_major < major;
+ }
+
+ if (our_minor != minor) {
+ return our_minor < minor;
+ }
+
+ return our_micro < micro;
+}
+
+
+void
+Film::set_territory_type(TerritoryType type)
+{
+ FilmChangeSignaller ch(this, FilmProperty::TERRITORY_TYPE);
+ _territory_type = type;
+}
+
+
+void
+Film::set_ui_state(string key, string value)
+{
+ _ui_state[key] = value;
+ write_ui_state();
+}
+
+
+boost::optional<std::string>
+Film::ui_state(string key) const
+{
+ auto iter = _ui_state.find(key);
+ if (iter == _ui_state.end()) {
+ return {};
+ }
+
+ return iter->second;
+}
+
+
+void
+Film::write_ui_state() const
+{
+ auto doc = make_shared<xmlpp::Document>();
+ auto root = doc->create_root_node("UI");
+
+ for (auto state: _ui_state) {
+ cxml::add_text_child(root, state.first, state.second);
+ }
+
+ try {
+ doc->write_to_file_formatted(dcp::filesystem::fix_long_path(file(ui_state_file)).string());
+ } catch (...) {}
+}
+
+
+void
+Film::read_ui_state()
+{
+ try {
+ cxml::Document xml("UI");
+ xml.read_file(dcp::filesystem::fix_long_path(file(ui_state_file)));
+ for (auto node: xml.node_children()) {
+ if (!node->is_text()) {
+ _ui_state[node->name()] = node->content();
+ }
+ }
+ } catch (...) {}
+}