X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=aa0bfa21192cfb6d1c87c2bf858198c1b283072c;hb=71b7808830e17423453502ed5112b70bf90fff1b;hp=90c410546184fe3c86e2db914388f5ae131b119e;hpb=c55d8bcda8f4da74bbc9489127354211cea8f2ff;p=dcpomatic.git diff --git a/src/lib/film.cc b/src/lib/film.cc index 90c410546..aa0bfa211 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -27,9 +27,10 @@ #include #include #include +#include #include "film.h" #include "format.h" -#include "tiff_encoder.h" +#include "imagemagick_encoder.h" #include "job.h" #include "filter.h" #include "transcoder.h" @@ -49,6 +50,7 @@ #include "decoder_factory.h" #include "config.h" #include "check_hashes_job.h" +#include "version.h" using namespace std; using namespace boost; @@ -62,9 +64,8 @@ using namespace boost; */ Film::Film (string d, bool must_exist) - : _dirty (false) { - /* Make _state.directory a complete path without ..s (where possible) + /* Make state.directory a complete path without ..s (where possible) (Code swiped from Adam Bowen on stackoverflow) */ @@ -82,23 +83,19 @@ Film::Film (string d, bool must_exist) } } - _state.directory = result.string (); + set_directory (result.string ()); - if (must_exist && !filesystem::exists (_state.directory)) { - throw OpenFileError (_state.directory); + if (!filesystem::exists (directory())) { + if (must_exist) { + throw OpenFileError (directory()); + } else { + filesystem::create_directory (directory()); + } } read_metadata (); - _log = new FileLog (_state.file ("log")); -} - -/** Copy constructor */ -Film::Film (Film const & other) - : _state (other._state) - , _dirty (other._dirty) -{ - + _log = new FileLog (file ("log")); } Film::~Film () @@ -106,327 +103,6 @@ Film::~Film () delete _log; } -/** Read the `metadata' file inside this Film's directory, and fill the - * object's data with its content. - */ - -void -Film::read_metadata () -{ - ifstream f (metadata_file().c_str ()); - string line; - while (getline (f, line)) { - if (line.empty ()) { - continue; - } - - if (line[0] == '#') { - continue; - } - - if (line[line.size() - 1] == '\r') { - line = line.substr (0, line.size() - 1); - } - - size_t const s = line.find (' '); - if (s == string::npos) { - continue; - } - - _state.read_metadata (line.substr (0, s), line.substr (s + 1)); - } - - _dirty = false; -} - -/** Write our state to a file `metadata' inside the Film's directory */ -void -Film::write_metadata () const -{ - filesystem::create_directories (_state.directory); - - ofstream f (metadata_file().c_str ()); - if (!f.good ()) { - throw CreateFileError (metadata_file ()); - } - - _state.write_metadata (f); - - _dirty = false; -} - -/** Set the name by which DVD-o-matic refers to this Film */ -void -Film::set_name (string n) -{ - _state.name = n; - signal_changed (NAME); -} - -/** Set the content file for this film. - * @param c New content file; if specified as an absolute path, the content should - * be within the film's _state.directory; if specified as a relative path, the content - * will be assumed to be within the film's _state.directory. - */ -void -Film::set_content (string c) -{ - string check = _state.directory; - -#if BOOST_FILESYSTEM_VERSION == 3 - filesystem::path slash ("/"); - string platform_slash = slash.make_preferred().string (); -#else -#ifdef DVDOMATIC_WINDOWS - string platform_slash = "\\"; -#else - string platform_slash = "/"; -#endif -#endif - - if (!ends_with (check, platform_slash)) { - check += platform_slash; - } - - if (filesystem::path(c).has_root_directory () && starts_with (c, check)) { - c = c.substr (_state.directory.length() + 1); - } - - if (c == _state.content) { - return; - } - - /* Create a temporary decoder so that we can get information - about the content. - */ - shared_ptr s = state_copy (); - s->content = c; - shared_ptr o (new Options ("", "", "")); - o->out_size = Size (1024, 1024); - - shared_ptr d = decoder_factory (s, o, 0, _log); - - _state.size = d->native_size (); - _state.length = d->length_in_frames (); - _state.frames_per_second = d->frames_per_second (); - _state.audio_channels = d->audio_channels (); - _state.audio_sample_rate = d->audio_sample_rate (); - _state.audio_sample_format = d->audio_sample_format (); - - _state.content_digest = md5_digest (s->content_path ()); - _state.content = c; - - signal_changed (SIZE); - signal_changed (LENGTH); - signal_changed (FRAMES_PER_SECOND); - signal_changed (AUDIO_CHANNELS); - signal_changed (AUDIO_SAMPLE_RATE); - signal_changed (CONTENT); -} - -/** Set the format that this Film should be shown in */ -void -Film::set_format (Format const * f) -{ - _state.format = f; - signal_changed (FORMAT); -} - -/** Set the type to specify the DCP as having - * (feature, trailer etc.) - */ -void -Film::set_dcp_content_type (DCPContentType const * t) -{ - _state.dcp_content_type = t; - signal_changed (DCP_CONTENT_TYPE); -} - -/** Set the number of pixels by which to crop the left of the source video */ -void -Film::set_left_crop (int c) -{ - if (c == _state.crop.left) { - return; - } - - _state.crop.left = c; - signal_changed (CROP); -} - -/** Set the number of pixels by which to crop the right of the source video */ -void -Film::set_right_crop (int c) -{ - if (c == _state.crop.right) { - return; - } - - _state.crop.right = c; - signal_changed (CROP); -} - -/** Set the number of pixels by which to crop the top of the source video */ -void -Film::set_top_crop (int c) -{ - if (c == _state.crop.top) { - return; - } - - _state.crop.top = c; - signal_changed (CROP); -} - -/** Set the number of pixels by which to crop the bottom of the source video */ -void -Film::set_bottom_crop (int c) -{ - if (c == _state.crop.bottom) { - return; - } - - _state.crop.bottom = c; - signal_changed (CROP); -} - -/** Set the filters to apply to the image when generating thumbnails - * or a DCP. - */ -void -Film::set_filters (vector const & f) -{ - _state.filters = f; - signal_changed (FILTERS); -} - -/** Set the number of frames to put in any generated DCP (from - * the start of the film). 0 indicates that all frames should - * be used. - */ -void -Film::set_dcp_frames (int n) -{ - _state.dcp_frames = n; - signal_changed (DCP_FRAMES); -} - -void -Film::set_dcp_trim_action (TrimAction a) -{ - _state.dcp_trim_action = a; - signal_changed (DCP_TRIM_ACTION); -} - -/** Set whether or not to generate a A/B comparison DCP. - * Such a DCP has the left half of its frame as the Film - * content without any filtering or post-processing; the - * right half is rendered with filters and post-processing. - */ -void -Film::set_dcp_ab (bool a) -{ - _state.dcp_ab = a; - signal_changed (DCP_AB); -} - -void -Film::set_audio_gain (float g) -{ - _state.audio_gain = g; - signal_changed (AUDIO_GAIN); -} - -void -Film::set_audio_delay (int d) -{ - _state.audio_delay = d; - signal_changed (AUDIO_DELAY); -} - -/** @return path of metadata file */ -string -Film::metadata_file () const -{ - return _state.file ("metadata"); -} - -/** @return full path of the content (actual video) file - * of this Film. - */ -string -Film::content () const -{ - return _state.content_path (); -} - -/** The pre-processing GUI part of a thumbs update. - * Must be called from the GUI thread. - */ -void -Film::update_thumbs_pre_gui () -{ - _state.thumbs.clear (); - filesystem::remove_all (_state.dir ("thumbs")); - - /* This call will recreate the directory */ - _state.dir ("thumbs"); -} - -/** The post-processing GUI part of a thumbs update. - * Must be called from the GUI thread. - */ -void -Film::update_thumbs_post_gui () -{ - string const tdir = _state.dir ("thumbs"); - - for (filesystem::directory_iterator i = filesystem::directory_iterator (tdir); i != filesystem::directory_iterator(); ++i) { - - /* Aah, the sweet smell of progress */ -#if BOOST_FILESYSTEM_VERSION == 3 - string const l = filesystem::path(*i).leaf().generic_string(); -#else - string const l = i->leaf (); -#endif - - size_t const d = l.find (".tiff"); - if (d != string::npos) { - _state.thumbs.push_back (atoi (l.substr (0, d).c_str())); - } - } - - sort (_state.thumbs.begin(), _state.thumbs.end()); - - write_metadata (); - signal_changed (THUMBS); -} - -/** @return the number of thumbnail images that we have */ -int -Film::num_thumbs () const -{ - return _state.thumbs.size (); -} - -/** @param n A thumb index. - * @return The frame within the Film that it is for. - */ -int -Film::thumb_frame (int n) const -{ - return _state.thumb_frame (n); -} - -/** @param n A thumb index. - * @return The path to the thumb's image file. - */ -string -Film::thumb_file (int n) const -{ - return _state.thumb_file (n); -} - /** @return The path to the directory to write JPEG2000 files to */ string Film::j2k_dir () const @@ -438,18 +114,18 @@ Film::j2k_dir () const /* Start with j2c */ p /= "j2c"; - pair f = Filter::ffmpeg_strings (filters ()); + pair f = Filter::ffmpeg_strings (filters()); /* Write stuff to specify the filter / post-processing settings that are in use, so that we don't get confused about J2K files generated using different settings. */ stringstream s; - s << _state.format->id() - << "_" << _state.content_digest + s << format()->id() + << "_" << content_digest() << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom << "_" << f.first << "_" << f.second - << "_" << _state.scaler->id(); + << "_" << scaler()->id(); p /= s.str (); @@ -461,29 +137,20 @@ Film::j2k_dir () const p /= s.str (); } - return _state.dir (p.string ()); -} - -/** Handle a change to the Film's metadata */ -void -Film::signal_changed (Property p) -{ - _dirty = true; - Changed (p); + return dir (p.string()); } /** Add suitable Jobs to the JobManager to create a DCP for this Film. * @param true to transcode, false to use the WAV and J2K files that are already there. */ void -Film::make_dcp (bool transcode, int freq) +Film::make_dcp (bool transcode) { - string const t = name (); - if (t.find ("/") != string::npos) { + if (dcp_name().find ("/") != string::npos) { throw BadSettingError ("name", "cannot contain slashes"); } - log()->log (String::compose ("DVD-o-matic %1 using %2", DVDOMATIC_VERSION, dependency_version_summary())); + log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary())); { char buffer[128]; @@ -508,52 +175,45 @@ Film::make_dcp (bool transcode, int freq) } shared_ptr fs = state_copy (); - shared_ptr o (new Options (j2k_dir(), ".j2c", _state.dir ("wavs"))); + shared_ptr o (new Options (j2k_dir(), ".j2c", dir ("wavs"))); o->out_size = format()->dcp_size (); if (dcp_frames() == 0) { /* Decode the whole film, no blacking */ - o->num_frames = 0; o->black_after = 0; } else { switch (dcp_trim_action()) { case CUT: /* Decode only part of the film, no blacking */ - o->num_frames = dcp_frames (); o->black_after = 0; break; case BLACK_OUT: /* Decode the whole film, but black some frames out */ - o->num_frames = 0; o->black_after = dcp_frames (); } } - o->decode_video_frequency = freq; - o->padding = format()->dcp_padding (); - o->ratio = format()->ratio_as_float (); + o->padding = format()->dcp_padding (this); + o->ratio = format()->ratio_as_float (this); + o->decode_subtitles = with_subtitles (); + + shared_ptr r; if (transcode) { - if (_state.dcp_ab) { - JobManager::instance()->add (shared_ptr (new ABTranscodeJob (fs, o, log ()))); + if (dcp_ab()) { + r = JobManager::instance()->add (shared_ptr (new ABTranscodeJob (fs, o, log(), shared_ptr ()))); } else { - JobManager::instance()->add (shared_ptr (new TranscodeJob (fs, o, log ()))); + r = JobManager::instance()->add (shared_ptr (new TranscodeJob (fs, o, log(), shared_ptr ()))); } } - JobManager::instance()->add (shared_ptr (new CheckHashesJob (fs, o, log ()))); - JobManager::instance()->add (shared_ptr (new MakeDCPJob (fs, o, log ()))); -} - -shared_ptr -Film::state_copy () const -{ - return shared_ptr (new FilmState (_state)); + r = JobManager::instance()->add (shared_ptr (new CheckHashesJob (fs, o, log(), r))); + JobManager::instance()->add (shared_ptr (new MakeDCPJob (fs, o, log(), r))); } void Film::copy_from_dvd_post_gui () { - const string dvd_dir = _state.dir ("dvd"); + const string dvd_dir = dir ("dvd"); string largest_file; uintmax_t largest_size = 0; @@ -573,14 +233,21 @@ Film::copy_from_dvd_post_gui () set_content (largest_file); } +/** Start a job to examine our content file */ void Film::examine_content () { if (_examine_content_job) { return; } + + set_thumbs (vector ()); + filesystem::remove_all (dir ("thumbs")); + + /* This call will recreate the directory */ + dir ("thumbs"); - _examine_content_job.reset (new ExamineContentJob (state_copy (), log ())); + _examine_content_job.reset (new ExamineContentJob (state_copy (), log(), shared_ptr ())); _examine_content_job->Finished.connect (sigc::mem_fun (*this, &Film::examine_content_post_gui)); JobManager::instance()->add (_examine_content_job); } @@ -588,59 +255,64 @@ Film::examine_content () void Film::examine_content_post_gui () { - _state.length = _examine_content_job->last_video_frame (); - signal_changed (LENGTH); - + set_length (_examine_content_job->last_video_frame ()); _examine_content_job.reset (); -} -void -Film::set_scaler (Scaler const * s) -{ - _state.scaler = s; - signal_changed (SCALER); + string const tdir = dir ("thumbs"); + vector thumbs; + + for (filesystem::directory_iterator i = filesystem::directory_iterator (tdir); i != filesystem::directory_iterator(); ++i) { + + /* Aah, the sweet smell of progress */ +#if BOOST_FILESYSTEM_VERSION == 3 + string const l = filesystem::path(*i).leaf().generic_string(); +#else + string const l = i->leaf (); +#endif + + size_t const d = l.find (".png"); + size_t const t = l.find (".tmp"); + if (d != string::npos && t == string::npos) { + thumbs.push_back (atoi (l.substr (0, d).c_str())); + } + } + + sort (thumbs.begin(), thumbs.end()); + set_thumbs (thumbs); } + /** @return full paths to any audio files that this Film has */ vector Film::audio_files () const { vector f; - for (filesystem::directory_iterator i = filesystem::directory_iterator (_state.dir("wavs")); i != filesystem::directory_iterator(); ++i) { + for (filesystem::directory_iterator i = filesystem::directory_iterator (dir("wavs")); i != filesystem::directory_iterator(); ++i) { f.push_back (i->path().string ()); } return f; } -ContentType -Film::content_type () const -{ - return _state.content_type (); -} - -void -Film::set_still_duration (int d) -{ - _state.still_duration = d; - signal_changed (STILL_DURATION); -} - +/** Start a job to send our DCP to the configured TMS */ void Film::send_dcp_to_tms () { - shared_ptr j (new SCPDCPJob (state_copy (), log ())); + shared_ptr j (new SCPDCPJob (state_copy (), log(), shared_ptr ())); JobManager::instance()->add (j); } void Film::copy_from_dvd () { - shared_ptr j (new CopyFromDVDJob (state_copy (), log ())); + shared_ptr j (new CopyFromDVDJob (state_copy (), log(), shared_ptr ())); j->Finished.connect (sigc::mem_fun (*this, &Film::copy_from_dvd_post_gui)); JobManager::instance()->add (j); } +/** Count the number of frames that have been encoded for this film. + * @return frame count. + */ int Film::encoded_frames () const { @@ -656,3 +328,39 @@ Film::encoded_frames () const return N; } + +/** Return the filename of a subtitle image if one exists for a given thumb index. + * @param Thumbnail index. + * @return Position of the image within the source frame, and the image filename, if one exists. + * Otherwise the filename will be empty. + */ +pair +Film::thumb_subtitle (int n) const +{ + string sub_file = thumb_base(n) + ".sub"; + if (!filesystem::exists (sub_file)) { + return pair (); + } + + pair sub; + + ifstream f (sub_file.c_str ()); + multimap kv = read_key_value (f); + for (map::const_iterator i = kv.begin(); i != kv.end(); ++i) { + if (i->first == "x") { + sub.first.x = lexical_cast (i->second); + } else if (i->first == "y") { + sub.first.y = lexical_cast (i->second); + sub.second = String::compose ("%1.sub.png", thumb_base(n)); + } + } + + return sub; +} + +void +Film::set_content (string c) +{ + FilmState::set_content (c); + examine_content (); +}