Use MD5 digest of content file as part of the J2C directory, rather than the filename...
[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_digest = md5_digest (c);
197         _state.content = c;
198         
199         signal_changed (SIZE);
200         signal_changed (LENGTH);
201         signal_changed (FRAMES_PER_SECOND);
202         signal_changed (AUDIO_CHANNELS);
203         signal_changed (AUDIO_SAMPLE_RATE);
204         signal_changed (CONTENT);
205 }
206
207 /** Set the format that this Film should be shown in */
208 void
209 Film::set_format (Format const * f)
210 {
211         _state.format = f;
212         signal_changed (FORMAT);
213 }
214
215 /** Set the type to specify the DCP as having
216  *  (feature, trailer etc.)
217  */
218 void
219 Film::set_dcp_content_type (DCPContentType const * t)
220 {
221         _state.dcp_content_type = t;
222         signal_changed (DCP_CONTENT_TYPE);
223 }
224
225 /** Set the number of pixels by which to crop the left of the source video */
226 void
227 Film::set_left_crop (int c)
228 {
229         if (c == _state.left_crop) {
230                 return;
231         }
232         
233         _state.left_crop = c;
234         signal_changed (LEFT_CROP);
235 }
236
237 /** Set the number of pixels by which to crop the right of the source video */
238 void
239 Film::set_right_crop (int c)
240 {
241         if (c == _state.right_crop) {
242                 return;
243         }
244
245         _state.right_crop = c;
246         signal_changed (RIGHT_CROP);
247 }
248
249 /** Set the number of pixels by which to crop the top of the source video */
250 void
251 Film::set_top_crop (int c)
252 {
253         if (c == _state.top_crop) {
254                 return;
255         }
256         
257         _state.top_crop = c;
258         signal_changed (TOP_CROP);
259 }
260
261 /** Set the number of pixels by which to crop the bottom of the source video */
262 void
263 Film::set_bottom_crop (int c)
264 {
265         if (c == _state.bottom_crop) {
266                 return;
267         }
268         
269         _state.bottom_crop = c;
270         signal_changed (BOTTOM_CROP);
271 }
272
273 /** Set the filters to apply to the image when generating thumbnails
274  *  or a DCP.
275  */
276 void
277 Film::set_filters (vector<Filter const *> const & f)
278 {
279         _state.filters = f;
280         signal_changed (FILTERS);
281 }
282
283 /** Set the number of frames to put in any generated DCP (from
284  *  the start of the film).  0 indicates that all frames should
285  *  be used.
286  */
287 void
288 Film::set_dcp_frames (int n)
289 {
290         _state.dcp_frames = n;
291         signal_changed (DCP_FRAMES);
292 }
293
294 void
295 Film::set_dcp_trim_action (TrimAction a)
296 {
297         _state.dcp_trim_action = a;
298         signal_changed (DCP_TRIM_ACTION);
299 }
300
301 /** Set whether or not to generate a A/B comparison DCP.
302  *  Such a DCP has the left half of its frame as the Film
303  *  content without any filtering or post-processing; the
304  *  right half is rendered with filters and post-processing.
305  */
306 void
307 Film::set_dcp_ab (bool a)
308 {
309         _state.dcp_ab = a;
310         signal_changed (DCP_AB);
311 }
312
313 void
314 Film::set_audio_gain (float g)
315 {
316         _state.audio_gain = g;
317         signal_changed (AUDIO_GAIN);
318 }
319
320 void
321 Film::set_audio_delay (int d)
322 {
323         _state.audio_delay = d;
324         signal_changed (AUDIO_DELAY);
325 }
326
327 /** @return path of metadata file */
328 string
329 Film::metadata_file () const
330 {
331         return _state.file ("metadata");
332 }
333
334 /** @return full path of the content (actual video) file
335  *  of this Film.
336  */
337 string
338 Film::content () const
339 {
340         return _state.content_path ();
341 }
342
343 /** The pre-processing GUI part of a thumbs update.
344  *  Must be called from the GUI thread.
345  */
346 void
347 Film::update_thumbs_pre_gui ()
348 {
349         _state.thumbs.clear ();
350         filesystem::remove_all (_state.dir ("thumbs"));
351
352         /* This call will recreate the directory */
353         _state.dir ("thumbs");
354 }
355
356 /** The post-processing GUI part of a thumbs update.
357  *  Must be called from the GUI thread.
358  */
359 void
360 Film::update_thumbs_post_gui ()
361 {
362         string const tdir = _state.dir ("thumbs");
363         
364         for (filesystem::directory_iterator i = filesystem::directory_iterator (tdir); i != filesystem::directory_iterator(); ++i) {
365
366                 /* Aah, the sweet smell of progress */
367 #if BOOST_FILESYSTEM_VERSION == 3               
368                 string const l = filesystem::path(*i).leaf().generic_string();
369 #else
370                 string const l = i->leaf ();
371 #endif
372                 
373                 size_t const d = l.find (".tiff");
374                 if (d != string::npos) {
375                         _state.thumbs.push_back (atoi (l.substr (0, d).c_str()));
376                 }
377         }
378
379         sort (_state.thumbs.begin(), _state.thumbs.end());
380         
381         write_metadata ();
382         signal_changed (THUMBS);
383 }
384
385 /** @return the number of thumbnail images that we have */
386 int
387 Film::num_thumbs () const
388 {
389         return _state.thumbs.size ();
390 }
391
392 /** @param n A thumb index.
393  *  @return The frame within the Film that it is for.
394  */
395 int
396 Film::thumb_frame (int n) const
397 {
398         return _state.thumb_frame (n);
399 }
400
401 /** @param n A thumb index.
402  *  @return The path to the thumb's image file.
403  */
404 string
405 Film::thumb_file (int n) const
406 {
407         return _state.thumb_file (n);
408 }
409
410 /** @return The path to the directory to write JPEG2000 files to */
411 string
412 Film::j2k_dir () const
413 {
414         assert (format());
415
416         stringstream s;
417
418         /* Start with j2c */
419         s << "j2c/";
420
421         pair<string, string> f = Filter::ffmpeg_strings (filters ());
422
423         /* Write stuff to specify the filter / post-processing settings that are in use,
424            so that we don't get confused about J2K files generated using different
425            settings.
426         */
427         s << _state.format->nickname()
428           << "_" << _state.content_digest
429           << "_" << left_crop() << "_" << right_crop() << "_" << top_crop() << "_" << bottom_crop()
430           << "_" << f.first << "_" << f.second
431           << "_" << _state.scaler->id();
432
433         /* Similarly for the A/B case */
434         if (dcp_ab()) {
435                 pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
436                 s << "/ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
437         }
438         
439         return _state.dir (s.str ());
440 }
441
442 /** Handle a change to the Film's metadata */
443 void
444 Film::signal_changed (Property p)
445 {
446         _dirty = true;
447         Changed (p);
448 }
449
450 /** Add suitable Jobs to the JobManager to create a DCP for this Film.
451  *  @param true to transcode, false to use the WAV and J2K files that are already there.
452  */
453 void
454 Film::make_dcp (bool transcode, int freq)
455 {
456         string const t = name ();
457         if (t.find ("/") != string::npos) {
458                 throw BadSettingError ("name", "cannot contain slashes");
459         }
460         
461         {
462                 stringstream s;
463                 s << "DVD-o-matic " << DVDOMATIC_VERSION << " using " << dependency_version_summary ();
464                 log()->log (s.str ());
465         }
466
467         {
468                 char buffer[128];
469                 gethostname (buffer, sizeof (buffer));
470                 stringstream s;
471                 s << "Starting to make a DCP on " << buffer;
472                 log()->log (s.str ());
473         }
474                 
475         if (format() == 0) {
476                 throw MissingSettingError ("format");
477         }
478
479         if (content().empty ()) {
480                 throw MissingSettingError ("content");
481         }
482
483         if (dcp_content_type() == 0) {
484                 throw MissingSettingError ("content type");
485         }
486
487         if (name().empty()) {
488                 throw MissingSettingError ("name");
489         }
490
491         shared_ptr<const FilmState> fs = state_copy ();
492         shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", _state.dir ("wavs")));
493         o->out_size = format()->dcp_size ();
494         if (dcp_frames() == 0) {
495                 /* Decode the whole film, no blacking */
496                 o->num_frames = 0;
497                 o->black_after = 0;
498         } else {
499                 switch (dcp_trim_action()) {
500                 case CUT:
501                         /* Decode only part of the film, no blacking */
502                         o->num_frames = dcp_frames ();
503                         o->black_after = 0;
504                         break;
505                 case BLACK_OUT:
506                         /* Decode the whole film, but black some frames out */
507                         o->num_frames = 0;
508                         o->black_after = dcp_frames ();
509                 }
510         }
511         
512         o->decode_video_frequency = freq;
513         o->padding = format()->dcp_padding ();
514         o->ratio = format()->ratio_as_float ();
515
516         if (transcode) {
517                 if (_state.dcp_ab) {
518                         JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (fs, o, log ())));
519                 } else {
520                         JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (fs, o, log ())));
521                 }
522         }
523         
524         JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (fs, o, log ())));
525 }
526
527 shared_ptr<FilmState>
528 Film::state_copy () const
529 {
530         return shared_ptr<FilmState> (new FilmState (_state));
531 }
532
533 void
534 Film::copy_from_dvd_post_gui ()
535 {
536         const string dvd_dir = _state.dir ("dvd");
537
538         string largest_file;
539         uintmax_t largest_size = 0;
540         for (filesystem::directory_iterator i = filesystem::directory_iterator (dvd_dir); i != filesystem::directory_iterator(); ++i) {
541                 uintmax_t const s = filesystem::file_size (*i);
542                 if (s > largest_size) {
543
544 #if BOOST_FILESYSTEM_VERSION == 3               
545                         largest_file = filesystem::path(*i).generic_string();
546 #else
547                         largest_file = i->string ();
548 #endif
549                         largest_size = s;
550                 }
551         }
552
553         set_content (largest_file);
554 }
555
556 void
557 Film::examine_content ()
558 {
559         if (_examine_content_job) {
560                 return;
561         }
562         
563         _examine_content_job.reset (new ExamineContentJob (state_copy (), log ()));
564         _examine_content_job->Finished.connect (sigc::mem_fun (*this, &Film::examine_content_post_gui));
565         JobManager::instance()->add (_examine_content_job);
566 }
567
568 void
569 Film::examine_content_post_gui ()
570 {
571         _state.length = _examine_content_job->last_video_frame ();
572         signal_changed (LENGTH);
573         
574         _examine_content_job.reset ();
575 }
576
577 void
578 Film::set_scaler (Scaler const * s)
579 {
580         _state.scaler = s;
581         signal_changed (SCALER);
582 }
583
584 void
585 Film::set_frames_per_second (float f)
586 {
587         _state.frames_per_second = f;
588         signal_changed (FRAMES_PER_SECOND);
589 }
590
591 /** @return full paths to any audio files that this Film has */
592 vector<string>
593 Film::audio_files () const
594 {
595         vector<string> f;
596         for (filesystem::directory_iterator i = filesystem::directory_iterator (_state.dir("wavs")); i != filesystem::directory_iterator(); ++i) {
597                 f.push_back (i->path().string ());
598         }
599
600         return f;
601 }
602
603 ContentType
604 Film::content_type () const
605 {
606         return _state.content_type ();
607 }
608
609 void
610 Film::set_still_duration (int d)
611 {
612         _state.still_duration = d;
613         signal_changed (STILL_DURATION);
614 }
615
616 void
617 Film::send_dcp_to_tms ()
618 {
619         shared_ptr<Job> j (new SCPDCPJob (state_copy (), log ()));
620         JobManager::instance()->add (j);
621 }
622
623 void
624 Film::copy_from_dvd ()
625 {
626         shared_ptr<Job> j (new CopyFromDVDJob (state_copy (), log ()));
627         j->Finished.connect (sigc::mem_fun (*this, &Film::copy_from_dvd_post_gui));
628         JobManager::instance()->add (j);
629 }
630