+ {
+ char buffer[128];
+ gethostname (buffer, sizeof (buffer));
+ log()->log (String::compose ("Starting to make DCP on %1", buffer));
+ }
+
+ if (format() == 0) {
+ throw MissingSettingError ("format");
+ }
+
+ if (content().empty ()) {
+ throw MissingSettingError ("content");
+ }
+
+ if (dcp_content_type() == 0) {
+ throw MissingSettingError ("content type");
+ }
+
+ if (name().empty()) {
+ throw MissingSettingError ("name");
+ }
+
+ shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", dir ("wavs")));
+ o->out_size = format()->dcp_size ();
+ o->padding = format()->dcp_padding (shared_from_this ());
+ o->ratio = format()->ratio_as_float (shared_from_this ());
+ if (dcp_length ()) {
+ o->video_decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
+ o->audio_decode_range = make_pair (
+ video_frames_to_audio_frames (o->video_decode_range.get().first, audio_sample_rate(), frames_per_second()),
+ video_frames_to_audio_frames (o->video_decode_range.get().second, audio_sample_rate(), frames_per_second())
+ );
+
+ }
+ o->decode_subtitles = with_subtitles ();
+ o->decode_video_skip = dcp_frame_rate (frames_per_second()).skip;
+
+ shared_ptr<Job> r;
+
+ if (transcode) {
+ if (dcp_ab()) {
+ r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
+ } else {
+ r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
+ }
+ }
+
+ r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), o, r)));
+ JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), o, r)));
+}
+
+/** Start a job to examine our content file */
+void
+Film::examine_content ()
+{
+ if (_examine_content_job) {
+ return;
+ }
+
+ set_thumbs (vector<SourceFrame> ());
+ boost::filesystem::remove_all (dir ("thumbs"));
+
+ /* This call will recreate the directory */
+ dir ("thumbs");
+
+ _examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr<Job> ()));
+ _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
+ JobManager::instance()->add (_examine_content_job);
+}
+
+void
+Film::examine_content_finished ()
+{
+ _examine_content_job.reset ();
+}
+
+/** @return full paths to any audio files that this Film has */
+vector<string>
+Film::audio_files () const
+{
+ vector<string> f;
+ for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (dir("wavs")); i != boost::filesystem::directory_iterator(); ++i) {
+ f.push_back (i->path().string ());
+ }
+
+ return f;
+}
+
+/** Start a job to send our DCP to the configured TMS */
+void
+Film::send_dcp_to_tms ()
+{
+ shared_ptr<Job> j (new SCPDCPJob (shared_from_this(), shared_ptr<Job> ()));
+ JobManager::instance()->add (j);
+}
+
+/** Count the number of frames that have been encoded for this film.
+ * @return frame count.
+ */
+int
+Film::encoded_frames () const
+{
+ if (format() == 0) {
+ return 0;
+ }
+
+ int N = 0;
+ for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
+ ++N;
+ boost::this_thread::interruption_point ();
+ }
+
+ return N;
+}
+
+/** Return the filename of a subtitle image if one exists for a given thumb index.
+ * @param Thumbnail index.
+ * @return Position of the image within the source frame, and the image filename, if one exists.
+ * Otherwise the filename will be empty.
+ */
+pair<Position, string>
+Film::thumb_subtitle (int n) const
+{
+ string sub_file = thumb_base(n) + ".sub";
+ if (!boost::filesystem::exists (sub_file)) {
+ return pair<Position, string> ();
+ }
+
+ pair<Position, string> sub;
+
+ ifstream f (sub_file.c_str ());
+ multimap<string, string> kv = read_key_value (f);
+ for (map<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
+ if (i->first == "x") {
+ sub.first.x = lexical_cast<int> (i->second);
+ } else if (i->first == "y") {
+ sub.first.y = lexical_cast<int> (i->second);
+ sub.second = String::compose ("%1.sub.png", thumb_base(n));
+ }
+ }
+
+ return sub;
+}
+
+/** Write state to our `metadata' file */
+void
+Film::write_metadata () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+
+ boost::filesystem::create_directories (directory());
+
+ string const m = file ("metadata");
+ ofstream f (m.c_str ());
+ if (!f.good ()) {
+ throw CreateFileError (m);
+ }
+
+ /* User stuff */
+ f << "name " << _name << "\n";
+ f << "use_dci_name " << _use_dci_name << "\n";
+ f << "content " << _content << "\n";
+ if (_dcp_content_type) {
+ f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
+ }
+ if (_format) {
+ f << "format " << _format->as_metadata () << "\n";
+ }
+ f << "left_crop " << _crop.left << "\n";
+ f << "right_crop " << _crop.right << "\n";
+ f << "top_crop " << _crop.top << "\n";
+ f << "bottom_crop " << _crop.bottom << "\n";
+ for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+ f << "filter " << (*i)->id () << "\n";
+ }
+ f << "scaler " << _scaler->id () << "\n";
+ f << "dcp_trim_start " << _dcp_trim_start << "\n";
+ f << "dcp_trim_end " << _dcp_trim_end << "\n";
+ f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
+ f << "selected_audio_stream " << _audio_stream << "\n";
+ f << "audio_gain " << _audio_gain << "\n";
+ f << "audio_delay " << _audio_delay << "\n";
+ f << "still_duration " << _still_duration << "\n";
+ f << "selected_subtitle_stream " << _subtitle_stream << "\n";
+ f << "with_subtitles " << _with_subtitles << "\n";
+ f << "subtitle_offset " << _subtitle_offset << "\n";
+ f << "subtitle_scale " << _subtitle_scale << "\n";
+ f << "audio_language " << _audio_language << "\n";
+ f << "subtitle_language " << _subtitle_language << "\n";
+ f << "territory " << _territory << "\n";
+ f << "rating " << _rating << "\n";
+ f << "studio " << _studio << "\n";
+ f << "facility " << _facility << "\n";
+ f << "package_type " << _package_type << "\n";
+
+ /* Cached stuff; this is information about our content; we could
+ look it up each time, but that's slow.
+ */
+ for (vector<SourceFrame>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) {
+ f << "thumb " << *i << "\n";
+ }
+ f << "width " << _size.width << "\n";
+ f << "height " << _size.height << "\n";
+ f << "length " << _length.get_value_or(0) << "\n";
+ f << "audio_sample_rate " << _audio_sample_rate << "\n";
+ f << "content_digest " << _content_digest << "\n";
+ f << "has_subtitles " << _has_subtitles << "\n";
+
+ for (vector<AudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
+ f << "audio_stream " << i->to_string () << "\n";
+ }
+
+ for (vector<SubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
+ f << "subtitle_stream " << i->to_string () << "\n";
+ }
+
+ f << "frames_per_second " << _frames_per_second << "\n";
+
+ _dirty = false;
+}
+
+/** Read state from our metadata file */