Use boost::signals2; fix bugs with x-thread signalling.
[dcpomatic.git] / src / lib / film.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
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.
8
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.
13
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.
17
18 */
19
20 #include <stdexcept>
21 #include <iostream>
22 #include <algorithm>
23 #include <fstream>
24 #include <cstdlib>
25 #include <sstream>
26 #include <iomanip>
27 #include <unistd.h>
28 #include <boost/filesystem.hpp>
29 #include <boost/algorithm/string.hpp>
30 #include <boost/lexical_cast.hpp>
31 #include <boost/date_time.hpp>
32 #include "film.h"
33 #include "format.h"
34 #include "imagemagick_encoder.h"
35 #include "job.h"
36 #include "filter.h"
37 #include "transcoder.h"
38 #include "util.h"
39 #include "job_manager.h"
40 #include "ab_transcode_job.h"
41 #include "transcode_job.h"
42 #include "scp_dcp_job.h"
43 #include "copy_from_dvd_job.h"
44 #include "make_dcp_job.h"
45 #include "log.h"
46 #include "options.h"
47 #include "exceptions.h"
48 #include "examine_content_job.h"
49 #include "scaler.h"
50 #include "decoder_factory.h"
51 #include "config.h"
52 #include "check_hashes_job.h"
53 #include "version.h"
54 #include "ui_signaller.h"
55
56 using std::string;
57 using std::stringstream;
58 using std::multimap;
59 using std::pair;
60 using std::map;
61 using std::vector;
62 using std::ifstream;
63 using std::ofstream;
64 using std::setfill;
65 using boost::shared_ptr;
66 using boost::lexical_cast;
67 using boost::to_upper_copy;
68 using boost::ends_with;
69 using boost::starts_with;
70
71 /** Construct a Film object in a given directory, reading any metadata
72  *  file that exists in that directory.  An exception will be thrown if
73  *  must_exist is true and the specified directory does not exist.
74  *
75  *  @param d Film directory.
76  *  @param must_exist true to throw an exception if does not exist.
77  */
78
79 Film::Film (string d, bool must_exist)
80         : _use_dci_name (false)
81         , _dcp_content_type (0)
82         , _format (0)
83         , _scaler (Scaler::from_id ("bicubic"))
84         , _dcp_frames (0)
85         , _dcp_trim_action (CUT)
86         , _dcp_ab (false)
87         , _audio_stream (-1)
88         , _audio_gain (0)
89         , _audio_delay (0)
90         , _still_duration (10)
91         , _subtitle_stream (-1)
92         , _with_subtitles (false)
93         , _subtitle_offset (0)
94         , _subtitle_scale (1)
95         , _length (0)
96         , _audio_sample_rate (0)
97         , _has_subtitles (false)
98         , _frames_per_second (0)
99         , _dirty (false)
100 {
101         /* Make state.directory a complete path without ..s (where possible)
102            (Code swiped from Adam Bowen on stackoverflow)
103         */
104         
105         boost::filesystem::path p (boost::filesystem::system_complete (d));
106         boost::filesystem::path result;
107         for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
108                 if (*i == "..") {
109                         if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
110                                 result /= *i;
111                         } else {
112                                 result = result.parent_path ();
113                         }
114                 } else if (*i != ".") {
115                         result /= *i;
116                 }
117         }
118
119         set_directory (result.string ());
120         
121         if (!boost::filesystem::exists (directory())) {
122                 if (must_exist) {
123                         throw OpenFileError (directory());
124                 } else {
125                         boost::filesystem::create_directory (directory());
126                 }
127         }
128
129         read_metadata ();
130
131         _log = new FileLog (file ("log"));
132 }
133
134 Film::Film (Film const & o)
135         : _log (0)
136         , _directory         (o._directory)
137         , _name              (o._name)
138         , _use_dci_name      (o._use_dci_name)
139         , _content           (o._content)
140         , _dcp_content_type  (o._dcp_content_type)
141         , _format            (o._format)
142         , _crop              (o._crop)
143         , _filters           (o._filters)
144         , _scaler            (o._scaler)
145         , _dcp_frames        (o._dcp_frames)
146         , _dcp_trim_action   (o._dcp_trim_action)
147         , _dcp_ab            (o._dcp_ab)
148         , _audio_stream      (o._audio_stream)
149         , _audio_gain        (o._audio_gain)
150         , _audio_delay       (o._audio_delay)
151         , _still_duration    (o._still_duration)
152         , _subtitle_stream   (o._subtitle_stream)
153         , _with_subtitles    (o._with_subtitles)
154         , _subtitle_offset   (o._subtitle_offset)
155         , _subtitle_scale    (o._subtitle_scale)
156         , _audio_language    (o._audio_language)
157         , _subtitle_language (o._subtitle_language)
158         , _territory         (o._territory)
159         , _rating            (o._rating)
160         , _studio            (o._studio)
161         , _facility          (o._facility)
162         , _package_type      (o._package_type)
163         , _thumbs            (o._thumbs)
164         , _size              (o._size)
165         , _length            (o._length)
166         , _audio_sample_rate (o._audio_sample_rate)
167         , _content_digest    (o._content_digest)
168         , _has_subtitles     (o._has_subtitles)
169         , _audio_streams     (o._audio_streams)
170         , _subtitle_streams  (o._subtitle_streams)
171         , _frames_per_second (o._frames_per_second)
172         , _dirty             (o._dirty)
173 {
174
175 }
176
177 Film::~Film ()
178 {
179         delete _log;
180 }
181           
182 /** @return The path to the directory to write JPEG2000 files to */
183 string
184 Film::j2k_dir () const
185 {
186         assert (format());
187
188         boost::filesystem::path p;
189
190         /* Start with j2c */
191         p /= "j2c";
192
193         pair<string, string> f = Filter::ffmpeg_strings (filters());
194
195         /* Write stuff to specify the filter / post-processing settings that are in use,
196            so that we don't get confused about J2K files generated using different
197            settings.
198         */
199         stringstream s;
200         s << format()->id()
201           << "_" << content_digest()
202           << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
203           << "_" << f.first << "_" << f.second
204           << "_" << scaler()->id();
205
206         p /= s.str ();
207
208         /* Similarly for the A/B case */
209         if (dcp_ab()) {
210                 stringstream s;
211                 pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
212                 s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
213                 p /= s.str ();
214         }
215         
216         return dir (p.string());
217 }
218
219 /** Add suitable Jobs to the JobManager to create a DCP for this Film.
220  *  @param true to transcode, false to use the WAV and J2K files that are already there.
221  */
222 void
223 Film::make_dcp (bool transcode)
224 {
225         if (dcp_name().find ("/") != string::npos) {
226                 throw BadSettingError ("name", "cannot contain slashes");
227         }
228         
229         log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
230
231         {
232                 char buffer[128];
233                 gethostname (buffer, sizeof (buffer));
234                 log()->log (String::compose ("Starting to make DCP on %1", buffer));
235         }
236                 
237         if (format() == 0) {
238                 throw MissingSettingError ("format");
239         }
240
241         if (content().empty ()) {
242                 throw MissingSettingError ("content");
243         }
244
245         if (dcp_content_type() == 0) {
246                 throw MissingSettingError ("content type");
247         }
248
249         if (name().empty()) {
250                 throw MissingSettingError ("name");
251         }
252
253         shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", dir ("wavs")));
254         o->out_size = format()->dcp_size ();
255         if (dcp_frames() == 0) {
256                 /* Decode the whole film, no blacking */
257                 o->black_after = 0;
258         } else {
259                 switch (dcp_trim_action()) {
260                 case CUT:
261                         /* Decode only part of the film, no blacking */
262                         o->black_after = 0;
263                         break;
264                 case BLACK_OUT:
265                         /* Decode the whole film, but black some frames out */
266                         o->black_after = dcp_frames ();
267                 }
268         }
269         
270         o->padding = format()->dcp_padding (this);
271         o->ratio = format()->ratio_as_float (this);
272         o->decode_subtitles = with_subtitles ();
273
274         shared_ptr<Job> r;
275
276         if (transcode) {
277                 if (dcp_ab()) {
278                         r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
279                 } else {
280                         r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
281                 }
282         }
283
284         r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), o, r)));
285         JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), o, r)));
286 }
287
288 /** Start a job to examine our content file */
289 void
290 Film::examine_content ()
291 {
292         if (_examine_content_job) {
293                 return;
294         }
295
296         set_thumbs (vector<int> ());
297         boost::filesystem::remove_all (dir ("thumbs"));
298
299         /* This call will recreate the directory */
300         dir ("thumbs");
301         
302         _examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr<Job> ()));
303         _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
304         JobManager::instance()->add (_examine_content_job);
305 }
306
307 void
308 Film::examine_content_finished ()
309 {
310         _examine_content_job.reset ();
311 }
312
313 /** @return full paths to any audio files that this Film has */
314 vector<string>
315 Film::audio_files () const
316 {
317         vector<string> f;
318         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (dir("wavs")); i != boost::filesystem::directory_iterator(); ++i) {
319                 f.push_back (i->path().string ());
320         }
321
322         return f;
323 }
324
325 /** Start a job to send our DCP to the configured TMS */
326 void
327 Film::send_dcp_to_tms ()
328 {
329         shared_ptr<Job> j (new SCPDCPJob (shared_from_this(), shared_ptr<Job> ()));
330         JobManager::instance()->add (j);
331 }
332
333 void
334 Film::copy_from_dvd ()
335 {
336         shared_ptr<Job> j (new CopyFromDVDJob (shared_from_this(), shared_ptr<Job> ()));
337         JobManager::instance()->add (j);
338 }
339
340 /** Count the number of frames that have been encoded for this film.
341  *  @return frame count.
342  */
343 int
344 Film::encoded_frames () const
345 {
346         if (format() == 0) {
347                 return 0;
348         }
349
350         int N = 0;
351         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
352                 ++N;
353                 boost::this_thread::interruption_point ();
354         }
355
356         return N;
357 }
358
359 /** Return the filename of a subtitle image if one exists for a given thumb index.
360  *  @param Thumbnail index.
361  *  @return Position of the image within the source frame, and the image filename, if one exists.
362  *  Otherwise the filename will be empty.
363  */
364 pair<Position, string>
365 Film::thumb_subtitle (int n) const
366 {
367         string sub_file = thumb_base(n) + ".sub";
368         if (!boost::filesystem::exists (sub_file)) {
369                 return pair<Position, string> ();
370         }
371
372         pair<Position, string> sub;
373         
374         ifstream f (sub_file.c_str ());
375         multimap<string, string> kv = read_key_value (f);
376         for (map<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
377                 if (i->first == "x") {
378                         sub.first.x = lexical_cast<int> (i->second);
379                 } else if (i->first == "y") {
380                         sub.first.y = lexical_cast<int> (i->second);
381                         sub.second = String::compose ("%1.sub.png", thumb_base(n));
382                 }
383         }
384         
385         return sub;
386 }
387
388 /** Write state to our `metadata' file */
389 void
390 Film::write_metadata () const
391 {
392         boost::mutex::scoped_lock lm (_state_mutex);
393         
394         boost::filesystem::create_directories (directory());
395
396         string const m = file_locked ("metadata");
397         ofstream f (m.c_str ());
398         if (!f.good ()) {
399                 throw CreateFileError (m);
400         }
401
402         /* User stuff */
403         f << "name " << _name << "\n";
404         f << "use_dci_name " << _use_dci_name << "\n";
405         f << "content " << _content << "\n";
406         if (_dcp_content_type) {
407                 f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
408         }
409         if (_format) {
410                 f << "format " << _format->as_metadata () << "\n";
411         }
412         f << "left_crop " << _crop.left << "\n";
413         f << "right_crop " << _crop.right << "\n";
414         f << "top_crop " << _crop.top << "\n";
415         f << "bottom_crop " << _crop.bottom << "\n";
416         for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
417                 f << "filter " << (*i)->id () << "\n";
418         }
419         f << "scaler " << _scaler->id () << "\n";
420         f << "dcp_frames " << _dcp_frames << "\n";
421
422         f << "dcp_trim_action ";
423         switch (_dcp_trim_action) {
424         case CUT:
425                 f << "cut\n";
426                 break;
427         case BLACK_OUT:
428                 f << "black_out\n";
429                 break;
430         }
431         
432         f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
433         f << "selected_audio_stream " << _audio_stream << "\n";
434         f << "audio_gain " << _audio_gain << "\n";
435         f << "audio_delay " << _audio_delay << "\n";
436         f << "still_duration " << _still_duration << "\n";
437         f << "selected_subtitle_stream " << _subtitle_stream << "\n";
438         f << "with_subtitles " << _with_subtitles << "\n";
439         f << "subtitle_offset " << _subtitle_offset << "\n";
440         f << "subtitle_scale " << _subtitle_scale << "\n";
441         f << "audio_language " << _audio_language << "\n";
442         f << "subtitle_language " << _subtitle_language << "\n";
443         f << "territory " << _territory << "\n";
444         f << "rating " << _rating << "\n";
445         f << "studio " << _studio << "\n";
446         f << "facility " << _facility << "\n";
447         f << "package_type " << _package_type << "\n";
448
449         /* Cached stuff; this is information about our content; we could
450            look it up each time, but that's slow.
451         */
452         for (vector<int>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) {
453                 f << "thumb " << *i << "\n";
454         }
455         f << "width " << _size.width << "\n";
456         f << "height " << _size.height << "\n";
457         f << "length " << _length << "\n";
458         f << "audio_sample_rate " << _audio_sample_rate << "\n";
459         f << "content_digest " << _content_digest << "\n";
460         f << "has_subtitles " << _has_subtitles << "\n";
461
462         for (vector<AudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
463                 f << "audio_stream " << i->to_string () << "\n";
464         }
465
466         for (vector<SubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
467                 f << "subtitle_stream " << i->to_string () << "\n";
468         }
469
470         f << "frames_per_second " << _frames_per_second << "\n";
471         
472         _dirty = false;
473 }
474
475 /** Read state from our metadata file */
476 void
477 Film::read_metadata ()
478 {
479         boost::mutex::scoped_lock lm (_state_mutex);
480         
481         ifstream f (file_locked("metadata").c_str());
482         multimap<string, string> kv = read_key_value (f);
483         for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
484                 string const k = i->first;
485                 string const v = i->second;
486
487                 /* User-specified stuff */
488                 if (k == "name") {
489                         _name = v;
490                 } else if (k == "use_dci_name") {
491                         _use_dci_name = (v == "1");
492                 } else if (k == "content") {
493                         _content = v;
494                 } else if (k == "dcp_content_type") {
495                         _dcp_content_type = DCPContentType::from_pretty_name (v);
496                 } else if (k == "format") {
497                         _format = Format::from_metadata (v);
498                 } else if (k == "left_crop") {
499                         _crop.left = atoi (v.c_str ());
500                 } else if (k == "right_crop") {
501                         _crop.right = atoi (v.c_str ());
502                 } else if (k == "top_crop") {
503                         _crop.top = atoi (v.c_str ());
504                 } else if (k == "bottom_crop") {
505                         _crop.bottom = atoi (v.c_str ());
506                 } else if (k == "filter") {
507                         _filters.push_back (Filter::from_id (v));
508                 } else if (k == "scaler") {
509                         _scaler = Scaler::from_id (v);
510                 } else if (k == "dcp_frames") {
511                         _dcp_frames = atoi (v.c_str ());
512                 } else if (k == "dcp_trim_action") {
513                         if (v == "cut") {
514                                 _dcp_trim_action = CUT;
515                         } else if (v == "black_out") {
516                                 _dcp_trim_action = BLACK_OUT;
517                         }
518                 } else if (k == "dcp_ab") {
519                         _dcp_ab = (v == "1");
520                 } else if (k == "selected_audio_stream") {
521                         _audio_stream = atoi (v.c_str ());
522                 } else if (k == "audio_gain") {
523                         _audio_gain = atof (v.c_str ());
524                 } else if (k == "audio_delay") {
525                         _audio_delay = atoi (v.c_str ());
526                 } else if (k == "still_duration") {
527                         _still_duration = atoi (v.c_str ());
528                 } else if (k == "selected_subtitle_stream") {
529                         _subtitle_stream = atoi (v.c_str ());
530                 } else if (k == "with_subtitles") {
531                         _with_subtitles = (v == "1");
532                 } else if (k == "subtitle_offset") {
533                         _subtitle_offset = atoi (v.c_str ());
534                 } else if (k == "subtitle_scale") {
535                         _subtitle_scale = atof (v.c_str ());
536                 } else if (k == "audio_language") {
537                         _audio_language = v;
538                 } else if (k == "subtitle_language") {
539                         _subtitle_language = v;
540                 } else if (k == "territory") {
541                         _territory = v;
542                 } else if (k == "rating") {
543                         _rating = v;
544                 } else if (k == "studio") {
545                         _studio = v;
546                 } else if (k == "facility") {
547                         _facility = v;
548                 } else if (k == "package_type") {
549                         _package_type = v;
550                 }
551                 
552                 /* Cached stuff */
553                 if (k == "thumb") {
554                         int const n = atoi (v.c_str ());
555                         /* Only add it to the list if it still exists */
556                         if (boost::filesystem::exists (thumb_file_for_frame_locked (n))) {
557                                 _thumbs.push_back (n);
558                         }
559                 } else if (k == "width") {
560                         _size.width = atoi (v.c_str ());
561                 } else if (k == "height") {
562                         _size.height = atoi (v.c_str ());
563                 } else if (k == "length") {
564                         _length = atof (v.c_str ());
565                 } else if (k == "audio_sample_rate") {
566                         _audio_sample_rate = atoi (v.c_str ());
567                 } else if (k == "content_digest") {
568                         _content_digest = v;
569                 } else if (k == "has_subtitles") {
570                         _has_subtitles = (v == "1");
571                 } else if (k == "audio_stream") {
572                         _audio_streams.push_back (AudioStream (v));
573                 } else if (k == "subtitle_stream") {
574                         _subtitle_streams.push_back (SubtitleStream (v));
575                 } else if (k == "frames_per_second") {
576                         _frames_per_second = atof (v.c_str ());
577                 }
578         }
579                 
580         _dirty = false;
581 }
582
583 /** @param n A thumb index.
584  *  @return The path to the thumb's image file.
585  */
586 string
587 Film::thumb_file (int n) const
588 {
589         return thumb_file_for_frame (thumb_frame (n));
590 }
591
592 /** @param n A frame index within the Film.
593  *  @return The path to the thumb's image file for this frame;
594  *  we assume that it exists.
595  */
596 string
597 Film::thumb_file_for_frame (int n) const
598 {
599         return thumb_base_for_frame(n) + ".png";
600 }
601
602 string
603 Film::thumb_file_for_frame_locked (int n) const
604 {
605         return thumb_base_for_frame_locked(n) + ".png";
606 }
607
608 string
609 Film::thumb_base (int n) const
610 {
611         return thumb_base_for_frame (thumb_frame (n));
612 }
613
614 string
615 Film::thumb_base_for_frame (int n) const
616 {
617         boost::mutex::scoped_lock lm (_state_mutex);
618         return thumb_base_for_frame_locked (n);
619 }
620
621 string
622 Film::thumb_base_for_frame_locked (int n) const
623 {
624         stringstream s;
625         s.width (8);
626         s << setfill('0') << n;
627         
628         boost::filesystem::path p;
629         p /= dir_locked ("thumbs");
630         p /= s.str ();
631                 
632         return p.string ();
633 }
634
635 /** @param n A thumb index.
636  *  @return The frame within the Film that it is for.
637  */
638 int
639 Film::thumb_frame (int n) const
640 {
641         boost::mutex::scoped_lock lm (_state_mutex);
642         assert (n < int (_thumbs.size ()));
643         return _thumbs[n];
644 }
645
646 Size
647 Film::cropped_size (Size s) const
648 {
649         boost::mutex::scoped_lock lm (_state_mutex);
650         s.width -= _crop.left + _crop.right;
651         s.height -= _crop.top + _crop.bottom;
652         return s;
653 }
654
655 /** Given a directory name, return its full path within the Film's directory.
656  *  The directory (and its parents) will be created if they do not exist.
657  */
658 string
659 Film::dir (string d) const
660 {
661         boost::mutex::scoped_lock lm (_state_mutex);
662         return dir_locked (d);
663 }
664
665 string
666 Film::dir_locked (string d) const
667 {
668         boost::filesystem::path p;
669         p /= _directory;
670         p /= d;
671         boost::filesystem::create_directories (p);
672         return p.string ();
673 }
674
675 /** Given a file or directory name, return its full path within the Film's directory */
676 string
677 Film::file (string f) const
678 {
679         boost::mutex::scoped_lock lm (_state_mutex);
680         return file_locked (f);
681 }
682
683 string
684 Film::file_locked (string f) const
685 {
686         boost::filesystem::path p;
687         p /= _directory;
688         p /= f;
689         return p.string ();
690 }
691
692 /** @return full path of the content (actual video) file
693  *  of the Film.
694  */
695 string
696 Film::content_path () const
697 {
698         boost::mutex::scoped_lock lm (_state_mutex);
699         if (boost::filesystem::path(_content).has_root_directory ()) {
700                 return _content;
701         }
702
703         return file_locked (_content);
704 }
705
706 ContentType
707 Film::content_type () const
708 {
709 #if BOOST_FILESYSTEM_VERSION == 3
710         string ext = boost::filesystem::path(_content).extension().string();
711 #else
712         string ext = filesystem::path(_content).extension();
713 #endif
714
715         transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
716         
717         if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
718                 return STILL;
719         }
720
721         return VIDEO;
722 }
723
724 /** @return The sampling rate that we will resample the audio to */
725 int
726 Film::target_audio_sample_rate () const
727 {
728         /* Resample to a DCI-approved sample rate */
729         double t = dcp_audio_sample_rate (audio_sample_rate());
730
731         /* Compensate for the fact that video will be rounded to the
732            nearest integer number of frames per second.
733         */
734         if (rint (frames_per_second()) != frames_per_second()) {
735                 t *= _frames_per_second / rint (frames_per_second());
736         }
737
738         return rint (t);
739 }
740
741 int
742 Film::dcp_length () const
743 {
744         if (dcp_frames()) {
745                 return dcp_frames();
746         }
747
748         return length();
749 }
750
751 /** @return a DCI-compliant name for a DCP of this film */
752 string
753 Film::dci_name () const
754 {
755         boost::mutex::scoped_lock lm (_state_mutex);
756         
757         stringstream d;
758
759         string fixed_name = to_upper_copy (_name);
760         for (size_t i = 0; i < fixed_name.length(); ++i) {
761                 if (fixed_name[i] == ' ') {
762                         fixed_name[i] = '-';
763                 }
764         }
765
766         /* Spec is that the name part should be maximum 14 characters, as I understand it */
767         if (fixed_name.length() > 14) {
768                 fixed_name = fixed_name.substr (0, 14);
769         }
770
771         d << fixed_name << "_";
772
773         if (_dcp_content_type) {
774                 d << _dcp_content_type->dci_name() << "_";
775         }
776
777         if (_format) {
778                 d << _format->dci_name() << "_";
779         }
780
781         if (!_audio_language.empty ()) {
782                 d << _audio_language;
783                 if (!_subtitle_language.empty() && _with_subtitles) {
784                         d << "-" << _subtitle_language;
785                 } else {
786                         d << "-XX";
787                 }
788                         
789                 d << "_";
790         }
791
792         if (!_territory.empty ()) {
793                 d << _territory;
794                 if (!_rating.empty ()) {
795                         d << "-" << _rating;
796                 }
797                 d << "_";
798         }
799
800         switch (_audio_streams[_audio_stream].channels()) {
801         case 1:
802                 d << "10_";
803                 break;
804         case 2:
805                 d << "20_";
806                 break;
807         case 6:
808                 d << "51_";
809                 break;
810         case 8:
811                 d << "71_";
812                 break;
813         }
814
815         d << "2K_";
816
817         if (!_studio.empty ()) {
818                 d << _studio << "_";
819         }
820
821         boost::gregorian::date today = boost::gregorian::day_clock::local_day ();
822         d << boost::gregorian::to_iso_string (today) << "_";
823
824         if (!_facility.empty ()) {
825                 d << _facility << "_";
826         }
827
828         if (!_package_type.empty ()) {
829                 d << _package_type;
830         }
831
832         return d.str ();
833 }
834
835 /** @return name to give the DCP */
836 string
837 Film::dcp_name () const
838 {
839         if (use_dci_name()) {
840                 return dci_name ();
841         }
842
843         return name();
844 }
845
846
847 void
848 Film::set_directory (string d)
849 {
850         boost::mutex::scoped_lock lm (_state_mutex);
851         _directory = d;
852         _dirty = true;
853 }
854
855 void
856 Film::set_name (string n)
857 {
858         {
859                 boost::mutex::scoped_lock lm (_state_mutex);
860                 _name = n;
861         }
862         signal_changed (NAME);
863 }
864
865 void
866 Film::set_use_dci_name (bool u)
867 {
868         {
869                 boost::mutex::scoped_lock lm (_state_mutex);
870                 _use_dci_name = u;
871         }
872         signal_changed (USE_DCI_NAME);
873 }
874
875 void
876 Film::set_content (string c)
877 {
878         string check = directory ();
879
880 #if BOOST_FILESYSTEM_VERSION == 3
881         boost::filesystem::path slash ("/");
882         string platform_slash = slash.make_preferred().string ();
883 #else
884 #ifdef DVDOMATIC_WINDOWS
885         string platform_slash = "\\";
886 #else
887         string platform_slash = "/";
888 #endif
889 #endif  
890
891         if (!ends_with (check, platform_slash)) {
892                 check += platform_slash;
893         }
894         
895         if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
896                 c = c.substr (_directory.length() + 1);
897         }
898
899         {
900                 boost::mutex::scoped_lock lm (_state_mutex);
901                 if (c == _content) {
902                         return;
903                 }
904
905                 _content = c;
906         }
907                 
908         /* Create a temporary decoder so that we can get information
909            about the content.
910         */
911         
912
913         shared_ptr<Options> o (new Options ("", "", ""));
914         o->out_size = Size (1024, 1024);
915         
916         shared_ptr<Decoder> d = decoder_factory (shared_from_this(), o, 0, 0);
917         
918         set_size (d->native_size ());
919         set_frames_per_second (d->frames_per_second ());
920         set_audio_sample_rate (d->audio_sample_rate ());
921         set_has_subtitles (d->has_subtitles ());
922         set_audio_streams (d->audio_streams ());
923         set_subtitle_streams (d->subtitle_streams ());
924         set_audio_stream (audio_streams().empty() ? -1 : 0);
925         set_subtitle_stream (subtitle_streams().empty() ? -1 : 0);
926         
927         signal_changed (CONTENT);
928
929         set_content_digest (md5_digest (content_path ()));
930 }
931                
932 void
933 Film::set_dcp_content_type (DCPContentType const * t)
934 {
935         {
936                 boost::mutex::scoped_lock lm (_state_mutex);
937                 _dcp_content_type = t;
938         }
939         signal_changed (DCP_CONTENT_TYPE);
940 }
941
942 void
943 Film::set_format (Format const * f)
944 {
945         {
946                 boost::mutex::scoped_lock lm (_state_mutex);
947                 _format = f;
948         }
949         signal_changed (FORMAT);
950 }
951
952 void
953 Film::set_crop (Crop c)
954 {
955         {
956                 boost::mutex::scoped_lock lm (_state_mutex);
957                 _crop = c;
958         }
959         signal_changed (CROP);
960 }
961
962 void
963 Film::set_left_crop (int c)
964 {
965         {
966                 boost::mutex::scoped_lock lm (_state_mutex);
967                 
968                 if (_crop.left == c) {
969                         return;
970                 }
971                 
972                 _crop.left = c;
973         }
974         signal_changed (CROP);
975 }
976
977 void
978 Film::set_right_crop (int c)
979 {
980         {
981                 boost::mutex::scoped_lock lm (_state_mutex);
982                 if (_crop.right == c) {
983                         return;
984                 }
985                 
986                 _crop.right = c;
987         }
988         signal_changed (CROP);
989 }
990
991 void
992 Film::set_top_crop (int c)
993 {
994         {
995                 boost::mutex::scoped_lock lm (_state_mutex);
996                 if (_crop.top == c) {
997                         return;
998                 }
999                 
1000                 _crop.top = c;
1001         }
1002         signal_changed (CROP);
1003 }
1004
1005 void
1006 Film::set_bottom_crop (int c)
1007 {
1008         {
1009                 boost::mutex::scoped_lock lm (_state_mutex);
1010                 if (_crop.bottom == c) {
1011                         return;
1012                 }
1013                 
1014                 _crop.bottom = c;
1015         }
1016         signal_changed (CROP);
1017 }
1018
1019 void
1020 Film::set_filters (vector<Filter const *> f)
1021 {
1022         {
1023                 boost::mutex::scoped_lock lm (_state_mutex);
1024                 _filters = f;
1025         }
1026         signal_changed (FILTERS);
1027 }
1028
1029 void
1030 Film::set_scaler (Scaler const * s)
1031 {
1032         {
1033                 boost::mutex::scoped_lock lm (_state_mutex);
1034                 _scaler = s;
1035         }
1036         signal_changed (SCALER);
1037 }
1038
1039 void
1040 Film::set_dcp_frames (int f)
1041 {
1042         {
1043                 boost::mutex::scoped_lock lm (_state_mutex);
1044                 _dcp_frames = f;
1045         }
1046         signal_changed (DCP_FRAMES);
1047 }
1048
1049 void
1050 Film::set_dcp_trim_action (TrimAction a)
1051 {
1052         {
1053                 boost::mutex::scoped_lock lm (_state_mutex);
1054                 _dcp_trim_action = a;
1055         }
1056         signal_changed (DCP_TRIM_ACTION);
1057 }
1058
1059 void
1060 Film::set_dcp_ab (bool a)
1061 {
1062         {
1063                 boost::mutex::scoped_lock lm (_state_mutex);
1064                 _dcp_ab = a;
1065         }
1066         signal_changed (DCP_AB);
1067 }
1068
1069 void
1070 Film::set_audio_stream (int s)
1071 {
1072         {
1073                 boost::mutex::scoped_lock lm (_state_mutex);
1074                 _audio_stream = s;
1075         }
1076         signal_changed (AUDIO_STREAM);
1077 }
1078
1079 void
1080 Film::set_audio_gain (float g)
1081 {
1082         {
1083                 boost::mutex::scoped_lock lm (_state_mutex);
1084                 _audio_gain = g;
1085         }
1086         signal_changed (AUDIO_GAIN);
1087 }
1088
1089 void
1090 Film::set_audio_delay (int d)
1091 {
1092         {
1093                 boost::mutex::scoped_lock lm (_state_mutex);
1094                 _audio_delay = d;
1095         }
1096         signal_changed (AUDIO_DELAY);
1097 }
1098
1099 void
1100 Film::set_still_duration (int d)
1101 {
1102         {
1103                 boost::mutex::scoped_lock lm (_state_mutex);
1104                 _still_duration = d;
1105         }
1106         signal_changed (STILL_DURATION);
1107 }
1108
1109 void
1110 Film::set_subtitle_stream (int s)
1111 {
1112         {
1113                 boost::mutex::scoped_lock lm (_state_mutex);
1114                 _subtitle_stream = s;
1115         }
1116         signal_changed (SUBTITLE_STREAM);
1117 }
1118
1119 void
1120 Film::set_with_subtitles (bool w)
1121 {
1122         {
1123                 boost::mutex::scoped_lock lm (_state_mutex);
1124                 _with_subtitles = w;
1125         }
1126         signal_changed (WITH_SUBTITLES);
1127 }
1128
1129 void
1130 Film::set_subtitle_offset (int o)
1131 {
1132         {
1133                 boost::mutex::scoped_lock lm (_state_mutex);
1134                 _subtitle_offset = o;
1135         }
1136         signal_changed (SUBTITLE_OFFSET);
1137 }
1138
1139 void
1140 Film::set_subtitle_scale (float s)
1141 {
1142         {
1143                 boost::mutex::scoped_lock lm (_state_mutex);
1144                 _subtitle_scale = s;
1145         }
1146         signal_changed (SUBTITLE_SCALE);
1147 }
1148
1149 void
1150 Film::set_audio_language (string l)
1151 {
1152         {
1153                 boost::mutex::scoped_lock lm (_state_mutex);
1154                 _audio_language = l;
1155         }
1156         signal_changed (DCI_METADATA);
1157 }
1158
1159 void
1160 Film::set_subtitle_language (string l)
1161 {
1162         {
1163                 boost::mutex::scoped_lock lm (_state_mutex);
1164                 _subtitle_language = l;
1165         }
1166         signal_changed (DCI_METADATA);
1167 }
1168
1169 void
1170 Film::set_territory (string t)
1171 {
1172         {
1173                 boost::mutex::scoped_lock lm (_state_mutex);
1174                 _territory = t;
1175         }
1176         signal_changed (DCI_METADATA);
1177 }
1178
1179 void
1180 Film::set_rating (string r)
1181 {
1182         {
1183                 boost::mutex::scoped_lock lm (_state_mutex);
1184                 _rating = r;
1185         }
1186         signal_changed (DCI_METADATA);
1187 }
1188
1189 void
1190 Film::set_studio (string s)
1191 {
1192         {
1193                 boost::mutex::scoped_lock lm (_state_mutex);
1194                 _studio = s;
1195         }
1196         signal_changed (DCI_METADATA);
1197 }
1198
1199 void
1200 Film::set_facility (string f)
1201 {
1202         {
1203                 boost::mutex::scoped_lock lm (_state_mutex);
1204                 _facility = f;
1205         }
1206         signal_changed (DCI_METADATA);
1207 }
1208
1209 void
1210 Film::set_package_type (string p)
1211 {
1212         {
1213                 boost::mutex::scoped_lock lm (_state_mutex);
1214                 _package_type = p;
1215         }
1216         signal_changed (DCI_METADATA);
1217 }
1218
1219 void
1220 Film::set_thumbs (vector<int> t)
1221 {
1222         {
1223                 boost::mutex::scoped_lock lm (_state_mutex);
1224                 _thumbs = t;
1225         }
1226         signal_changed (THUMBS);
1227 }
1228
1229 void
1230 Film::set_size (Size s)
1231 {
1232         {
1233                 boost::mutex::scoped_lock lm (_state_mutex);
1234                 _size = s;
1235         }
1236         signal_changed (SIZE);
1237 }
1238
1239 void
1240 Film::set_length (int l)
1241 {
1242         {
1243                 boost::mutex::scoped_lock lm (_state_mutex);
1244                 _length = l;
1245         }
1246         signal_changed (LENGTH);
1247 }
1248
1249 void
1250 Film::set_audio_sample_rate (int r)
1251 {
1252         {
1253                 boost::mutex::scoped_lock lm (_state_mutex);
1254                 _audio_sample_rate = r;
1255         }
1256         signal_changed (AUDIO_SAMPLE_RATE);
1257 }
1258
1259 void
1260 Film::set_content_digest (string d)
1261 {
1262         {
1263                 boost::mutex::scoped_lock lm (_state_mutex);
1264                 _content_digest = d;
1265         }
1266         _dirty = true;
1267 }
1268
1269 void
1270 Film::set_has_subtitles (bool s)
1271 {
1272         {
1273                 boost::mutex::scoped_lock lm (_state_mutex);
1274                 _has_subtitles = s;
1275         }
1276         signal_changed (HAS_SUBTITLES);
1277 }
1278
1279 void
1280 Film::set_audio_streams (vector<AudioStream> s)
1281 {
1282         {
1283                 boost::mutex::scoped_lock lm (_state_mutex);
1284                 _audio_streams = s;
1285         }
1286         _dirty = true;
1287 }
1288
1289 void
1290 Film::set_subtitle_streams (vector<SubtitleStream> s)
1291 {
1292         {
1293                 boost::mutex::scoped_lock lm (_state_mutex);
1294                 _subtitle_streams = s;
1295         }
1296         _dirty = true;
1297 }
1298
1299 void
1300 Film::set_frames_per_second (float f)
1301 {
1302         {
1303                 boost::mutex::scoped_lock lm (_state_mutex);
1304                 _frames_per_second = f;
1305         }
1306         signal_changed (FRAMES_PER_SECOND);
1307 }
1308         
1309 void
1310 Film::signal_changed (Property p)
1311 {
1312         {
1313                 boost::mutex::scoped_lock lm (_state_mutex);
1314                 _dirty = true;
1315         }
1316         ui_signaller->emit (boost::bind (boost::ref (Changed), p));
1317 }
1318
1319 int
1320 Film::audio_channels () const
1321 {
1322         boost::mutex::scoped_lock lm (_state_mutex);
1323         if (_audio_stream == -1) {
1324                 return 0;
1325         }
1326         
1327         return _audio_streams[_audio_stream].channels ();
1328 }