2 Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include <boost/filesystem.hpp>
29 #include <boost/algorithm/string.hpp>
30 #include <boost/lexical_cast.hpp>
31 #include <boost/date_time.hpp>
36 #include "transcoder.h"
38 #include "job_manager.h"
39 #include "ab_transcode_job.h"
40 #include "transcode_job.h"
41 #include "scp_dcp_job.h"
44 #include "exceptions.h"
45 #include "examine_content_job.h"
47 #include "decoder_factory.h"
50 #include "ui_signaller.h"
51 #include "video_decoder.h"
52 #include "audio_decoder.h"
53 #include "sndfile_decoder.h"
54 #include "analyse_audio_job.h"
60 using std::stringstream;
71 using boost::shared_ptr;
72 using boost::lexical_cast;
73 using boost::to_upper_copy;
74 using boost::ends_with;
75 using boost::starts_with;
76 using boost::optional;
79 int const Film::state_version = 4;
81 /** Construct a Film object in a given directory, reading any metadata
82 * file that exists in that directory. An exception will be thrown if
83 * must_exist is true and the specified directory does not exist.
85 * @param d Film directory.
86 * @param must_exist true to throw an exception if does not exist.
89 Film::Film (string d, bool must_exist)
90 : _use_dci_name (true)
91 , _trust_content_header (true)
92 , _dcp_content_type (0)
94 , _scaler (Scaler::from_id ("bicubic"))
100 , _with_subtitles (false)
101 , _subtitle_offset (0)
102 , _subtitle_scale (1)
104 , _j2k_bandwidth (200000000)
105 , _dci_metadata (Config::instance()->default_dci_metadata ())
106 , _dcp_frame_rate (0)
109 set_dci_date_today ();
111 /* Make state.directory a complete path without ..s (where possible)
112 (Code swiped from Adam Bowen on stackoverflow)
115 boost::filesystem::path p (boost::filesystem::system_complete (d));
116 boost::filesystem::path result;
117 for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
119 if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
122 result = result.parent_path ();
124 } else if (*i != ".") {
129 set_directory (result.string ());
131 if (!boost::filesystem::exists (directory())) {
133 throw OpenFileError (directory());
135 boost::filesystem::create_directory (directory());
143 _log.reset (new FileLog (file ("log")));
146 Film::Film (Film const & o)
147 : boost::enable_shared_from_this<Film> (o)
148 /* note: the copied film shares the original's log */
150 , _directory (o._directory)
152 , _use_dci_name (o._use_dci_name)
153 , _trust_content_header (o._trust_content_header)
154 , _dcp_content_type (o._dcp_content_type)
155 , _format (o._format)
157 , _filters (o._filters)
158 , _scaler (o._scaler)
159 , _trim_start (o._trim_start)
160 , _trim_end (o._trim_end)
161 , _dcp_ab (o._dcp_ab)
162 , _audio_gain (o._audio_gain)
163 , _audio_delay (o._audio_delay)
164 , _with_subtitles (o._with_subtitles)
165 , _subtitle_offset (o._subtitle_offset)
166 , _subtitle_scale (o._subtitle_scale)
167 , _colour_lut (o._colour_lut)
168 , _j2k_bandwidth (o._j2k_bandwidth)
169 , _dci_metadata (o._dci_metadata)
170 , _dci_date (o._dci_date)
171 , _dcp_frame_rate (o._dcp_frame_rate)
183 Film::video_state_identifier () const
191 pair<string, string> f = Filter::ffmpeg_strings (filters());
195 << "_" << content_digest()
196 << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
197 << "_" << _dcp_frame_rate
198 << "_" << f.first << "_" << f.second
199 << "_" << scaler()->id()
200 << "_" << j2k_bandwidth()
201 << "_" << boost::lexical_cast<int> (colour_lut());
204 pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
205 s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
212 /** @return The path to the directory to write video frame info files to */
214 Film::info_dir () const
216 boost::filesystem::path p;
218 p /= video_state_identifier ();
219 return dir (p.string());
223 Film::video_mxf_dir () const
225 boost::filesystem::path p;
226 return dir ("video");
230 Film::video_mxf_filename () const
232 return video_state_identifier() + ".mxf";
236 Film::audio_analysis_path () const
238 boost::filesystem::path p;
240 p /= "XXX";//content_digest();
241 return file (p.string ());
244 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
248 set_dci_date_today ();
250 if (dcp_name().find ("/") != string::npos) {
251 throw BadSettingError (_("name"), _("cannot contain slashes"));
254 log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
258 gethostname (buffer, sizeof (buffer));
259 log()->log (String::compose ("Starting to make DCP on %1", buffer));
262 // log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
264 // log()->log (String::compose ("Content length %1", length().get()));
266 // log()->log (String::compose ("Content digest %1", content_digest()));
267 // log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
268 log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
269 log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
270 #ifdef DVDOMATIC_DEBUG
271 log()->log ("DVD-o-matic built in debug mode.");
273 log()->log ("DVD-o-matic built in optimised mode.");
276 log()->log ("libdcp built in debug mode.");
278 log()->log ("libdcp built in optimised mode.");
280 pair<string, int> const c = cpu_info ();
281 log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
284 throw MissingSettingError (_("format"));
287 if (content().empty ()) {
288 throw MissingSettingError (_("content"));
291 if (dcp_content_type() == 0) {
292 throw MissingSettingError (_("content type"));
295 if (name().empty()) {
296 throw MissingSettingError (_("name"));
300 od.decode_subtitles = with_subtitles ();
305 r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
307 r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
311 /** Start a job to analyse the audio of our content file */
313 Film::analyse_audio ()
315 if (_analyse_audio_job) {
319 _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
320 _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
321 JobManager::instance()->add (_analyse_audio_job);
324 /** Start a job to examine a piece of content */
326 Film::examine_content (shared_ptr<Content> c)
328 shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c, trust_content_header ()));
329 j->Finished.connect (bind (&Film::examine_content_finished, this));
330 JobManager::instance()->add (j);
334 Film::analyse_audio_finished ()
338 if (_analyse_audio_job->finished_ok ()) {
339 AudioAnalysisSucceeded ();
342 _analyse_audio_job.reset ();
346 Film::examine_content_finished ()
351 /** Start a job to send our DCP to the configured TMS */
353 Film::send_dcp_to_tms ()
355 shared_ptr<Job> j (new SCPDCPJob (shared_from_this()));
356 JobManager::instance()->add (j);
359 /** Count the number of frames that have been encoded for this film.
360 * @return frame count.
363 Film::encoded_frames () const
370 for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
372 boost::this_thread::interruption_point ();
378 /** Write state to our `metadata' file */
380 Film::write_metadata () const
382 boost::mutex::scoped_lock lm (_state_mutex);
384 boost::filesystem::create_directories (directory());
386 string const m = file ("metadata");
387 ofstream f (m.c_str ());
389 throw CreateFileError (m);
392 f << "version " << state_version << endl;
395 f << "name " << _name << endl;
396 f << "use_dci_name " << _use_dci_name << endl;
397 // f << "content " << _content << endl;
398 f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
399 if (_dcp_content_type) {
400 f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
403 f << "format " << _format->as_metadata () << endl;
405 f << "left_crop " << _crop.left << endl;
406 f << "right_crop " << _crop.right << endl;
407 f << "top_crop " << _crop.top << endl;
408 f << "bottom_crop " << _crop.bottom << endl;
409 for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
410 f << "filter " << (*i)->id () << endl;
412 f << "scaler " << _scaler->id () << endl;
413 f << "trim_start " << _trim_start << endl;
414 f << "trim_end " << _trim_end << endl;
415 f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
416 // if (_content_audio_stream) {
417 // f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
419 // for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
420 // f << "external_audio " << *i << endl;
422 // f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
423 f << "audio_gain " << _audio_gain << endl;
424 f << "audio_delay " << _audio_delay << endl;
425 // f << "still_duration " << _still_duration << endl;
426 // if (_subtitle_stream) {
427 // f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
429 f << "with_subtitles " << _with_subtitles << endl;
430 f << "subtitle_offset " << _subtitle_offset << endl;
431 f << "subtitle_scale " << _subtitle_scale << endl;
432 f << "colour_lut " << _colour_lut << endl;
433 f << "j2k_bandwidth " << _j2k_bandwidth << endl;
434 _dci_metadata.write (f);
435 f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
436 f << "dcp_frame_rate " << _dcp_frame_rate << endl;
437 // f << "width " << _size.width << endl;
438 // f << "height " << _size.height << endl;
439 // f << "length " << _length.get_value_or(0) << endl;
440 // f << "content_digest " << _content_digest << endl;
442 // for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
443 // f << "content_audio_stream " << (*i)->to_string () << endl;
446 // f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
448 // for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
449 // f << "subtitle_stream " << (*i)->to_string () << endl;
452 // f << "source_frame_rate " << _source_frame_rate << endl;
457 /** Read state from our metadata file */
459 Film::read_metadata ()
461 boost::mutex::scoped_lock lm (_state_mutex);
463 // _external_audio.clear ();
464 // _content_audio_streams.clear ();
465 // _subtitle_streams.clear ();
467 boost::optional<int> version;
469 /* Backward compatibility things */
470 boost::optional<int> audio_sample_rate;
471 boost::optional<int> audio_stream_index;
472 boost::optional<int> subtitle_stream_index;
474 ifstream f (file ("metadata").c_str());
476 throw OpenFileError (file ("metadata"));
479 multimap<string, string> kv = read_key_value (f);
481 /* We need version before anything else */
482 multimap<string, string>::iterator v = kv.find ("version");
483 if (v != kv.end ()) {
484 version = atoi (v->second.c_str());
487 for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
488 string const k = i->first;
489 string const v = i->second;
491 if (k == "audio_sample_rate") {
492 audio_sample_rate = atoi (v.c_str());
495 /* User-specified stuff */
498 } else if (k == "use_dci_name") {
499 _use_dci_name = (v == "1");
500 } else if (k == "content") {
502 } else if (k == "trust_content_header") {
503 _trust_content_header = (v == "1");
504 } else if (k == "dcp_content_type") {
506 _dcp_content_type = DCPContentType::from_pretty_name (v);
508 _dcp_content_type = DCPContentType::from_dci_name (v);
510 } else if (k == "format") {
511 _format = Format::from_metadata (v);
512 } else if (k == "left_crop") {
513 _crop.left = atoi (v.c_str ());
514 } else if (k == "right_crop") {
515 _crop.right = atoi (v.c_str ());
516 } else if (k == "top_crop") {
517 _crop.top = atoi (v.c_str ());
518 } else if (k == "bottom_crop") {
519 _crop.bottom = atoi (v.c_str ());
520 } else if (k == "filter") {
521 _filters.push_back (Filter::from_id (v));
522 } else if (k == "scaler") {
523 _scaler = Scaler::from_id (v);
524 } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
525 _trim_start = atoi (v.c_str ());
526 } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
527 _trim_end = atoi (v.c_str ());
528 } else if (k == "dcp_ab") {
529 _dcp_ab = (v == "1");
530 } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
532 audio_stream_index = atoi (v.c_str ());
534 // _content_audio_stream = audio_stream_factory (v, version);
536 } else if (k == "external_audio") {
537 // _external_audio.push_back (v);
538 } else if (k == "use_content_audio") {
539 // _use_content_audio = (v == "1");
540 } else if (k == "audio_gain") {
541 _audio_gain = atof (v.c_str ());
542 } else if (k == "audio_delay") {
543 _audio_delay = atoi (v.c_str ());
544 } else if (k == "still_duration") {
545 // _still_duration = atoi (v.c_str ());
546 } else if (k == "selected_subtitle_stream") {
548 subtitle_stream_index = atoi (v.c_str ());
550 // _subtitle_stream = subtitle_stream_factory (v, version);
552 } else if (k == "with_subtitles") {
553 _with_subtitles = (v == "1");
554 } else if (k == "subtitle_offset") {
555 _subtitle_offset = atoi (v.c_str ());
556 } else if (k == "subtitle_scale") {
557 _subtitle_scale = atof (v.c_str ());
558 } else if (k == "colour_lut") {
559 _colour_lut = atoi (v.c_str ());
560 } else if (k == "j2k_bandwidth") {
561 _j2k_bandwidth = atoi (v.c_str ());
562 } else if (k == "dci_date") {
563 _dci_date = boost::gregorian::from_undelimited_string (v);
564 } else if (k == "dcp_frame_rate") {
565 _dcp_frame_rate = atoi (v.c_str ());
568 _dci_metadata.read (k, v);
572 // _size.width = atoi (v.c_str ());
573 } else if (k == "height") {
574 // _size.height = atoi (v.c_str ());
575 } else if (k == "length") {
576 int const vv = atoi (v.c_str ());
580 } else if (k == "content_digest") {
581 // _content_digest = v;
582 } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
583 // _content_audio_streams.push_back (audio_stream_factory (v, version));
584 } else if (k == "external_audio_stream") {
585 // _sndfile_stream = audio_stream_factory (v, version);
586 } else if (k == "subtitle_stream") {
587 // _subtitle_streams.push_back (subtitle_stream_factory (v, version));
588 } else if (k == "source_frame_rate") {
589 // _source_frame_rate = atof (v.c_str ());
590 } else if (version < 4 && k == "frames_per_second") {
591 // _source_frame_rate = atof (v.c_str ());
592 /* Fill in what would have been used for DCP frame rate by the older version */
593 // _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
601 Film::cropped_size (libdcp::Size s) const
603 boost::mutex::scoped_lock lm (_state_mutex);
604 s.width -= _crop.left + _crop.right;
605 s.height -= _crop.top + _crop.bottom;
609 /** Given a directory name, return its full path within the Film's directory.
610 * The directory (and its parents) will be created if they do not exist.
613 Film::dir (string d) const
615 boost::mutex::scoped_lock lm (_directory_mutex);
617 boost::filesystem::path p;
621 boost::filesystem::create_directories (p);
626 /** Given a file or directory name, return its full path within the Film's directory.
627 * _directory_mutex must not be locked on entry.
628 * Any required parent directories will be created.
631 Film::file (string f) const
633 boost::mutex::scoped_lock lm (_directory_mutex);
635 boost::filesystem::path p;
639 boost::filesystem::create_directories (p.parent_path ());
644 /** @return The sampling rate that we will resample the audio to */
646 Film::target_audio_sample_rate () const
648 /* XXX: how often is this method called? */
650 boost::shared_ptr<Playlist> p = playlist ();
651 if (p->has_audio ()) {
655 /* Resample to a DCI-approved sample rate */
656 double t = dcp_audio_sample_rate (p->audio_frame_rate());
658 FrameRateConversion frc (p->video_frame_rate(), dcp_frame_rate());
660 /* Compensate if the DCP is being run at a different frame rate
661 to the source; that is, if the video is run such that it will
662 look different in the DCP compared to the source (slower or faster).
663 skip/repeat doesn't come into effect here.
666 if (frc.change_speed) {
667 t *= p->video_frame_rate() * frc.factor() / dcp_frame_rate();
673 /** @return a DCI-compliant name for a DCP of this film */
675 Film::dci_name (bool if_created_now) const
679 string fixed_name = to_upper_copy (name());
680 for (size_t i = 0; i < fixed_name.length(); ++i) {
681 if (fixed_name[i] == ' ') {
686 /* Spec is that the name part should be maximum 14 characters, as I understand it */
687 if (fixed_name.length() > 14) {
688 fixed_name = fixed_name.substr (0, 14);
693 if (dcp_content_type()) {
694 d << "_" << dcp_content_type()->dci_name();
698 d << "_" << format()->dci_name();
701 DCIMetadata const dm = dci_metadata ();
703 if (!dm.audio_language.empty ()) {
704 d << "_" << dm.audio_language;
705 if (!dm.subtitle_language.empty()) {
706 d << "-" << dm.subtitle_language;
712 if (!dm.territory.empty ()) {
713 d << "_" << dm.territory;
714 if (!dm.rating.empty ()) {
715 d << "-" << dm.rating;
737 if (!dm.studio.empty ()) {
738 d << "_" << dm.studio;
741 if (if_created_now) {
742 d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
744 d << "_" << boost::gregorian::to_iso_string (_dci_date);
747 if (!dm.facility.empty ()) {
748 d << "_" << dm.facility;
751 if (!dm.package_type.empty ()) {
752 d << "_" << dm.package_type;
758 /** @return name to give the DCP */
760 Film::dcp_name (bool if_created_now) const
762 if (use_dci_name()) {
763 return dci_name (if_created_now);
771 Film::set_directory (string d)
773 boost::mutex::scoped_lock lm (_state_mutex);
779 Film::set_name (string n)
782 boost::mutex::scoped_lock lm (_state_mutex);
785 signal_changed (NAME);
789 Film::set_use_dci_name (bool u)
792 boost::mutex::scoped_lock lm (_state_mutex);
795 signal_changed (USE_DCI_NAME);
799 Film::set_trust_content_header (bool t)
802 boost::mutex::scoped_lock lm (_state_mutex);
803 _trust_content_header = t;
806 signal_changed (TRUST_CONTENT_HEADER);
808 if (!_trust_content_header && !content().empty()) {
809 /* We just said that we don't trust the content's header */
811 // examine_content ();
816 Film::set_dcp_content_type (DCPContentType const * t)
819 boost::mutex::scoped_lock lm (_state_mutex);
820 _dcp_content_type = t;
822 signal_changed (DCP_CONTENT_TYPE);
826 Film::set_format (Format const * f)
829 boost::mutex::scoped_lock lm (_state_mutex);
832 signal_changed (FORMAT);
836 Film::set_crop (Crop c)
839 boost::mutex::scoped_lock lm (_state_mutex);
842 signal_changed (CROP);
846 Film::set_left_crop (int c)
849 boost::mutex::scoped_lock lm (_state_mutex);
851 if (_crop.left == c) {
857 signal_changed (CROP);
861 Film::set_right_crop (int c)
864 boost::mutex::scoped_lock lm (_state_mutex);
865 if (_crop.right == c) {
871 signal_changed (CROP);
875 Film::set_top_crop (int c)
878 boost::mutex::scoped_lock lm (_state_mutex);
879 if (_crop.top == c) {
885 signal_changed (CROP);
889 Film::set_bottom_crop (int c)
892 boost::mutex::scoped_lock lm (_state_mutex);
893 if (_crop.bottom == c) {
899 signal_changed (CROP);
903 Film::set_filters (vector<Filter const *> f)
906 boost::mutex::scoped_lock lm (_state_mutex);
909 signal_changed (FILTERS);
913 Film::set_scaler (Scaler const * s)
916 boost::mutex::scoped_lock lm (_state_mutex);
919 signal_changed (SCALER);
923 Film::set_trim_start (int t)
926 boost::mutex::scoped_lock lm (_state_mutex);
929 signal_changed (TRIM_START);
933 Film::set_trim_end (int t)
936 boost::mutex::scoped_lock lm (_state_mutex);
939 signal_changed (TRIM_END);
943 Film::set_dcp_ab (bool a)
946 boost::mutex::scoped_lock lm (_state_mutex);
949 signal_changed (DCP_AB);
953 Film::set_audio_gain (float g)
956 boost::mutex::scoped_lock lm (_state_mutex);
959 signal_changed (AUDIO_GAIN);
963 Film::set_audio_delay (int d)
966 boost::mutex::scoped_lock lm (_state_mutex);
969 signal_changed (AUDIO_DELAY);
973 Film::set_with_subtitles (bool w)
976 boost::mutex::scoped_lock lm (_state_mutex);
979 signal_changed (WITH_SUBTITLES);
983 Film::set_subtitle_offset (int o)
986 boost::mutex::scoped_lock lm (_state_mutex);
987 _subtitle_offset = o;
989 signal_changed (SUBTITLE_OFFSET);
993 Film::set_subtitle_scale (float s)
996 boost::mutex::scoped_lock lm (_state_mutex);
999 signal_changed (SUBTITLE_SCALE);
1003 Film::set_colour_lut (int i)
1006 boost::mutex::scoped_lock lm (_state_mutex);
1009 signal_changed (COLOUR_LUT);
1013 Film::set_j2k_bandwidth (int b)
1016 boost::mutex::scoped_lock lm (_state_mutex);
1019 signal_changed (J2K_BANDWIDTH);
1023 Film::set_dci_metadata (DCIMetadata m)
1026 boost::mutex::scoped_lock lm (_state_mutex);
1029 signal_changed (DCI_METADATA);
1034 Film::set_dcp_frame_rate (int f)
1037 boost::mutex::scoped_lock lm (_state_mutex);
1038 _dcp_frame_rate = f;
1040 signal_changed (DCP_FRAME_RATE);
1044 Film::signal_changed (Property p)
1047 boost::mutex::scoped_lock lm (_state_mutex);
1052 ui_signaller->emit (boost::bind (boost::ref (Changed), p));
1057 Film::set_dci_date_today ()
1059 _dci_date = boost::gregorian::day_clock::local_day ();
1063 Film::info_path (int f) const
1065 boost::filesystem::path p;
1070 s << setfill('0') << f << ".md5";
1074 /* info_dir() will already have added any initial bit of the path,
1075 so don't call file() on this.
1081 Film::j2c_path (int f, bool t) const
1083 boost::filesystem::path p;
1085 p /= video_state_identifier ();
1089 s << setfill('0') << f << ".j2c";
1096 return file (p.string ());
1099 /** Make an educated guess as to whether we have a complete DCP
1101 * @return true if we do.
1105 Film::have_dcp () const
1108 libdcp::DCP dcp (dir (dcp_name()));
1117 shared_ptr<Playlist>
1118 Film::playlist () const
1120 boost::mutex::scoped_lock lm (_state_mutex);
1121 return shared_ptr<Playlist> (new Playlist (shared_from_this (), _content));
1125 Film::add_content (shared_ptr<Content> c)
1128 boost::mutex::scoped_lock lm (_state_mutex);
1129 _content.push_back (c);
1132 signal_changed (CONTENT);
1134 examine_content (c);