Merge branch 'resample-drop-frame'
[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 "film.h"
31 #include "format.h"
32 #include "tiff_encoder.h"
33 #include "job.h"
34 #include "filter.h"
35 #include "transcoder.h"
36 #include "util.h"
37 #include "job_manager.h"
38 #include "ab_transcode_job.h"
39 #include "transcode_job.h"
40 #include "scp_dcp_job.h"
41 #include "copy_from_dvd_job.h"
42 #include "make_dcp_job.h"
43 #include "film_state.h"
44 #include "log.h"
45 #include "options.h"
46 #include "exceptions.h"
47 #include "examine_content_job.h"
48 #include "scaler.h"
49 #include "decoder_factory.h"
50 #include "config.h"
51 #include "check_hashes_job.h"
52
53 using namespace std;
54 using namespace boost;
55
56 /** Construct a Film object in a given directory, reading any metadata
57  *  file that exists in that directory.  An exception will be thrown if
58  *  must_exist is true, and the specified directory does not exist.
59  *
60  *  @param d Film directory.
61  *  @param must_exist true to throw an exception if does not exist.
62  */
63
64 Film::Film (string d, bool must_exist)
65         : _dirty (false)
66 {
67         /* Make _state.directory a complete path without ..s (where possible)
68            (Code swiped from Adam Bowen on stackoverflow)
69         */
70         
71         filesystem::path p (filesystem::system_complete (d));
72         filesystem::path result;
73         for (filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
74                 if (*i == "..") {
75                         if (filesystem::is_symlink (result) || result.filename() == "..") {
76                                 result /= *i;
77                         } else {
78                                 result = result.parent_path ();
79                         }
80                 } else if (*i != ".") {
81                         result /= *i;
82                 }
83         }
84
85         _state.directory = result.string ();
86         
87         if (must_exist && !filesystem::exists (_state.directory)) {
88                 throw OpenFileError (_state.directory);
89         }
90
91         read_metadata ();
92
93         _log = new FileLog (_state.file ("log"));
94 }
95
96 /** Copy constructor */
97 Film::Film (Film const & other)
98         : _state (other._state)
99         , _dirty (other._dirty)
100 {
101
102 }
103
104 Film::~Film ()
105 {
106         delete _log;
107 }
108           
109 /** Read the `metadata' file inside this Film's directory, and fill the
110  *  object's data with its content.
111  */
112
113 void
114 Film::read_metadata ()
115 {
116         ifstream f (metadata_file().c_str ());
117         string line;
118         while (getline (f, line)) {
119                 if (line.empty ()) {
120                         continue;
121                 }
122                 
123                 if (line[0] == '#') {
124                         continue;
125                 }
126
127                 if (line[line.size() - 1] == '\r') {
128                         line = line.substr (0, line.size() - 1);
129                 }
130
131                 size_t const s = line.find (' ');
132                 if (s == string::npos) {
133                         continue;
134                 }
135
136                 _state.read_metadata (line.substr (0, s), line.substr (s + 1));
137         }
138
139         _dirty = false;
140 }
141
142 /** Write our state to a file `metadata' inside the Film's directory */
143 void
144 Film::write_metadata () const
145 {
146         filesystem::create_directories (_state.directory);
147         
148         ofstream f (metadata_file().c_str ());
149         if (!f.good ()) {
150                 throw CreateFileError (metadata_file ());
151         }
152
153         _state.write_metadata (f);
154
155         _dirty = false;
156 }
157
158 /** Set the name by which DVD-o-matic refers to this Film */
159 void
160 Film::set_name (string n)
161 {
162         _state.name = n;
163         signal_changed (NAME);
164 }
165
166 /** Set the content file for this film.
167  *  @param c New content file; if specified as an absolute path, the content should
168  *  be within the film's _state.directory; if specified as a relative path, the content
169  *  will be assumed to be within the film's _state.directory.
170  */
171 void
172 Film::set_content (string c)
173 {
174         string check = _state.directory;
175
176 #if BOOST_FILESYSTEM_VERSION == 3
177         filesystem::path slash ("/");
178         string platform_slash = slash.make_preferred().string ();
179 #else
180 #ifdef DVDOMATIC_WINDOWS
181         string platform_slash = "\\";
182 #else
183         string platform_slash = "/";
184 #endif
185 #endif  
186
187         if (!ends_with (check, platform_slash)) {
188                 check += platform_slash;
189         }
190         
191         if (filesystem::path(c).has_root_directory () && starts_with (c, check)) {
192                 c = c.substr (_state.directory.length() + 1);
193         }
194
195         if (c == _state.content) {
196                 return;
197         }
198         
199         /* Create a temporary decoder so that we can get information
200            about the content.
201         */
202         shared_ptr<FilmState> s = state_copy ();
203         s->content = c;
204         shared_ptr<Options> o (new Options ("", "", ""));
205         o->out_size = Size (1024, 1024);
206
207         shared_ptr<Decoder> d = decoder_factory (s, o, 0, _log);
208         
209         _state.size = d->native_size ();
210         _state.length = d->length_in_frames ();
211         _state.frames_per_second = d->frames_per_second ();
212         _state.audio_channels = d->audio_channels ();
213         _state.audio_sample_rate = d->audio_sample_rate ();
214         _state.audio_sample_format = d->audio_sample_format ();
215
216         _state.content_digest = md5_digest (s->content_path ());
217         _state.content = c;
218         
219         signal_changed (SIZE);
220         signal_changed (LENGTH);
221         signal_changed (FRAMES_PER_SECOND);
222         signal_changed (AUDIO_CHANNELS);
223         signal_changed (AUDIO_SAMPLE_RATE);
224         signal_changed (CONTENT);
225 }
226
227 /** Set the format that this Film should be shown in */
228 void
229 Film::set_format (Format const * f)
230 {
231         _state.format = f;
232         signal_changed (FORMAT);
233 }
234
235 /** Set the type to specify the DCP as having
236  *  (feature, trailer etc.)
237  */
238 void
239 Film::set_dcp_content_type (DCPContentType const * t)
240 {
241         _state.dcp_content_type = t;
242         signal_changed (DCP_CONTENT_TYPE);
243 }
244
245 /** Set the number of pixels by which to crop the left of the source video */
246 void
247 Film::set_left_crop (int c)
248 {
249         if (c == _state.crop.left) {
250                 return;
251         }
252         
253         _state.crop.left = c;
254         signal_changed (CROP);
255 }
256
257 /** Set the number of pixels by which to crop the right of the source video */
258 void
259 Film::set_right_crop (int c)
260 {
261         if (c == _state.crop.right) {
262                 return;
263         }
264
265         _state.crop.right = c;
266         signal_changed (CROP);
267 }
268
269 /** Set the number of pixels by which to crop the top of the source video */
270 void
271 Film::set_top_crop (int c)
272 {
273         if (c == _state.crop.top) {
274                 return;
275         }
276         
277         _state.crop.top = c;
278         signal_changed (CROP);
279 }
280
281 /** Set the number of pixels by which to crop the bottom of the source video */
282 void
283 Film::set_bottom_crop (int c)
284 {
285         if (c == _state.crop.bottom) {
286                 return;
287         }
288         
289         _state.crop.bottom = c;
290         signal_changed (CROP);
291 }
292
293 /** Set the filters to apply to the image when generating thumbnails
294  *  or a DCP.
295  */
296 void
297 Film::set_filters (vector<Filter const *> const & f)
298 {
299         _state.filters = f;
300         signal_changed (FILTERS);
301 }
302
303 /** Set the number of frames to put in any generated DCP (from
304  *  the start of the film).  0 indicates that all frames should
305  *  be used.
306  */
307 void
308 Film::set_dcp_frames (int n)
309 {
310         _state.dcp_frames = n;
311         signal_changed (DCP_FRAMES);
312 }
313
314 void
315 Film::set_dcp_trim_action (TrimAction a)
316 {
317         _state.dcp_trim_action = a;
318         signal_changed (DCP_TRIM_ACTION);
319 }
320
321 /** Set whether or not to generate a A/B comparison DCP.
322  *  Such a DCP has the left half of its frame as the Film
323  *  content without any filtering or post-processing; the
324  *  right half is rendered with filters and post-processing.
325  */
326 void
327 Film::set_dcp_ab (bool a)
328 {
329         _state.dcp_ab = a;
330         signal_changed (DCP_AB);
331 }
332
333 void
334 Film::set_audio_gain (float g)
335 {
336         _state.audio_gain = g;
337         signal_changed (AUDIO_GAIN);
338 }
339
340 void
341 Film::set_audio_delay (int d)
342 {
343         _state.audio_delay = d;
344         signal_changed (AUDIO_DELAY);
345 }
346
347 /** @return path of metadata file */
348 string
349 Film::metadata_file () const
350 {
351         return _state.file ("metadata");
352 }
353
354 /** @return full path of the content (actual video) file
355  *  of this Film.
356  */
357 string
358 Film::content () const
359 {
360         return _state.content_path ();
361 }
362
363 /** The pre-processing GUI part of a thumbs update.
364  *  Must be called from the GUI thread.
365  */
366 void
367 Film::update_thumbs_pre_gui ()
368 {
369         _state.thumbs.clear ();
370         filesystem::remove_all (_state.dir ("thumbs"));
371
372         /* This call will recreate the directory */
373         _state.dir ("thumbs");
374 }
375
376 /** The post-processing GUI part of a thumbs update.
377  *  Must be called from the GUI thread.
378  */
379 void
380 Film::update_thumbs_post_gui ()
381 {
382         string const tdir = _state.dir ("thumbs");
383         
384         for (filesystem::directory_iterator i = filesystem::directory_iterator (tdir); i != filesystem::directory_iterator(); ++i) {
385
386                 /* Aah, the sweet smell of progress */
387 #if BOOST_FILESYSTEM_VERSION == 3               
388                 string const l = filesystem::path(*i).leaf().generic_string();
389 #else
390                 string const l = i->leaf ();
391 #endif
392                 
393                 size_t const d = l.find (".tiff");
394                 if (d != string::npos) {
395                         _state.thumbs.push_back (atoi (l.substr (0, d).c_str()));
396                 }
397         }
398
399         sort (_state.thumbs.begin(), _state.thumbs.end());
400         
401         write_metadata ();
402         signal_changed (THUMBS);
403 }
404
405 /** @return the number of thumbnail images that we have */
406 int
407 Film::num_thumbs () const
408 {
409         return _state.thumbs.size ();
410 }
411
412 /** @param n A thumb index.
413  *  @return The frame within the Film that it is for.
414  */
415 int
416 Film::thumb_frame (int n) const
417 {
418         return _state.thumb_frame (n);
419 }
420
421 /** @param n A thumb index.
422  *  @return The path to the thumb's image file.
423  */
424 string
425 Film::thumb_file (int n) const
426 {
427         return _state.thumb_file (n);
428 }
429
430 /** @return The path to the directory to write JPEG2000 files to */
431 string
432 Film::j2k_dir () const
433 {
434         assert (format());
435
436         filesystem::path p;
437
438         /* Start with j2c */
439         p /= "j2c";
440
441         pair<string, string> f = Filter::ffmpeg_strings (filters ());
442
443         /* Write stuff to specify the filter / post-processing settings that are in use,
444            so that we don't get confused about J2K files generated using different
445            settings.
446         */
447         stringstream s;
448         s << _state.format->id()
449           << "_" << _state.content_digest
450           << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
451           << "_" << f.first << "_" << f.second
452           << "_" << _state.scaler->id();
453
454         p /= s.str ();
455
456         /* Similarly for the A/B case */
457         if (dcp_ab()) {
458                 stringstream s;
459                 pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
460                 s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
461                 p /= s.str ();
462         }
463         
464         return _state.dir (p.string ());
465 }
466
467 /** Handle a change to the Film's metadata */
468 void
469 Film::signal_changed (Property p)
470 {
471         _dirty = true;
472         Changed (p);
473 }
474
475 /** Add suitable Jobs to the JobManager to create a DCP for this Film.
476  *  @param true to transcode, false to use the WAV and J2K files that are already there.
477  */
478 void
479 Film::make_dcp (bool transcode, int freq)
480 {
481         string const t = name ();
482         if (t.find ("/") != string::npos) {
483                 throw BadSettingError ("name", "cannot contain slashes");
484         }
485         
486         log()->log (String::compose ("DVD-o-matic %1 using %2", DVDOMATIC_VERSION, dependency_version_summary()));
487
488         {
489                 char buffer[128];
490                 gethostname (buffer, sizeof (buffer));
491                 log()->log (String::compose ("Starting to make DCP on %1", buffer));
492         }
493                 
494         if (format() == 0) {
495                 throw MissingSettingError ("format");
496         }
497
498         if (content().empty ()) {
499                 throw MissingSettingError ("content");
500         }
501
502         if (dcp_content_type() == 0) {
503                 throw MissingSettingError ("content type");
504         }
505
506         if (name().empty()) {
507                 throw MissingSettingError ("name");
508         }
509
510         shared_ptr<const FilmState> fs = state_copy ();
511         shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", _state.dir ("wavs")));
512         o->out_size = format()->dcp_size ();
513         if (dcp_frames() == 0) {
514                 /* Decode the whole film, no blacking */
515                 o->num_frames = 0;
516                 o->black_after = 0;
517         } else {
518                 switch (dcp_trim_action()) {
519                 case CUT:
520                         /* Decode only part of the film, no blacking */
521                         o->num_frames = dcp_frames ();
522                         o->black_after = 0;
523                         break;
524                 case BLACK_OUT:
525                         /* Decode the whole film, but black some frames out */
526                         o->num_frames = 0;
527                         o->black_after = dcp_frames ();
528                 }
529         }
530         
531         o->decode_video_frequency = freq;
532         o->padding = format()->dcp_padding ();
533         o->ratio = format()->ratio_as_float ();
534
535         if (transcode) {
536                 if (_state.dcp_ab) {
537                         JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (fs, o, log ())));
538                 } else {
539                         JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (fs, o, log ())));
540                 }
541         }
542
543         JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (fs, o, log ())));
544         JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (fs, o, log ())));
545 }
546
547 shared_ptr<FilmState>
548 Film::state_copy () const
549 {
550         return shared_ptr<FilmState> (new FilmState (_state));
551 }
552
553 void
554 Film::copy_from_dvd_post_gui ()
555 {
556         const string dvd_dir = _state.dir ("dvd");
557
558         string largest_file;
559         uintmax_t largest_size = 0;
560         for (filesystem::directory_iterator i = filesystem::directory_iterator (dvd_dir); i != filesystem::directory_iterator(); ++i) {
561                 uintmax_t const s = filesystem::file_size (*i);
562                 if (s > largest_size) {
563
564 #if BOOST_FILESYSTEM_VERSION == 3               
565                         largest_file = filesystem::path(*i).generic_string();
566 #else
567                         largest_file = i->string ();
568 #endif
569                         largest_size = s;
570                 }
571         }
572
573         set_content (largest_file);
574 }
575
576 void
577 Film::examine_content ()
578 {
579         if (_examine_content_job) {
580                 return;
581         }
582         
583         _examine_content_job.reset (new ExamineContentJob (state_copy (), log ()));
584         _examine_content_job->Finished.connect (sigc::mem_fun (*this, &Film::examine_content_post_gui));
585         JobManager::instance()->add (_examine_content_job);
586 }
587
588 void
589 Film::examine_content_post_gui ()
590 {
591         _state.length = _examine_content_job->last_video_frame ();
592         signal_changed (LENGTH);
593         
594         _examine_content_job.reset ();
595 }
596
597 void
598 Film::set_scaler (Scaler const * s)
599 {
600         _state.scaler = s;
601         signal_changed (SCALER);
602 }
603
604 /** @return full paths to any audio files that this Film has */
605 vector<string>
606 Film::audio_files () const
607 {
608         vector<string> f;
609         for (filesystem::directory_iterator i = filesystem::directory_iterator (_state.dir("wavs")); i != filesystem::directory_iterator(); ++i) {
610                 f.push_back (i->path().string ());
611         }
612
613         return f;
614 }
615
616 ContentType
617 Film::content_type () const
618 {
619         return _state.content_type ();
620 }
621
622 void
623 Film::set_still_duration (int d)
624 {
625         _state.still_duration = d;
626         signal_changed (STILL_DURATION);
627 }
628
629 void
630 Film::send_dcp_to_tms ()
631 {
632         shared_ptr<Job> j (new SCPDCPJob (state_copy (), log ()));
633         JobManager::instance()->add (j);
634 }
635
636 void
637 Film::copy_from_dvd ()
638 {
639         shared_ptr<Job> j (new CopyFromDVDJob (state_copy (), log ()));
640         j->Finished.connect (sigc::mem_fun (*this, &Film::copy_from_dvd_post_gui));
641         JobManager::instance()->add (j);
642 }
643
644 int
645 Film::encoded_frames () const
646 {
647         if (format() == 0) {
648                 return 0;
649         }
650
651         int N = 0;
652         for (filesystem::directory_iterator i = filesystem::directory_iterator (j2k_dir ()); i != filesystem::directory_iterator(); ++i) {
653                 ++N;
654                 this_thread::interruption_point ();
655         }
656
657         return N;
658 }