+/** Read state from our metadata file */
+void
+Film::read_metadata ()
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+
+ ifstream f (file_locked("metadata").c_str());
+ multimap<string, string> kv = read_key_value (f);
+ for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
+ string const k = i->first;
+ string const v = i->second;
+
+ /* User-specified stuff */
+ if (k == "name") {
+ _name = v;
+ } else if (k == "use_dci_name") {
+ _use_dci_name = (v == "1");
+ } else if (k == "content") {
+ _content = v;
+ } else if (k == "dcp_content_type") {
+ _dcp_content_type = DCPContentType::from_pretty_name (v);
+ } else if (k == "format") {
+ _format = Format::from_metadata (v);
+ } else if (k == "left_crop") {
+ _crop.left = atoi (v.c_str ());
+ } else if (k == "right_crop") {
+ _crop.right = atoi (v.c_str ());
+ } else if (k == "top_crop") {
+ _crop.top = atoi (v.c_str ());
+ } else if (k == "bottom_crop") {
+ _crop.bottom = atoi (v.c_str ());
+ } else if (k == "filter") {
+ _filters.push_back (Filter::from_id (v));
+ } else if (k == "scaler") {
+ _scaler = Scaler::from_id (v);
+ } else if (k == "dcp_frames") {
+ _dcp_frames = atoi (v.c_str ());
+ } else if (k == "dcp_trim_action") {
+ if (v == "cut") {
+ _dcp_trim_action = CUT;
+ } else if (v == "black_out") {
+ _dcp_trim_action = BLACK_OUT;
+ }
+ } else if (k == "dcp_ab") {
+ _dcp_ab = (v == "1");
+ } else if (k == "selected_audio_stream") {
+ _audio_stream = atoi (v.c_str ());
+ } else if (k == "audio_gain") {
+ _audio_gain = atof (v.c_str ());
+ } else if (k == "audio_delay") {
+ _audio_delay = atoi (v.c_str ());
+ } else if (k == "still_duration") {
+ _still_duration = atoi (v.c_str ());
+ } else if (k == "selected_subtitle_stream") {
+ _subtitle_stream = atoi (v.c_str ());
+ } else if (k == "with_subtitles") {
+ _with_subtitles = (v == "1");
+ } else if (k == "subtitle_offset") {
+ _subtitle_offset = atoi (v.c_str ());
+ } else if (k == "subtitle_scale") {
+ _subtitle_scale = atof (v.c_str ());
+ } else if (k == "audio_language") {
+ _audio_language = v;
+ } else if (k == "subtitle_language") {
+ _subtitle_language = v;
+ } else if (k == "territory") {
+ _territory = v;
+ } else if (k == "rating") {
+ _rating = v;
+ } else if (k == "studio") {
+ _studio = v;
+ } else if (k == "facility") {
+ _facility = v;
+ } else if (k == "package_type") {
+ _package_type = v;
+ }
+
+ /* Cached stuff */
+ if (k == "thumb") {
+ int const n = atoi (v.c_str ());
+ /* Only add it to the list if it still exists */
+ if (filesystem::exists (thumb_file_for_frame (n))) {
+ _thumbs.push_back (n);
+ }
+ } else if (k == "width") {
+ _size.width = atoi (v.c_str ());
+ } else if (k == "height") {
+ _size.height = atoi (v.c_str ());
+ } else if (k == "length") {
+ _length = atof (v.c_str ());
+ } else if (k == "audio_sample_rate") {
+ _audio_sample_rate = atoi (v.c_str ());
+ } else if (k == "content_digest") {
+ _content_digest = v;
+ } else if (k == "has_subtitles") {
+ _has_subtitles = (v == "1");
+ } else if (k == "audio_stream") {
+ _audio_streams.push_back (AudioStream (v));
+ } else if (k == "subtitle_stream") {
+ _subtitle_streams.push_back (SubtitleStream (v));
+ } else if (k == "frames_per_second") {
+ _frames_per_second = atof (v.c_str ());
+ }
+ }
+
+ _dirty = false;
+}
+
+/** @param n A thumb index.
+ * @return The path to the thumb's image file.
+ */
+string
+Film::thumb_file (int n) const
+{
+ return thumb_file_for_frame (thumb_frame (n));
+}
+
+/** @param n A frame index within the Film.
+ * @return The path to the thumb's image file for this frame;
+ * we assume that it exists.
+ */
+string
+Film::thumb_file_for_frame (int n) const
+{
+ return thumb_base_for_frame(n) + ".png";
+}
+
+string
+Film::thumb_base (int n) const
+{
+ return thumb_base_for_frame (thumb_frame (n));
+}
+
+string
+Film::thumb_base_for_frame (int n) const
+{
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << n;
+
+ filesystem::path p;
+ p /= dir ("thumbs");
+ p /= s.str ();
+
+ return p.string ();
+}
+
+
+/** @param n A thumb index.
+ * @return The frame within the Film that it is for.
+ */
+int
+Film::thumb_frame (int n) const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ assert (n < int (_thumbs.size ()));
+ return _thumbs[n];
+}
+
+Size
+Film::cropped_size (Size s) const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ s.width -= _crop.left + _crop.right;
+ s.height -= _crop.top + _crop.bottom;
+ return s;
+}
+
+/** Given a directory name, return its full path within the Film's directory.
+ * The directory (and its parents) will be created if they do not exist.
+ */
+string
+Film::dir (string d) const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ filesystem::path p;
+ p /= _directory;
+ p /= d;
+ filesystem::create_directories (p);
+ return p.string ();
+}
+
+/** Given a file or directory name, return its full path within the Film's directory */
+string
+Film::file (string f) const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return file_locked (f);
+}
+
+string
+Film::file_locked (string f) const
+{
+ filesystem::path p;
+ p /= _directory;
+ p /= f;
+ return p.string ();
+}
+
+/** @return full path of the content (actual video) file
+ * of the Film.
+ */
+string
+Film::content_path () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ if (filesystem::path(_content).has_root_directory ()) {
+ return _content;
+ }
+
+ return file_locked (_content);
+}
+
+ContentType
+Film::content_type () const
+{
+#if BOOST_FILESYSTEM_VERSION == 3
+ string ext = filesystem::path(_content).extension().string();
+#else
+ string ext = filesystem::path(_content).extension();
+#endif
+
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+
+ if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
+ return STILL;
+ }
+
+ return VIDEO;
+}
+
+/** @return The sampling rate that we will resample the audio to */
+int
+Film::target_audio_sample_rate () const
+{
+ /* Resample to a DCI-approved sample rate */
+ double t = dcp_audio_sample_rate (audio_sample_rate());
+
+ /* Compensate for the fact that video will be rounded to the
+ nearest integer number of frames per second.
+ */
+ if (rint (frames_per_second()) != frames_per_second()) {
+ t *= _frames_per_second / rint (frames_per_second());
+ }
+
+ return rint (t);
+}
+
+int
+Film::dcp_length () const
+{
+ if (dcp_frames()) {
+ return dcp_frames();
+ }
+
+ return length();
+}
+
+/** @return a DCI-compliant name for a DCP of this film */
+string
+Film::dci_name () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+
+ stringstream d;
+
+ string fixed_name = to_upper_copy (_name);
+ for (size_t i = 0; i < fixed_name.length(); ++i) {
+ if (fixed_name[i] == ' ') {
+ fixed_name[i] = '-';
+ }
+ }
+
+ /* Spec is that the name part should be maximum 14 characters, as I understand it */
+ if (fixed_name.length() > 14) {
+ fixed_name = fixed_name.substr (0, 14);
+ }
+
+ d << fixed_name << "_";
+
+ if (_dcp_content_type) {
+ d << _dcp_content_type->dci_name() << "_";
+ }
+
+ if (_format) {
+ d << _format->dci_name() << "_";
+ }
+
+ if (!_audio_language.empty ()) {
+ d << _audio_language;
+ if (!_subtitle_language.empty() && _with_subtitles) {
+ d << "-" << _subtitle_language;
+ } else {
+ d << "-XX";
+ }
+
+ d << "_";
+ }
+
+ if (!_territory.empty ()) {
+ d << _territory;
+ if (!_rating.empty ()) {
+ d << "-" << _rating;
+ }
+ d << "_";
+ }
+
+ switch (_audio_streams[_audio_stream].channels()) {
+ case 1:
+ d << "10_";
+ break;
+ case 2:
+ d << "20_";
+ break;
+ case 6:
+ d << "51_";
+ break;
+ case 8:
+ d << "71_";
+ break;
+ }
+
+ d << "2K_";
+
+ if (!_studio.empty ()) {
+ d << _studio << "_";
+ }
+
+ gregorian::date today = gregorian::day_clock::local_day ();
+ d << gregorian::to_iso_string (today) << "_";
+
+ if (!_facility.empty ()) {
+ d << _facility << "_";
+ }
+
+ if (!_package_type.empty ()) {
+ d << _package_type;
+ }
+
+ return d.str ();
+}
+
+/** @return name to give the DCP */
+string
+Film::dcp_name () const
+{
+ if (use_dci_name()) {
+ return dci_name ();
+ }
+
+ return name();
+}
+
+
+void
+Film::set_directory (string d)
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _directory = d;
+ _dirty = true;
+}
+