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