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