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