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