ef67a270420ab3b9e62c5c5a5d59e6bcc9cd400c
[dcpomatic.git] / src / lib / film.cc
1 /* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
2
3 /*
4     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 */
21
22 #include <stdexcept>
23 #include <iostream>
24 #include <algorithm>
25 #include <fstream>
26 #include <cstdlib>
27 #include <sstream>
28 #include <iomanip>
29 #include <unistd.h>
30 #include <boost/filesystem.hpp>
31 #include <boost/algorithm/string.hpp>
32 #include <boost/lexical_cast.hpp>
33 #include <boost/date_time.hpp>
34 #include <libxml++/libxml++.h>
35 #include <libcxml/cxml.h>
36 #include "film.h"
37 #include "format.h"
38 #include "job.h"
39 #include "filter.h"
40 #include "util.h"
41 #include "job_manager.h"
42 #include "ab_transcode_job.h"
43 #include "transcode_job.h"
44 #include "scp_dcp_job.h"
45 #include "log.h"
46 #include "exceptions.h"
47 #include "examine_content_job.h"
48 #include "scaler.h"
49 #include "config.h"
50 #include "version.h"
51 #include "ui_signaller.h"
52 #include "analyse_audio_job.h"
53 #include "playlist.h"
54 #include "player.h"
55 #include "ffmpeg_content.h"
56 #include "imagemagick_content.h"
57 #include "sndfile_content.h"
58 #include "dcp_content_type.h"
59
60 #include "i18n.h"
61
62 using std::string;
63 using std::stringstream;
64 using std::multimap;
65 using std::pair;
66 using std::map;
67 using std::vector;
68 using std::ifstream;
69 using std::ofstream;
70 using std::setfill;
71 using std::min;
72 using std::make_pair;
73 using std::endl;
74 using std::cout;
75 using std::list;
76 using boost::shared_ptr;
77 using boost::lexical_cast;
78 using boost::to_upper_copy;
79 using boost::ends_with;
80 using boost::starts_with;
81 using boost::optional;
82 using libdcp::Size;
83
84 int const Film::state_version = 4;
85
86 /** Construct a Film object in a given directory, reading any metadata
87  *  file that exists in that directory.  An exception will be thrown if
88  *  must_exist is true and the specified directory does not exist.
89  *
90  *  @param d Film directory.
91  *  @param must_exist true to throw an exception if does not exist.
92  */
93
94 Film::Film (string d, bool must_exist)
95         : _playlist (new Playlist)
96         , _use_dci_name (true)
97         , _dcp_content_type (Config::instance()->default_dcp_content_type ())
98         , _format (Config::instance()->default_format ())
99         , _scaler (Scaler::from_id ("bicubic"))
100         , _ab (false)
101         , _audio_gain (0)
102         , _audio_delay (0)
103         , _with_subtitles (false)
104         , _subtitle_offset (0)
105         , _subtitle_scale (1)
106         , _colour_lut (0)
107         , _j2k_bandwidth (200000000)
108         , _dci_metadata (Config::instance()->default_dci_metadata ())
109         , _dcp_video_frame_rate (0)
110         , _dirty (false)
111 {
112         set_dci_date_today ();
113
114         _playlist->Changed.connect (bind (&Film::playlist_changed, this));
115         _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
116         
117         /* Make state.directory a complete path without ..s (where possible)
118            (Code swiped from Adam Bowen on stackoverflow)
119         */
120         
121         boost::filesystem::path p (boost::filesystem::system_complete (d));
122         boost::filesystem::path result;
123         for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
124                 if (*i == "..") {
125                         if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
126                                 result /= *i;
127                         } else {
128                                 result = result.parent_path ();
129                         }
130                 } else if (*i != ".") {
131                         result /= *i;
132                 }
133         }
134
135         set_directory (result.string ());
136         
137         if (!boost::filesystem::exists (directory())) {
138                 if (must_exist) {
139                         throw OpenFileError (directory());
140                 } else {
141                         boost::filesystem::create_directory (directory());
142                 }
143         }
144
145         if (must_exist) {
146                 read_metadata ();
147         } else {
148                 write_metadata ();
149         }
150
151         _log.reset (new FileLog (file ("log")));
152 }
153
154 Film::Film (Film const & o)
155         : boost::enable_shared_from_this<Film> (o)
156         /* note: the copied film shares the original's log */
157         , _log               (o._log)
158         , _playlist          (new Playlist (o._playlist))
159         , _directory         (o._directory)
160         , _name              (o._name)
161         , _use_dci_name      (o._use_dci_name)
162         , _dcp_content_type  (o._dcp_content_type)
163         , _format            (o._format)
164         , _crop              (o._crop)
165         , _filters           (o._filters)
166         , _scaler            (o._scaler)
167         , _ab                (o._ab)
168         , _audio_gain        (o._audio_gain)
169         , _audio_delay       (o._audio_delay)
170         , _with_subtitles    (o._with_subtitles)
171         , _subtitle_offset   (o._subtitle_offset)
172         , _subtitle_scale    (o._subtitle_scale)
173         , _colour_lut        (o._colour_lut)
174         , _j2k_bandwidth     (o._j2k_bandwidth)
175         , _dci_metadata      (o._dci_metadata)
176         , _dcp_video_frame_rate (o._dcp_video_frame_rate)
177         , _dci_date          (o._dci_date)
178         , _dirty             (o._dirty)
179 {
180         _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
181 }
182
183 string
184 Film::video_state_identifier () const
185 {
186         assert (format ());
187         LocaleGuard lg;
188
189         pair<string, string> f = Filter::ffmpeg_strings (filters());
190
191         stringstream s;
192         s << format()->id()
193           << "_" << _playlist->video_digest()
194           << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
195           << "_" << _dcp_video_frame_rate
196           << "_" << f.first << "_" << f.second
197           << "_" << scaler()->id()
198           << "_" << j2k_bandwidth()
199           << "_" << boost::lexical_cast<int> (colour_lut());
200
201         if (ab()) {
202                 pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
203                 s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
204         }
205
206         return s.str ();
207 }
208           
209 /** @return The path to the directory to write video frame info files to */
210 string
211 Film::info_dir () const
212 {
213         boost::filesystem::path p;
214         p /= "info";
215         p /= video_state_identifier ();
216         return dir (p.string());
217 }
218
219 string
220 Film::internal_video_mxf_dir () const
221 {
222         boost::filesystem::path p;
223         return dir ("video");
224 }
225
226 string
227 Film::internal_video_mxf_filename () const
228 {
229         return video_state_identifier() + ".mxf";
230 }
231
232 string
233 Film::dcp_video_mxf_filename () const
234 {
235         return filename_safe_name() + "_video.mxf";
236 }
237
238 string
239 Film::dcp_audio_mxf_filename () const
240 {
241         return filename_safe_name() + "_audio.mxf";
242 }
243
244 string
245 Film::filename_safe_name () const
246 {
247         string const n = name ();
248         string o;
249         for (size_t i = 0; i < n.length(); ++i) {
250                 if (isalnum (n[i])) {
251                         o += n[i];
252                 } else {
253                         o += "_";
254                 }
255         }
256
257         return o;
258 }
259
260 string
261 Film::audio_analysis_path () const
262 {
263         boost::filesystem::path p;
264         p /= "analysis";
265         p /= _playlist->audio_digest();
266         return file (p.string ());
267 }
268
269 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
270 void
271 Film::make_dcp ()
272 {
273         set_dci_date_today ();
274         
275         if (dcp_name().find ("/") != string::npos) {
276                 throw BadSettingError (_("name"), _("cannot contain slashes"));
277         }
278         
279         log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
280
281         {
282                 char buffer[128];
283                 gethostname (buffer, sizeof (buffer));
284                 log()->log (String::compose ("Starting to make DCP on %1", buffer));
285         }
286         
287 //      log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
288 //      if (length()) {
289 //              log()->log (String::compose ("Content length %1", length().get()));
290 //      }
291 //      log()->log (String::compose ("Content digest %1", content_digest()));
292 //      log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
293         log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
294         log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
295 #ifdef DCPOMATIC_DEBUG
296         log()->log ("DCP-o-matic built in debug mode.");
297 #else
298         log()->log ("DCP-o-matic built in optimised mode.");
299 #endif
300 #ifdef LIBDCP_DEBUG
301         log()->log ("libdcp built in debug mode.");
302 #else
303         log()->log ("libdcp built in optimised mode.");
304 #endif
305         pair<string, int> const c = cpu_info ();
306         log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
307         
308         if (format() == 0) {
309                 throw MissingSettingError (_("format"));
310         }
311
312         if (_playlist->content().empty ()) {
313                 throw StringError (_("You must add some content to the DCP before creating it"));
314         }
315
316         if (dcp_content_type() == 0) {
317                 throw MissingSettingError (_("content type"));
318         }
319
320         if (name().empty()) {
321                 throw MissingSettingError (_("name"));
322         }
323
324         shared_ptr<Job> r;
325
326         if (ab()) {
327                 r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
328         } else {
329                 r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
330         }
331 }
332
333 /** Start a job to analyse the audio in our Playlist */
334 void
335 Film::analyse_audio ()
336 {
337         if (_analyse_audio_job) {
338                 return;
339         }
340
341         _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
342         _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
343         JobManager::instance()->add (_analyse_audio_job);
344 }
345
346 /** Start a job to examine a piece of content */
347 void
348 Film::examine_content (shared_ptr<Content> c)
349 {
350         shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
351         JobManager::instance()->add (j);
352 }
353
354 void
355 Film::analyse_audio_finished ()
356 {
357         ensure_ui_thread ();
358
359         if (_analyse_audio_job->finished_ok ()) {
360                 AudioAnalysisSucceeded ();
361         }
362         
363         _analyse_audio_job.reset ();
364 }
365
366 /** Start a job to send our DCP to the configured TMS */
367 void
368 Film::send_dcp_to_tms ()
369 {
370         shared_ptr<Job> j (new SCPDCPJob (shared_from_this()));
371         JobManager::instance()->add (j);
372 }
373
374 /** Count the number of frames that have been encoded for this film.
375  *  @return frame count.
376  */
377 int
378 Film::encoded_frames () const
379 {
380         if (format() == 0) {
381                 return 0;
382         }
383
384         int N = 0;
385         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
386                 ++N;
387                 boost::this_thread::interruption_point ();
388         }
389
390         return N;
391 }
392
393 /** Write state to our `metadata' file */
394 void
395 Film::write_metadata () const
396 {
397         boost::mutex::scoped_lock lm (_state_mutex);
398         LocaleGuard lg;
399
400         boost::filesystem::create_directories (directory());
401
402         xmlpp::Document doc;
403         xmlpp::Element* root = doc.create_root_node ("Metadata");
404
405         root->add_child("Version")->add_child_text (boost::lexical_cast<string> (state_version));
406         root->add_child("Name")->add_child_text (_name);
407         root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
408
409         if (_dcp_content_type) {
410                 root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
411         }
412
413         if (_format) {
414                 root->add_child("Format")->add_child_text (_format->id ());
415         }
416
417         root->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
418         root->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
419         root->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
420         root->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
421
422         for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
423                 root->add_child("Filter")->add_child_text ((*i)->id ());
424         }
425         
426         root->add_child("Scaler")->add_child_text (_scaler->id ());
427         root->add_child("AB")->add_child_text (_ab ? "1" : "0");
428         root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain));
429         root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay));
430         root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
431         root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset));
432         root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale));
433         root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut));
434         root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth));
435         _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
436         root->add_child("DCPVideoFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_video_frame_rate));
437         root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
438         _playlist->as_xml (root->add_child ("Playlist"));
439
440         doc.write_to_file_formatted (file ("metadata.xml"));
441         
442         _dirty = false;
443 }
444
445 /** Read state from our metadata file */
446 void
447 Film::read_metadata ()
448 {
449         boost::mutex::scoped_lock lm (_state_mutex);
450         LocaleGuard lg;
451
452         if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
453                 throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
454         }
455
456         cxml::File f (file ("metadata.xml"), "Metadata");
457         
458         _name = f.string_child ("Name");
459         _use_dci_name = f.bool_child ("UseDCIName");
460
461         {
462                 optional<string> c = f.optional_string_child ("DCPContentType");
463                 if (c) {
464                         _dcp_content_type = DCPContentType::from_dci_name (c.get ());
465                 }
466         }
467
468         {
469                 optional<string> c = f.optional_string_child ("Format");
470                 if (c) {
471                         _format = Format::from_id (c.get ());
472                 }
473         }
474
475         _crop.left = f.number_child<int> ("LeftCrop");
476         _crop.right = f.number_child<int> ("RightCrop");
477         _crop.top = f.number_child<int> ("TopCrop");
478         _crop.bottom = f.number_child<int> ("BottomCrop");
479
480         {
481                 list<shared_ptr<cxml::Node> > c = f.node_children ("Filter");
482                 for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
483                         _filters.push_back (Filter::from_id ((*i)->content ()));
484                 }
485         }
486
487         _scaler = Scaler::from_id (f.string_child ("Scaler"));
488         _ab = f.bool_child ("AB");
489         _audio_gain = f.number_child<float> ("AudioGain");
490         _audio_delay = f.number_child<int> ("AudioDelay");
491         _with_subtitles = f.bool_child ("WithSubtitles");
492         _subtitle_offset = f.number_child<float> ("SubtitleOffset");
493         _subtitle_scale = f.number_child<float> ("SubtitleScale");
494         _colour_lut = f.number_child<int> ("ColourLUT");
495         _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
496         _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
497         _dcp_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate");
498         _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
499
500         _playlist->set_from_xml (f.node_child ("Playlist"));
501
502         _dirty = false;
503 }
504
505 libdcp::Size
506 Film::cropped_size (libdcp::Size s) const
507 {
508         boost::mutex::scoped_lock lm (_state_mutex);
509         s.width -= _crop.left + _crop.right;
510         s.height -= _crop.top + _crop.bottom;
511         return s;
512 }
513
514 /** Given a directory name, return its full path within the Film's directory.
515  *  The directory (and its parents) will be created if they do not exist.
516  */
517 string
518 Film::dir (string d) const
519 {
520         boost::mutex::scoped_lock lm (_directory_mutex);
521         
522         boost::filesystem::path p;
523         p /= _directory;
524         p /= d;
525         
526         boost::filesystem::create_directories (p);
527         
528         return p.string ();
529 }
530
531 /** Given a file or directory name, return its full path within the Film's directory.
532  *  _directory_mutex must not be locked on entry.
533  *  Any required parent directories will be created.
534  */
535 string
536 Film::file (string f) const
537 {
538         boost::mutex::scoped_lock lm (_directory_mutex);
539
540         boost::filesystem::path p;
541         p /= _directory;
542         p /= f;
543
544         boost::filesystem::create_directories (p.parent_path ());
545         
546         return p.string ();
547 }
548
549 /** @return a DCI-compliant name for a DCP of this film */
550 string
551 Film::dci_name (bool if_created_now) const
552 {
553         stringstream d;
554
555         string fixed_name = to_upper_copy (name());
556         for (size_t i = 0; i < fixed_name.length(); ++i) {
557                 if (fixed_name[i] == ' ') {
558                         fixed_name[i] = '-';
559                 }
560         }
561
562         /* Spec is that the name part should be maximum 14 characters, as I understand it */
563         if (fixed_name.length() > 14) {
564                 fixed_name = fixed_name.substr (0, 14);
565         }
566
567         d << fixed_name;
568
569         if (dcp_content_type()) {
570                 d << "_" << dcp_content_type()->dci_name();
571         }
572
573         if (format()) {
574                 d << "_" << format()->dci_name();
575         }
576
577         DCIMetadata const dm = dci_metadata ();
578
579         if (!dm.audio_language.empty ()) {
580                 d << "_" << dm.audio_language;
581                 if (!dm.subtitle_language.empty()) {
582                         d << "-" << dm.subtitle_language;
583                 } else {
584                         d << "-XX";
585                 }
586         }
587
588         if (!dm.territory.empty ()) {
589                 d << "_" << dm.territory;
590                 if (!dm.rating.empty ()) {
591                         d << "-" << dm.rating;
592                 }
593         }
594
595         d << "_51_2K";
596
597         if (!dm.studio.empty ()) {
598                 d << "_" << dm.studio;
599         }
600
601         if (if_created_now) {
602                 d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
603         } else {
604                 d << "_" << boost::gregorian::to_iso_string (_dci_date);
605         }
606
607         if (!dm.facility.empty ()) {
608                 d << "_" << dm.facility;
609         }
610
611         if (!dm.package_type.empty ()) {
612                 d << "_" << dm.package_type;
613         }
614
615         return d.str ();
616 }
617
618 /** @return name to give the DCP */
619 string
620 Film::dcp_name (bool if_created_now) const
621 {
622         if (use_dci_name()) {
623                 return dci_name (if_created_now);
624         }
625
626         return name();
627 }
628
629
630 void
631 Film::set_directory (string d)
632 {
633         boost::mutex::scoped_lock lm (_state_mutex);
634         _directory = d;
635         _dirty = true;
636 }
637
638 void
639 Film::set_name (string n)
640 {
641         {
642                 boost::mutex::scoped_lock lm (_state_mutex);
643                 _name = n;
644         }
645         signal_changed (NAME);
646 }
647
648 void
649 Film::set_use_dci_name (bool u)
650 {
651         {
652                 boost::mutex::scoped_lock lm (_state_mutex);
653                 _use_dci_name = u;
654         }
655         signal_changed (USE_DCI_NAME);
656 }
657
658 void
659 Film::set_dcp_content_type (DCPContentType const * t)
660 {
661         {
662                 boost::mutex::scoped_lock lm (_state_mutex);
663                 _dcp_content_type = t;
664         }
665         signal_changed (DCP_CONTENT_TYPE);
666 }
667
668 void
669 Film::set_format (Format const * f)
670 {
671         {
672                 boost::mutex::scoped_lock lm (_state_mutex);
673                 _format = f;
674         }
675         signal_changed (FORMAT);
676 }
677
678 void
679 Film::set_crop (Crop c)
680 {
681         {
682                 boost::mutex::scoped_lock lm (_state_mutex);
683                 _crop = c;
684         }
685         signal_changed (CROP);
686 }
687
688 void
689 Film::set_left_crop (int c)
690 {
691         {
692                 boost::mutex::scoped_lock lm (_state_mutex);
693                 
694                 if (_crop.left == c) {
695                         return;
696                 }
697                 
698                 _crop.left = c;
699         }
700         signal_changed (CROP);
701 }
702
703 void
704 Film::set_right_crop (int c)
705 {
706         {
707                 boost::mutex::scoped_lock lm (_state_mutex);
708                 if (_crop.right == c) {
709                         return;
710                 }
711                 
712                 _crop.right = c;
713         }
714         signal_changed (CROP);
715 }
716
717 void
718 Film::set_top_crop (int c)
719 {
720         {
721                 boost::mutex::scoped_lock lm (_state_mutex);
722                 if (_crop.top == c) {
723                         return;
724                 }
725                 
726                 _crop.top = c;
727         }
728         signal_changed (CROP);
729 }
730
731 void
732 Film::set_bottom_crop (int c)
733 {
734         {
735                 boost::mutex::scoped_lock lm (_state_mutex);
736                 if (_crop.bottom == c) {
737                         return;
738                 }
739                 
740                 _crop.bottom = c;
741         }
742         signal_changed (CROP);
743 }
744
745 void
746 Film::set_filters (vector<Filter const *> f)
747 {
748         {
749                 boost::mutex::scoped_lock lm (_state_mutex);
750                 _filters = f;
751         }
752         signal_changed (FILTERS);
753 }
754
755 void
756 Film::set_scaler (Scaler const * s)
757 {
758         {
759                 boost::mutex::scoped_lock lm (_state_mutex);
760                 _scaler = s;
761         }
762         signal_changed (SCALER);
763 }
764
765 void
766 Film::set_ab (bool a)
767 {
768         {
769                 boost::mutex::scoped_lock lm (_state_mutex);
770                 _ab = a;
771         }
772         signal_changed (AB);
773 }
774
775 void
776 Film::set_audio_gain (float g)
777 {
778         {
779                 boost::mutex::scoped_lock lm (_state_mutex);
780                 _audio_gain = g;
781         }
782         signal_changed (AUDIO_GAIN);
783 }
784
785 void
786 Film::set_audio_delay (int d)
787 {
788         {
789                 boost::mutex::scoped_lock lm (_state_mutex);
790                 _audio_delay = d;
791         }
792         signal_changed (AUDIO_DELAY);
793 }
794
795 void
796 Film::set_with_subtitles (bool w)
797 {
798         {
799                 boost::mutex::scoped_lock lm (_state_mutex);
800                 _with_subtitles = w;
801         }
802         signal_changed (WITH_SUBTITLES);
803 }
804
805 void
806 Film::set_subtitle_offset (int o)
807 {
808         {
809                 boost::mutex::scoped_lock lm (_state_mutex);
810                 _subtitle_offset = o;
811         }
812         signal_changed (SUBTITLE_OFFSET);
813 }
814
815 void
816 Film::set_subtitle_scale (float s)
817 {
818         {
819                 boost::mutex::scoped_lock lm (_state_mutex);
820                 _subtitle_scale = s;
821         }
822         signal_changed (SUBTITLE_SCALE);
823 }
824
825 void
826 Film::set_colour_lut (int i)
827 {
828         {
829                 boost::mutex::scoped_lock lm (_state_mutex);
830                 _colour_lut = i;
831         }
832         signal_changed (COLOUR_LUT);
833 }
834
835 void
836 Film::set_j2k_bandwidth (int b)
837 {
838         {
839                 boost::mutex::scoped_lock lm (_state_mutex);
840                 _j2k_bandwidth = b;
841         }
842         signal_changed (J2K_BANDWIDTH);
843 }
844
845 void
846 Film::set_dci_metadata (DCIMetadata m)
847 {
848         {
849                 boost::mutex::scoped_lock lm (_state_mutex);
850                 _dci_metadata = m;
851         }
852         signal_changed (DCI_METADATA);
853 }
854
855
856 void
857 Film::set_dcp_video_frame_rate (int f)
858 {
859         {
860                 boost::mutex::scoped_lock lm (_state_mutex);
861                 _dcp_video_frame_rate = f;
862         }
863         signal_changed (DCP_VIDEO_FRAME_RATE);
864 }
865
866 void
867 Film::signal_changed (Property p)
868 {
869         {
870                 boost::mutex::scoped_lock lm (_state_mutex);
871                 _dirty = true;
872         }
873
874         switch (p) {
875         case Film::CONTENT:
876                 set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
877                 break;
878         default:
879                 break;
880         }
881
882         if (ui_signaller) {
883                 ui_signaller->emit (boost::bind (boost::ref (Changed), p));
884         }
885 }
886
887 void
888 Film::set_dci_date_today ()
889 {
890         _dci_date = boost::gregorian::day_clock::local_day ();
891 }
892
893 string
894 Film::info_path (int f) const
895 {
896         boost::filesystem::path p;
897         p /= info_dir ();
898
899         stringstream s;
900         s.width (8);
901         s << setfill('0') << f << ".md5";
902
903         p /= s.str();
904
905         /* info_dir() will already have added any initial bit of the path,
906            so don't call file() on this.
907         */
908         return p.string ();
909 }
910
911 string
912 Film::j2c_path (int f, bool t) const
913 {
914         boost::filesystem::path p;
915         p /= "j2c";
916         p /= video_state_identifier ();
917
918         stringstream s;
919         s.width (8);
920         s << setfill('0') << f << ".j2c";
921
922         if (t) {
923                 s << ".tmp";
924         }
925
926         p /= s.str();
927         return file (p.string ());
928 }
929
930 /** Make an educated guess as to whether we have a complete DCP
931  *  or not.
932  *  @return true if we do.
933  */
934
935 bool
936 Film::have_dcp () const
937 {
938         try {
939                 libdcp::DCP dcp (dir (dcp_name()));
940                 dcp.read ();
941         } catch (...) {
942                 return false;
943         }
944
945         return true;
946 }
947
948 shared_ptr<Player>
949 Film::player () const
950 {
951         boost::mutex::scoped_lock lm (_state_mutex);
952         return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
953 }
954
955 shared_ptr<Playlist>
956 Film::playlist () const
957 {
958         boost::mutex::scoped_lock lm (_state_mutex);
959         return _playlist;
960 }
961
962 Playlist::ContentList
963 Film::content () const
964 {
965         return _playlist->content ();
966 }
967
968 void
969 Film::add_content (shared_ptr<Content> c)
970 {
971         _playlist->add (c);
972         examine_content (c);
973 }
974
975 void
976 Film::remove_content (shared_ptr<Content> c)
977 {
978         _playlist->remove (c);
979 }
980
981 Time
982 Film::length () const
983 {
984         return _playlist->length (shared_from_this ());
985 }
986
987 bool
988 Film::has_subtitles () const
989 {
990         return _playlist->has_subtitles ();
991 }
992
993 OutputVideoFrame
994 Film::best_dcp_video_frame_rate () const
995 {
996         return _playlist->best_dcp_frame_rate ();
997 }
998
999 void
1000 Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
1001 {
1002         if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
1003                 set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
1004         } 
1005
1006         if (ui_signaller) {
1007                 ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
1008         }
1009 }
1010
1011 void
1012 Film::playlist_changed ()
1013 {
1014         signal_changed (CONTENT);
1015 }       
1016
1017 int
1018 Film::loop () const
1019 {
1020         return _playlist->loop ();
1021 }
1022
1023 void
1024 Film::set_loop (int c)
1025 {
1026         _playlist->set_loop (c);
1027 }
1028
1029 OutputAudioFrame
1030 Film::time_to_audio_frames (Time t) const
1031 {
1032         return t * dcp_audio_frame_rate () / TIME_HZ;
1033 }
1034
1035 OutputVideoFrame
1036 Film::time_to_video_frames (Time t) const
1037 {
1038         return t * dcp_video_frame_rate () / TIME_HZ;
1039 }
1040
1041 Time
1042 Film::audio_frames_to_time (OutputAudioFrame f) const
1043 {
1044         return f * TIME_HZ / dcp_audio_frame_rate ();
1045 }
1046
1047 Time
1048 Film::video_frames_to_time (OutputVideoFrame f) const
1049 {
1050         return f * TIME_HZ / dcp_video_frame_rate ();
1051 }
1052
1053 OutputAudioFrame
1054 Film::dcp_audio_frame_rate () const
1055 {
1056         /* XXX */
1057         return 48000;
1058 }