Give Film a container; move crop into video content; other bits.
[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 "container.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         , _container (Config::instance()->default_container ())
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         , _container         (o._container)
164         , _filters           (o._filters)
165         , _scaler            (o._scaler)
166         , _ab                (o._ab)
167         , _audio_gain        (o._audio_gain)
168         , _audio_delay       (o._audio_delay)
169         , _with_subtitles    (o._with_subtitles)
170         , _subtitle_offset   (o._subtitle_offset)
171         , _subtitle_scale    (o._subtitle_scale)
172         , _colour_lut        (o._colour_lut)
173         , _j2k_bandwidth     (o._j2k_bandwidth)
174         , _dci_metadata      (o._dci_metadata)
175         , _dcp_video_frame_rate (o._dcp_video_frame_rate)
176         , _dci_date          (o._dci_date)
177         , _dirty             (o._dirty)
178 {
179         _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
180 }
181
182 string
183 Film::video_state_identifier () const
184 {
185         assert (container ());
186         LocaleGuard lg;
187
188         pair<string, string> f = Filter::ffmpeg_strings (filters());
189
190         stringstream s;
191         s << container()->id()
192           << "_" << _playlist->video_digest()
193           << "_" << _dcp_video_frame_rate
194           << "_" << f.first << "_" << f.second
195           << "_" << scaler()->id()
196           << "_" << j2k_bandwidth()
197           << "_" << boost::lexical_cast<int> (colour_lut());
198
199         if (ab()) {
200                 pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
201                 s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
202         }
203
204         return s.str ();
205 }
206           
207 /** @return The path to the directory to write video frame info files to */
208 string
209 Film::info_dir () const
210 {
211         boost::filesystem::path p;
212         p /= "info";
213         p /= video_state_identifier ();
214         return dir (p.string());
215 }
216
217 string
218 Film::internal_video_mxf_dir () const
219 {
220         boost::filesystem::path p;
221         return dir ("video");
222 }
223
224 string
225 Film::internal_video_mxf_filename () const
226 {
227         return video_state_identifier() + ".mxf";
228 }
229
230 string
231 Film::dcp_video_mxf_filename () const
232 {
233         return filename_safe_name() + "_video.mxf";
234 }
235
236 string
237 Film::dcp_audio_mxf_filename () const
238 {
239         return filename_safe_name() + "_audio.mxf";
240 }
241
242 string
243 Film::filename_safe_name () const
244 {
245         string const n = name ();
246         string o;
247         for (size_t i = 0; i < n.length(); ++i) {
248                 if (isalnum (n[i])) {
249                         o += n[i];
250                 } else {
251                         o += "_";
252                 }
253         }
254
255         return o;
256 }
257
258 string
259 Film::audio_analysis_path () const
260 {
261         boost::filesystem::path p;
262         p /= "analysis";
263         p /= _playlist->audio_digest();
264         return file (p.string ());
265 }
266
267 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
268 void
269 Film::make_dcp ()
270 {
271         set_dci_date_today ();
272         
273         if (dcp_name().find ("/") != string::npos) {
274                 throw BadSettingError (_("name"), _("cannot contain slashes"));
275         }
276         
277         log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
278
279         {
280                 char buffer[128];
281                 gethostname (buffer, sizeof (buffer));
282                 log()->log (String::compose ("Starting to make DCP on %1", buffer));
283         }
284         
285 //      log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
286 //      if (length()) {
287 //              log()->log (String::compose ("Content length %1", length().get()));
288 //      }
289 //      log()->log (String::compose ("Content digest %1", content_digest()));
290 //      log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
291         log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
292         log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
293 #ifdef DCPOMATIC_DEBUG
294         log()->log ("DCP-o-matic built in debug mode.");
295 #else
296         log()->log ("DCP-o-matic built in optimised mode.");
297 #endif
298 #ifdef LIBDCP_DEBUG
299         log()->log ("libdcp built in debug mode.");
300 #else
301         log()->log ("libdcp built in optimised mode.");
302 #endif
303         pair<string, int> const c = cpu_info ();
304         log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
305         
306         if (container() == 0) {
307                 throw MissingSettingError (_("container"));
308         }
309
310         if (_playlist->content().empty ()) {
311                 throw StringError (_("You must add some content to the DCP before creating it"));
312         }
313
314         if (dcp_content_type() == 0) {
315                 throw MissingSettingError (_("content type"));
316         }
317
318         if (name().empty()) {
319                 throw MissingSettingError (_("name"));
320         }
321
322         shared_ptr<Job> r;
323
324         if (ab()) {
325                 r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
326         } else {
327                 r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
328         }
329 }
330
331 /** Start a job to analyse the audio in our Playlist */
332 void
333 Film::analyse_audio ()
334 {
335         if (_analyse_audio_job) {
336                 return;
337         }
338
339         _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
340         _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
341         JobManager::instance()->add (_analyse_audio_job);
342 }
343
344 /** Start a job to examine a piece of content */
345 void
346 Film::examine_content (shared_ptr<Content> c)
347 {
348         shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
349         JobManager::instance()->add (j);
350 }
351
352 void
353 Film::analyse_audio_finished ()
354 {
355         ensure_ui_thread ();
356
357         if (_analyse_audio_job->finished_ok ()) {
358                 AudioAnalysisSucceeded ();
359         }
360         
361         _analyse_audio_job.reset ();
362 }
363
364 /** Start a job to send our DCP to the configured TMS */
365 void
366 Film::send_dcp_to_tms ()
367 {
368         shared_ptr<Job> j (new SCPDCPJob (shared_from_this()));
369         JobManager::instance()->add (j);
370 }
371
372 /** Count the number of frames that have been encoded for this film.
373  *  @return frame count.
374  */
375 int
376 Film::encoded_frames () const
377 {
378         if (container() == 0) {
379                 return 0;
380         }
381
382         int N = 0;
383         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
384                 ++N;
385                 boost::this_thread::interruption_point ();
386         }
387
388         return N;
389 }
390
391 /** Write state to our `metadata' file */
392 void
393 Film::write_metadata () const
394 {
395         boost::mutex::scoped_lock lm (_state_mutex);
396         LocaleGuard lg;
397
398         boost::filesystem::create_directories (directory());
399
400         xmlpp::Document doc;
401         xmlpp::Element* root = doc.create_root_node ("Metadata");
402
403         root->add_child("Version")->add_child_text (boost::lexical_cast<string> (state_version));
404         root->add_child("Name")->add_child_text (_name);
405         root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
406
407         if (_dcp_content_type) {
408                 root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
409         }
410
411         if (_container) {
412                 root->add_child("Container")->add_child_text (_container->id ());
413         }
414
415         for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
416                 root->add_child("Filter")->add_child_text ((*i)->id ());
417         }
418         
419         root->add_child("Scaler")->add_child_text (_scaler->id ());
420         root->add_child("AB")->add_child_text (_ab ? "1" : "0");
421         root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain));
422         root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay));
423         root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
424         root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset));
425         root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale));
426         root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut));
427         root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth));
428         _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
429         root->add_child("DCPVideoFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_video_frame_rate));
430         root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
431         _playlist->as_xml (root->add_child ("Playlist"));
432
433         doc.write_to_file_formatted (file ("metadata.xml"));
434         
435         _dirty = false;
436 }
437
438 /** Read state from our metadata file */
439 void
440 Film::read_metadata ()
441 {
442         boost::mutex::scoped_lock lm (_state_mutex);
443         LocaleGuard lg;
444
445         if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
446                 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!"));
447         }
448
449         cxml::File f (file ("metadata.xml"), "Metadata");
450         
451         _name = f.string_child ("Name");
452         _use_dci_name = f.bool_child ("UseDCIName");
453
454         {
455                 optional<string> c = f.optional_string_child ("DCPContentType");
456                 if (c) {
457                         _dcp_content_type = DCPContentType::from_dci_name (c.get ());
458                 }
459         }
460
461         {
462                 optional<string> c = f.optional_string_child ("Container");
463                 if (c) {
464                         _container = Container::from_id (c.get ());
465                 }
466         }
467
468         {
469                 list<shared_ptr<cxml::Node> > c = f.node_children ("Filter");
470                 for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
471                         _filters.push_back (Filter::from_id ((*i)->content ()));
472                 }
473         }
474
475         _scaler = Scaler::from_id (f.string_child ("Scaler"));
476         _ab = f.bool_child ("AB");
477         _audio_gain = f.number_child<float> ("AudioGain");
478         _audio_delay = f.number_child<int> ("AudioDelay");
479         _with_subtitles = f.bool_child ("WithSubtitles");
480         _subtitle_offset = f.number_child<float> ("SubtitleOffset");
481         _subtitle_scale = f.number_child<float> ("SubtitleScale");
482         _colour_lut = f.number_child<int> ("ColourLUT");
483         _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
484         _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
485         _dcp_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate");
486         _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
487
488         _playlist->set_from_xml (f.node_child ("Playlist"));
489
490         _dirty = false;
491 }
492
493 /** Given a directory name, return its full path within the Film's directory.
494  *  The directory (and its parents) will be created if they do not exist.
495  */
496 string
497 Film::dir (string d) const
498 {
499         boost::mutex::scoped_lock lm (_directory_mutex);
500         
501         boost::filesystem::path p;
502         p /= _directory;
503         p /= d;
504         
505         boost::filesystem::create_directories (p);
506         
507         return p.string ();
508 }
509
510 /** Given a file or directory name, return its full path within the Film's directory.
511  *  _directory_mutex must not be locked on entry.
512  *  Any required parent directories will be created.
513  */
514 string
515 Film::file (string f) const
516 {
517         boost::mutex::scoped_lock lm (_directory_mutex);
518
519         boost::filesystem::path p;
520         p /= _directory;
521         p /= f;
522
523         boost::filesystem::create_directories (p.parent_path ());
524         
525         return p.string ();
526 }
527
528 /** @return a DCI-compliant name for a DCP of this film */
529 string
530 Film::dci_name (bool if_created_now) const
531 {
532         stringstream d;
533
534         string fixed_name = to_upper_copy (name());
535         for (size_t i = 0; i < fixed_name.length(); ++i) {
536                 if (fixed_name[i] == ' ') {
537                         fixed_name[i] = '-';
538                 }
539         }
540
541         /* Spec is that the name part should be maximum 14 characters, as I understand it */
542         if (fixed_name.length() > 14) {
543                 fixed_name = fixed_name.substr (0, 14);
544         }
545
546         d << fixed_name;
547
548         if (dcp_content_type()) {
549                 d << "_" << dcp_content_type()->dci_name();
550         }
551
552         if (container()) {
553                 d << "_" << container()->dci_name();
554         }
555
556         DCIMetadata const dm = dci_metadata ();
557
558         if (!dm.audio_language.empty ()) {
559                 d << "_" << dm.audio_language;
560                 if (!dm.subtitle_language.empty()) {
561                         d << "-" << dm.subtitle_language;
562                 } else {
563                         d << "-XX";
564                 }
565         }
566
567         if (!dm.territory.empty ()) {
568                 d << "_" << dm.territory;
569                 if (!dm.rating.empty ()) {
570                         d << "-" << dm.rating;
571                 }
572         }
573
574         d << "_51_2K";
575
576         if (!dm.studio.empty ()) {
577                 d << "_" << dm.studio;
578         }
579
580         if (if_created_now) {
581                 d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
582         } else {
583                 d << "_" << boost::gregorian::to_iso_string (_dci_date);
584         }
585
586         if (!dm.facility.empty ()) {
587                 d << "_" << dm.facility;
588         }
589
590         if (!dm.package_type.empty ()) {
591                 d << "_" << dm.package_type;
592         }
593
594         return d.str ();
595 }
596
597 /** @return name to give the DCP */
598 string
599 Film::dcp_name (bool if_created_now) const
600 {
601         if (use_dci_name()) {
602                 return dci_name (if_created_now);
603         }
604
605         return name();
606 }
607
608
609 void
610 Film::set_directory (string d)
611 {
612         boost::mutex::scoped_lock lm (_state_mutex);
613         _directory = d;
614         _dirty = true;
615 }
616
617 void
618 Film::set_name (string n)
619 {
620         {
621                 boost::mutex::scoped_lock lm (_state_mutex);
622                 _name = n;
623         }
624         signal_changed (NAME);
625 }
626
627 void
628 Film::set_use_dci_name (bool u)
629 {
630         {
631                 boost::mutex::scoped_lock lm (_state_mutex);
632                 _use_dci_name = u;
633         }
634         signal_changed (USE_DCI_NAME);
635 }
636
637 void
638 Film::set_dcp_content_type (DCPContentType const * t)
639 {
640         {
641                 boost::mutex::scoped_lock lm (_state_mutex);
642                 _dcp_content_type = t;
643         }
644         signal_changed (DCP_CONTENT_TYPE);
645 }
646
647 void
648 Film::set_container (Container const * c)
649 {
650         {
651                 boost::mutex::scoped_lock lm (_state_mutex);
652                 _container = c;
653         }
654         signal_changed (CONTAINER);
655 }
656
657 void
658 Film::set_filters (vector<Filter const *> f)
659 {
660         {
661                 boost::mutex::scoped_lock lm (_state_mutex);
662                 _filters = f;
663         }
664         signal_changed (FILTERS);
665 }
666
667 void
668 Film::set_scaler (Scaler const * s)
669 {
670         {
671                 boost::mutex::scoped_lock lm (_state_mutex);
672                 _scaler = s;
673         }
674         signal_changed (SCALER);
675 }
676
677 void
678 Film::set_ab (bool a)
679 {
680         {
681                 boost::mutex::scoped_lock lm (_state_mutex);
682                 _ab = a;
683         }
684         signal_changed (AB);
685 }
686
687 void
688 Film::set_audio_gain (float g)
689 {
690         {
691                 boost::mutex::scoped_lock lm (_state_mutex);
692                 _audio_gain = g;
693         }
694         signal_changed (AUDIO_GAIN);
695 }
696
697 void
698 Film::set_audio_delay (int d)
699 {
700         {
701                 boost::mutex::scoped_lock lm (_state_mutex);
702                 _audio_delay = d;
703         }
704         signal_changed (AUDIO_DELAY);
705 }
706
707 void
708 Film::set_with_subtitles (bool w)
709 {
710         {
711                 boost::mutex::scoped_lock lm (_state_mutex);
712                 _with_subtitles = w;
713         }
714         signal_changed (WITH_SUBTITLES);
715 }
716
717 void
718 Film::set_subtitle_offset (int o)
719 {
720         {
721                 boost::mutex::scoped_lock lm (_state_mutex);
722                 _subtitle_offset = o;
723         }
724         signal_changed (SUBTITLE_OFFSET);
725 }
726
727 void
728 Film::set_subtitle_scale (float s)
729 {
730         {
731                 boost::mutex::scoped_lock lm (_state_mutex);
732                 _subtitle_scale = s;
733         }
734         signal_changed (SUBTITLE_SCALE);
735 }
736
737 void
738 Film::set_colour_lut (int i)
739 {
740         {
741                 boost::mutex::scoped_lock lm (_state_mutex);
742                 _colour_lut = i;
743         }
744         signal_changed (COLOUR_LUT);
745 }
746
747 void
748 Film::set_j2k_bandwidth (int b)
749 {
750         {
751                 boost::mutex::scoped_lock lm (_state_mutex);
752                 _j2k_bandwidth = b;
753         }
754         signal_changed (J2K_BANDWIDTH);
755 }
756
757 void
758 Film::set_dci_metadata (DCIMetadata m)
759 {
760         {
761                 boost::mutex::scoped_lock lm (_state_mutex);
762                 _dci_metadata = m;
763         }
764         signal_changed (DCI_METADATA);
765 }
766
767
768 void
769 Film::set_dcp_video_frame_rate (int f)
770 {
771         {
772                 boost::mutex::scoped_lock lm (_state_mutex);
773                 _dcp_video_frame_rate = f;
774         }
775         signal_changed (DCP_VIDEO_FRAME_RATE);
776 }
777
778 void
779 Film::signal_changed (Property p)
780 {
781         {
782                 boost::mutex::scoped_lock lm (_state_mutex);
783                 _dirty = true;
784         }
785
786         switch (p) {
787         case Film::CONTENT:
788                 set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
789                 break;
790         default:
791                 break;
792         }
793
794         if (ui_signaller) {
795                 ui_signaller->emit (boost::bind (boost::ref (Changed), p));
796         }
797 }
798
799 void
800 Film::set_dci_date_today ()
801 {
802         _dci_date = boost::gregorian::day_clock::local_day ();
803 }
804
805 string
806 Film::info_path (int f) const
807 {
808         boost::filesystem::path p;
809         p /= info_dir ();
810
811         stringstream s;
812         s.width (8);
813         s << setfill('0') << f << ".md5";
814
815         p /= s.str();
816
817         /* info_dir() will already have added any initial bit of the path,
818            so don't call file() on this.
819         */
820         return p.string ();
821 }
822
823 string
824 Film::j2c_path (int f, bool t) const
825 {
826         boost::filesystem::path p;
827         p /= "j2c";
828         p /= video_state_identifier ();
829
830         stringstream s;
831         s.width (8);
832         s << setfill('0') << f << ".j2c";
833
834         if (t) {
835                 s << ".tmp";
836         }
837
838         p /= s.str();
839         return file (p.string ());
840 }
841
842 /** Make an educated guess as to whether we have a complete DCP
843  *  or not.
844  *  @return true if we do.
845  */
846
847 bool
848 Film::have_dcp () const
849 {
850         try {
851                 libdcp::DCP dcp (dir (dcp_name()));
852                 dcp.read ();
853         } catch (...) {
854                 return false;
855         }
856
857         return true;
858 }
859
860 shared_ptr<Player>
861 Film::player () const
862 {
863         boost::mutex::scoped_lock lm (_state_mutex);
864         return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
865 }
866
867 shared_ptr<Playlist>
868 Film::playlist () const
869 {
870         boost::mutex::scoped_lock lm (_state_mutex);
871         return _playlist;
872 }
873
874 Playlist::ContentList
875 Film::content () const
876 {
877         return _playlist->content ();
878 }
879
880 void
881 Film::add_content (shared_ptr<Content> c)
882 {
883         _playlist->add (c);
884         examine_content (c);
885 }
886
887 void
888 Film::remove_content (shared_ptr<Content> c)
889 {
890         _playlist->remove (c);
891 }
892
893 Time
894 Film::length () const
895 {
896         return _playlist->length (shared_from_this ());
897 }
898
899 bool
900 Film::has_subtitles () const
901 {
902         return _playlist->has_subtitles ();
903 }
904
905 OutputVideoFrame
906 Film::best_dcp_video_frame_rate () const
907 {
908         return _playlist->best_dcp_frame_rate ();
909 }
910
911 void
912 Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
913 {
914         if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
915                 set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
916         } 
917
918         if (ui_signaller) {
919                 ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
920         }
921 }
922
923 void
924 Film::playlist_changed ()
925 {
926         signal_changed (CONTENT);
927 }       
928
929 int
930 Film::loop () const
931 {
932         return _playlist->loop ();
933 }
934
935 void
936 Film::set_loop (int c)
937 {
938         _playlist->set_loop (c);
939 }
940
941 OutputAudioFrame
942 Film::time_to_audio_frames (Time t) const
943 {
944         return t * dcp_audio_frame_rate () / TIME_HZ;
945 }
946
947 OutputVideoFrame
948 Film::time_to_video_frames (Time t) const
949 {
950         return t * dcp_video_frame_rate () / TIME_HZ;
951 }
952
953 Time
954 Film::audio_frames_to_time (OutputAudioFrame f) const
955 {
956         return f * TIME_HZ / dcp_audio_frame_rate ();
957 }
958
959 Time
960 Film::video_frames_to_time (OutputVideoFrame f) const
961 {
962         return f * TIME_HZ / dcp_video_frame_rate ();
963 }
964
965 OutputAudioFrame
966 Film::dcp_audio_frame_rate () const
967 {
968         /* XXX */
969         return 48000;
970 }