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