diff options
Diffstat (limited to 'src/lib')
47 files changed, 929 insertions, 723 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc index a6233c185..0efd277bb 100644 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@ -30,12 +30,11 @@ using std::string; using boost::shared_ptr; /** @param f Film to compare. - * @param o Options. + * @param o Decode options. */ -ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req) +ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req) : Job (f, req) - , _decode_opt (od) - , _encode_opt (oe) + , _decode_opt (o) { _film_b.reset (new Film (*_film)); _film_b->set_scaler (Config::instance()->reference_scaler ()); @@ -53,7 +52,7 @@ ABTranscodeJob::run () { try { /* _film_b is the one with reference filters */ - ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film, _encode_opt))); + ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film))); w.go (); set_progress (1); set_state (FINISHED_OK); diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h index 86a2a81b8..983842038 100644 --- a/src/lib/ab_transcode_job.h +++ b/src/lib/ab_transcode_job.h @@ -23,10 +23,9 @@ #include <boost/shared_ptr.hpp> #include "job.h" +#include "options.h" class Film; -class DecodeOptions; -class EncodeOptions; /** @class ABTranscodeJob * @brief Job to run a transcoder which produces output for A/B comparison of various settings. @@ -40,8 +39,7 @@ class ABTranscodeJob : public Job public: ABTranscodeJob ( boost::shared_ptr<Film> f, - boost::shared_ptr<const DecodeOptions> od, - boost::shared_ptr<const EncodeOptions> oe, + DecodeOptions o, boost::shared_ptr<Job> req ); @@ -49,8 +47,7 @@ public: void run (); private: - boost::shared_ptr<const DecodeOptions> _decode_opt; - boost::shared_ptr<const EncodeOptions> _encode_opt; + DecodeOptions _decode_opt; /** Copy of our Film using the reference filters and scaler */ boost::shared_ptr<Film> _film_b; diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc index 53af43b5d..fc4fb8daa 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -49,7 +49,7 @@ using boost::shared_ptr; */ ABTranscoder::ABTranscoder ( - shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e) + shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e) : _film_a (a) , _film_b (b) , _job (j) diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h index 7bfcb393c..58a08af04 100644 --- a/src/lib/ab_transcoder.h +++ b/src/lib/ab_transcoder.h @@ -31,7 +31,6 @@ class Job; class Encoder; class VideoDecoder; class AudioDecoder; -class DecodeOptions; class Image; class Log; class Subtitle; @@ -51,7 +50,7 @@ public: ABTranscoder ( boost::shared_ptr<Film> a, boost::shared_ptr<Film> b, - boost::shared_ptr<const DecodeOptions> o, + DecodeOptions o, Job* j, boost::shared_ptr<Encoder> e ); diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 9d8de971c..a038dd2bb 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -23,7 +23,7 @@ using boost::optional; using boost::shared_ptr; -AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) +AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j) : Decoder (f, o, j) { diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index 013a6327f..3bf585f4d 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -34,7 +34,7 @@ class AudioDecoder : public AudioSource, public virtual Decoder { public: - AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); + AudioDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *); virtual void set_audio_stream (boost::shared_ptr<AudioStream>); diff --git a/src/lib/check_hashes_job.cc b/src/lib/check_hashes_job.cc deleted file mode 100644 index 701584c74..000000000 --- a/src/lib/check_hashes_job.cc +++ /dev/null @@ -1,123 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <fstream> -#include <boost/lexical_cast.hpp> -#include <boost/filesystem.hpp> -#include "check_hashes_job.h" -#include "options.h" -#include "log.h" -#include "job_manager.h" -#include "ab_transcode_job.h" -#include "transcode_job.h" -#include "film.h" -#include "exceptions.h" - -using std::string; -using std::stringstream; -using std::ifstream; -using boost::shared_ptr; - -CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req) - : Job (f, req) - , _decode_opt (od) - , _encode_opt (oe) - , _bad (0) -{ - -} - -string -CheckHashesJob::name () const -{ - return String::compose ("Check hashes of %1", _film->name()); -} - -void -CheckHashesJob::run () -{ - _bad = 0; - - if (!_film->dcp_length()) { - throw EncodeError ("cannot check hashes of a DCP with unknown length"); - } - - SourceFrame const N = _film->dcp_trim_start() + _film->dcp_length().get(); - DCPFrameRate const dfr = dcp_frame_rate (_film->frames_per_second ()); - - for (SourceFrame i = _film->dcp_trim_start(); i < N; i += dfr.skip) { - string const j2k_file = _encode_opt->frame_out_path (i, false); - string const hash_file = _encode_opt->hash_out_path (i, false); - - if (!boost::filesystem::exists (j2k_file)) { - _film->log()->log (String::compose ("Frame %1 has a missing J2K file.", i)); - boost::filesystem::remove (hash_file); - ++_bad; - } else if (!boost::filesystem::exists (hash_file)) { - _film->log()->log (String::compose ("Frame %1 has a missing hash file.", i)); - boost::filesystem::remove (j2k_file); - ++_bad; - } else { - ifstream ref (hash_file.c_str ()); - string hash; - ref >> hash; - if (hash != md5_digest (j2k_file)) { - _film->log()->log (String::compose ("Frame %1 has wrong hash; deleting.", i)); - boost::filesystem::remove (j2k_file); - boost::filesystem::remove (hash_file); - ++_bad; - } - } - - set_progress (float (i) / N); - } - - if (_bad) { - shared_ptr<Job> tc; - - if (_film->dcp_ab()) { - tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this())); - } else { - tc.reset (new TranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this())); - } - - JobManager::instance()->add_after (shared_from_this(), tc); - JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, _encode_opt, tc))); - } - - set_progress (1); - set_state (FINISHED_OK); -} - -string -CheckHashesJob::status () const -{ - stringstream s; - s << Job::status (); - if (overall_progress() > 0) { - if (_bad == 0) { - s << "; no bad frames found"; - } else if (_bad == 1) { - s << "; 1 bad frame found"; - } else { - s << "; " << _bad << " bad frames found"; - } - } - return s.str (); -} diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h deleted file mode 100644 index c41af9d3f..000000000 --- a/src/lib/check_hashes_job.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include "job.h" - -class DecodeOptions; -class EncodeOptions; - -class CheckHashesJob : public Job -{ -public: - CheckHashesJob ( - boost::shared_ptr<Film> f, - boost::shared_ptr<const DecodeOptions> od, - boost::shared_ptr<const EncodeOptions> oe, - boost::shared_ptr<Job> req - ); - - std::string name () const; - void run (); - std::string status () const; - -private: - boost::shared_ptr<const DecodeOptions> _decode_opt; - boost::shared_ptr<const EncodeOptions> _encode_opt; - int _bad; -}; diff --git a/src/lib/config.cc b/src/lib/config.cc index c4659eecf..c165859b0 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -44,6 +44,10 @@ Config::Config () , _tms_path (".") , _sound_processor (SoundProcessor::from_id ("dolby_cp750")) { + _allowed_dcp_frame_rates.push_back (24); + _allowed_dcp_frame_rates.push_back (25); + _allowed_dcp_frame_rates.push_back (30); + ifstream f (file().c_str ()); string line; while (getline (f, line)) { diff --git a/src/lib/config.h b/src/lib/config.h index 98cbf67e5..fed297ad0 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -95,6 +95,10 @@ public: return _sound_processor; } + std::list<int> allowed_dcp_frame_rates () const { + return _allowed_dcp_frame_rates; + } + DCIMetadata default_dci_metadata () const { return _default_dci_metadata; } @@ -146,6 +150,10 @@ public: _tms_password = p; } + void set_allowed_dcp_frame_rates (std::list<int> const & r) { + _allowed_dcp_frame_rates = r; + } + void set_default_dci_metadata (DCIMetadata d) { _default_dci_metadata = d; } @@ -181,6 +189,7 @@ private: std::string _tms_password; /** Our sound processor */ SoundProcessor const * _sound_processor; + std::list<int> _allowed_dcp_frame_rates; /** Default DCI metadata for newly-created Films */ DCIMetadata _default_dci_metadata; diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index 921a1876b..5df2d7a46 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -59,15 +59,17 @@ using std::string; using std::stringstream; using std::ofstream; +using std::cout; using boost::shared_ptr; +using libdcp::Size; /** Construct a DCP video frame. * @param input Input image. * @param out Required size of output, in pixels (including any padding). * @param s Scaler to use. * @param p Number of pixels of padding either side of the image. - * @param f Index of the frame within the Film. - * @param fps Frames per second of the Film. + * @param f Index of the frame within the DCP's intrinsic duration. + * @param fps Frames per second of the Film's source. * @param pp FFmpeg post-processing string to use. * @param clut Colour look-up table to use (see Config::colour_lut_index ()) * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ()) @@ -75,8 +77,8 @@ using boost::shared_ptr; */ DCPVideoFrame::DCPVideoFrame ( shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub, - libdcp::Size out, int p, int subtitle_offset, float subtitle_scale, - Scaler const * s, SourceFrame f, float fps, string pp, int clut, int bw, Log* l + Size out, int p, int subtitle_offset, float subtitle_scale, + Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l ) : _input (yuv) , _subtitle (sub) @@ -86,7 +88,7 @@ DCPVideoFrame::DCPVideoFrame ( , _subtitle_scale (subtitle_scale) , _scaler (s) , _frame (f) - , _frames_per_second (dcp_frame_rate(fps).frames_per_second) + , _frames_per_second (DCPFrameRate(fps).frames_per_second) , _post_process (pp) , _colour_lut (clut) , _j2k_bandwidth (bw) @@ -371,34 +373,63 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv) return e; } +EncodedData::EncodedData (int s) + : _data (new uint8_t[s]) + , _size (s) +{ + +} + +EncodedData::EncodedData (string file) +{ + _size = boost::filesystem::file_size (file); + _data = new uint8_t[_size]; + + FILE* f = fopen (file.c_str(), "rb"); + if (!f) { + throw FileError ("could not open file for reading", file); + } + + fread (_data, 1, _size, f); + fclose (f); +} + + +EncodedData::~EncodedData () +{ + delete[] _data; +} + /** Write this data to a J2K file. - * @param opt Options. - * @param frame Frame index. + * @param Film Film. + * @param frame DCP frame index. */ void -EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame) +EncodedData::write (shared_ptr<const Film> film, int frame) const { - string const tmp_j2k = opt->frame_out_path (frame, true); + string const tmp_j2c = film->j2c_path (frame, true); - FILE* f = fopen (tmp_j2k.c_str (), "wb"); + FILE* f = fopen (tmp_j2c.c_str (), "wb"); if (!f) { - throw WriteFileError (tmp_j2k, errno); + throw WriteFileError (tmp_j2c, errno); } fwrite (_data, 1, _size, f); fclose (f); - string const real_j2k = opt->frame_out_path (frame, false); + string const real_j2c = film->j2c_path (frame, false); /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */ - boost::filesystem::rename (tmp_j2k, real_j2k); + boost::filesystem::rename (tmp_j2c, real_j2c); +} - /* Write a file containing the hash */ - string const hash = opt->hash_out_path (frame, false); +void +EncodedData::write_hash (shared_ptr<const Film> film, int frame) const +{ + string const hash = film->hash_path (frame); ofstream h (hash.c_str()); h << md5_digest (_data, _size) << "\n"; - h.close (); } /** Send this data to a socket. @@ -413,14 +444,15 @@ EncodedData::send (shared_ptr<Socket> socket) socket->write (_data, _size, 30); } -/** @param s Size of data in bytes */ -RemotelyEncodedData::RemotelyEncodedData (int s) - : EncodedData (new uint8_t[s], s) +LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s) + : EncodedData (s) { - + memcpy (_data, d, s); } -RemotelyEncodedData::~RemotelyEncodedData () +/** @param s Size of data in bytes */ +RemotelyEncodedData::RemotelyEncodedData (int s) + : EncodedData (s) { - delete[] _data; + } diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index c0eff3f35..e988b663a 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -26,7 +26,7 @@ */ class FilmState; -class EncodeOptions; +class Film; class ServerDescription; class Scaler; class Image; @@ -39,18 +39,16 @@ class Subtitle; class EncodedData { public: - /** @param d Data (will not be freed by this class, but may be by subclasses) - * @param s libdcp::Size of data, in bytes. - */ - EncodedData (uint8_t* d, int s) - : _data (d) - , _size (s) - {} + /** @param s Size of data, in bytes */ + EncodedData (int s); + + EncodedData (std::string f); - virtual ~EncodedData () {} + virtual ~EncodedData (); void send (boost::shared_ptr<Socket> socket); - void write (boost::shared_ptr<const EncodeOptions>, SourceFrame); + void write (boost::shared_ptr<const Film>, int) const; + void write_hash (boost::shared_ptr<const Film>, int) const; /** @return data */ uint8_t* data () const { @@ -65,6 +63,10 @@ public: protected: uint8_t* _data; ///< data int _size; ///< data size in bytes + +private: + /* No copy construction */ + EncodedData (EncodedData const &); }; /** @class LocallyEncodedData @@ -75,12 +77,10 @@ protected: class LocallyEncodedData : public EncodedData { public: - /** @param d Data (which will not be freed by this class) - * @param s libdcp::Size of data, in bytes. + /** @param d Data (which will be copied by this class) + * @param s Size of data, in bytes. */ - LocallyEncodedData (uint8_t* d, int s) - : EncodedData (d, s) - {} + LocallyEncodedData (uint8_t* d, int s); }; /** @class RemotelyEncodedData @@ -91,7 +91,6 @@ class RemotelyEncodedData : public EncodedData { public: RemotelyEncodedData (int s); - ~RemotelyEncodedData (); }; /** @class DCPVideoFrame @@ -108,7 +107,7 @@ class DCPVideoFrame public: DCPVideoFrame ( boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size, - int, int, float, Scaler const *, SourceFrame, float, std::string, int, int, Log * + int, int, float, Scaler const *, int, float, std::string, int, int, Log * ); virtual ~DCPVideoFrame (); @@ -116,7 +115,7 @@ public: boost::shared_ptr<EncodedData> encode_locally (); boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *); - SourceFrame frame () const { + int frame () const { return _frame; } @@ -125,12 +124,12 @@ private: boost::shared_ptr<const Image> _input; ///< the input image boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image - libdcp::Size _out_size; ///< the required size of the output, in pixels + libdcp::Size _out_size; ///< the required size of the output, in pixels int _padding; int _subtitle_offset; float _subtitle_scale; Scaler const * _scaler; ///< scaler to use - SourceFrame _frame; ///< frame index within the Film's source + int _frame; ///< frame index within the DCP's intrinsic duration int _frames_per_second; ///< Frames per second that we will use for the DCP (rounded) std::string _post_process; ///< FFmpeg post-processing string to use int _colour_lut; ///< Colour look-up table to use diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 7066b488e..fd0abee41 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -46,10 +46,10 @@ using boost::shared_ptr; using boost::optional; /** @param f Film. - * @param o Options. + * @param o Decode options. * @param j Job that we are running within, or 0 */ -Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j) +Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o, Job* j) : _film (f) , _opt (o) , _job (j) diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 3908afa2f..cc4c87373 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -34,9 +34,9 @@ #include "video_source.h" #include "audio_source.h" #include "film.h" +#include "options.h" class Job; -class DecodeOptions; class Image; class Log; class DelayLine; @@ -54,7 +54,7 @@ class FilterGraph; class Decoder { public: - Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); + Decoder (boost::shared_ptr<Film>, DecodeOptions, Job *); virtual ~Decoder () {} virtual bool pass () = 0; @@ -66,8 +66,8 @@ public: protected: /** our Film */ boost::shared_ptr<Film> _film; - /** our options */ - boost::shared_ptr<const DecodeOptions> _opt; + /** our decode options */ + DecodeOptions _opt; /** associated Job, or 0 */ Job* _job; diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc index 2a0d828e2..c4d818f49 100644 --- a/src/lib/decoder_factory.cc +++ b/src/lib/decoder_factory.cc @@ -36,7 +36,7 @@ using boost::dynamic_pointer_cast; Decoders decoder_factory ( - shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j + shared_ptr<Film> f, DecodeOptions o, Job* j ) { if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) { diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h index 47d977ce7..445a1c8a2 100644 --- a/src/lib/decoder_factory.h +++ b/src/lib/decoder_factory.h @@ -24,8 +24,9 @@ * @brief A method to create appropriate decoders for some content. */ +#include "options.h" + class Film; -class DecodeOptions; class Job; class VideoDecoder; class AudioDecoder; @@ -43,7 +44,7 @@ struct Decoders { }; extern Decoders decoder_factory ( - boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job * + boost::shared_ptr<Film>, DecodeOptions, Job * ); #endif diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 910d7c58e..978d787e8 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -24,6 +24,7 @@ #include <iostream> #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> +#include <libdcp/picture_asset.h> #include "encoder.h" #include "util.h" #include "options.h" @@ -34,7 +35,9 @@ #include "config.h" #include "dcp_video_frame.h" #include "server.h" +#include "format.h" #include "cross.h" +#include "writer.h" using std::pair; using std::string; @@ -50,41 +53,25 @@ int const Encoder::_history_size = 25; /** @param f Film that we are encoding. * @param o Options. */ -Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) +Encoder::Encoder (shared_ptr<Film> f) : _film (f) - , _opt (o) - , _just_skipped (false) - , _video_frame (0) - , _audio_frame (0) + , _video_frames_in (0) + , _video_frames_out (0) #ifdef HAVE_SWRESAMPLE , _swr_context (0) -#endif - , _audio_frames_written (0) - , _process_end (false) +#endif + , _have_a_real_frame (false) + , _terminate_encoder (false) { - if (_film->audio_stream()) { - /* Create sound output files with .tmp suffixes; we will rename - them if and when we complete. - */ - for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) { - SF_INFO sf_info; - sf_info.samplerate = dcp_audio_sample_rate (_film->audio_stream()->sample_rate()); - /* We write mono files */ - sf_info.channels = 1; - sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24; - SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info); - if (f == 0) { - throw CreateFileError (_opt->multichannel_audio_out_path (i, true)); - } - _sound_files.push_back (f); - } - } + } Encoder::~Encoder () { - close_sound_files (); terminate_worker_threads (); + if (_writer) { + _writer->finish (); + } } void @@ -130,6 +117,8 @@ Encoder::process_begin () _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i))); } } + + _writer.reset (new Writer (_film)); } @@ -153,32 +142,20 @@ Encoder::process_end () } out->set_frames (frames); - write_audio (out); + _writer->write (out); } swr_free (&_swr_context); } #endif - if (_film->audio_stream()) { - close_sound_files (); - - /* Rename .wav.tmp files to .wav */ - for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) { - if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) { - boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false)); - } - boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false)); - } - } - boost::mutex::scoped_lock lock (_worker_mutex); - _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ())); + _film->log()->log ("Clearing queue of " + lexical_cast<string> (_encode_queue.size ())); /* Keep waking workers until the queue is empty */ - while (!_queue.empty ()) { - _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE); + while (!_encode_queue.empty ()) { + _film->log()->log ("Waking with " + lexical_cast<string> (_encode_queue.size ()), Log::VERBOSE); _worker_condition.notify_all (); _worker_condition.wait (lock); } @@ -187,7 +164,7 @@ Encoder::process_end () terminate_worker_threads (); - _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size())); + _film->log()->log ("Mopping up " + lexical_cast<string> (_encode_queue.size())); /* The following sequence of events can occur in the above code: 1. a remote worker takes the last image off the queue @@ -198,22 +175,18 @@ Encoder::process_end () So just mop up anything left in the queue here. */ - for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { + for (list<shared_ptr<DCPVideoFrame> >::iterator i = _encode_queue.begin(); i != _encode_queue.end(); ++i) { _film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ())); try { - shared_ptr<EncodedData> e = (*i)->encode_locally (); - e->write (_opt, (*i)->frame ()); + _writer->write ((*i)->encode_locally(), (*i)->frame ()); frame_done (); } catch (std::exception& e) { _film->log()->log (String::compose ("Local encode failed (%1)", e.what ())); } } - /* Now do links (or copies on windows) to duplicate frames */ - for (list<pair<int, int> >::iterator i = _links_required.begin(); i != _links_required.end(); ++i) { - link (_opt->frame_out_path (i->first, false), _opt->frame_out_path (i->second, false)); - link (_opt->hash_out_path (i->first, false), _opt->hash_out_path (i->second, false)); - } + _writer->finish (); + _writer.reset (); } /** @return an estimate of the current number of frames we are encoding per second, @@ -233,20 +206,12 @@ Encoder::current_frames_per_second () const return _history_size / (seconds (now) - seconds (_time_history.back ())); } -/** @return true if the last frame to be processed was skipped as it already existed */ -bool -Encoder::skipping () const +/** @return Number of video frames that have been sent out */ +int +Encoder::video_frames_out () const { boost::mutex::scoped_lock (_history_mutex); - return _just_skipped; -} - -/** @return Number of video frames that have been received */ -SourceFrame -Encoder::video_frame () const -{ - boost::mutex::scoped_lock (_history_mutex); - return _video_frame; + return _video_frames_out; } /** Should be called when a frame has been encoded successfully. @@ -256,7 +221,6 @@ void Encoder::frame_done () { boost::mutex::scoped_lock lock (_history_mutex); - _just_skipped = false; struct timeval tv; gettimeofday (&tv, 0); @@ -266,105 +230,64 @@ Encoder::frame_done () } } -/** Called by a subclass when it has just skipped the processing - of a frame because it has already been done. -*/ -void -Encoder::frame_skipped () -{ - boost::mutex::scoped_lock lock (_history_mutex); - _just_skipped = true; -} - void Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub) { - if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) { - ++_video_frame; + DCPFrameRate dfr (_film->frames_per_second ()); + + if (dfr.skip && (_video_frames_in % 2)) { + ++_video_frames_in; return; } - if (_opt->video_range) { - pair<SourceFrame, SourceFrame> const r = _opt->video_range.get(); - if (_video_frame < r.first || _video_frame >= r.second) { - ++_video_frame; - return; - } - } - boost::mutex::scoped_lock lock (_worker_mutex); /* Wait until the queue has gone down a bit */ - while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) { - TIMING ("decoder sleeps with queue of %1", _queue.size()); + while (_encode_queue.size() >= _worker_threads.size() * 2 && !_terminate_encoder) { + TIMING ("decoder sleeps with queue of %1", _encode_queue.size()); _worker_condition.wait (lock); - TIMING ("decoder wakes with queue of %1", _queue.size()); + TIMING ("decoder wakes with queue of %1", _encode_queue.size()); } - if (_process_end) { + if (_terminate_encoder) { return; } - /* Only do the processing if we don't already have a file for this frame */ - if (boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) { - frame_skipped (); - return; - } - - if (same && _last_real_frame) { - /* Use the last frame that we encoded. We need to postpone doing the actual link, - as on windows the link is really a copy and the reference frame might not have - finished encoding yet. - */ - _links_required.push_back (make_pair (_last_real_frame.get(), _video_frame)); + if (same && _have_a_real_frame) { + /* Use the last frame that we encoded. */ + _writer->repeat (_video_frames_out); + frame_done (); } else { /* Queue this new frame for encoding */ pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); - TIMING ("adding to queue of %1", _queue.size ()); - _queue.push_back (boost::shared_ptr<DCPVideoFrame> ( + TIMING ("adding to queue of %1", _encode_queue.size ()); + _encode_queue.push_back (boost::shared_ptr<DCPVideoFrame> ( new DCPVideoFrame ( - image, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(), - _film->scaler(), _video_frame, _film->frames_per_second(), s.second, + image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film), + _film->subtitle_offset(), _film->subtitle_scale(), + _film->scaler(), _video_frames_out, _film->frames_per_second(), s.second, _film->colour_lut(), _film->j2k_bandwidth(), _film->log() ) )); _worker_condition.notify_all (); - _last_real_frame = _video_frame; + _have_a_real_frame = true; } - ++_video_frame; + ++_video_frames_in; + ++_video_frames_out; + + if (dfr.repeat) { + _writer->repeat (_video_frames_out); + ++_video_frames_out; + frame_done (); + } } void Encoder::process_audio (shared_ptr<AudioBuffers> data) { - if (_opt->audio_range) { - shared_ptr<AudioBuffers> trimmed (new AudioBuffers (*data.get ())); - - /* Range that we are encoding */ - pair<int64_t, int64_t> required_range = _opt->audio_range.get(); - /* Range of this block of data */ - pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames()); - - if (this_range.second < required_range.first || required_range.second < this_range.first) { - /* No part of this audio is within the required range */ - _audio_frame += data->frames(); - return; - } else if (required_range.first >= this_range.first && required_range.first < this_range.second) { - /* Trim start */ - int64_t const shift = required_range.first - this_range.first; - trimmed->move (shift, 0, trimmed->frames() - shift); - trimmed->set_frames (trimmed->frames() - shift); - } else if (required_range.second >= this_range.first && required_range.second < this_range.second) { - /* Trim end */ - trimmed->set_frames (required_range.second - this_range.first); - } - - data = trimmed; - } - #if HAVE_SWRESAMPLE /* Maybe sample-rate convert */ if (_swr_context) { @@ -406,36 +329,14 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data) data = b; } - write_audio (data); - - _audio_frame += data->frames (); -} - -void -Encoder::write_audio (shared_ptr<const AudioBuffers> audio) -{ - for (int i = 0; i < audio->channels(); ++i) { - sf_write_float (_sound_files[i], audio->data(i), audio->frames()); - } - - _audio_frames_written += audio->frames (); + _writer->write (data); } void -Encoder::close_sound_files () -{ - for (vector<SNDFILE*>::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) { - sf_close (*i); - } - - _sound_files.clear (); -} - -void Encoder::terminate_worker_threads () { boost::mutex::scoped_lock lock (_worker_mutex); - _process_end = true; + _terminate_encoder = true; _worker_condition.notify_all (); lock.unlock (); @@ -458,18 +359,18 @@ Encoder::encoder_thread (ServerDescription* server) TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id()); boost::mutex::scoped_lock lock (_worker_mutex); - while (_queue.empty () && !_process_end) { + while (_encode_queue.empty () && !_terminate_encoder) { _worker_condition.wait (lock); } - if (_process_end) { + if (_terminate_encoder) { return; } - TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); - boost::shared_ptr<DCPVideoFrame> vf = _queue.front (); + TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _encode_queue.size()); + boost::shared_ptr<DCPVideoFrame> vf = _encode_queue.front (); _film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE); - _queue.pop_front (); + _encode_queue.pop_front (); lock.unlock (); @@ -509,14 +410,14 @@ Encoder::encoder_thread (ServerDescription* server) } if (encoded) { - encoded->write (_opt, vf->frame ()); + _writer->write (encoded, vf->frame ()); frame_done (); } else { lock.lock (); _film->log()->log ( String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame()) ); - _queue.push_front (vf); + _encode_queue.push_front (vf); lock.unlock (); } @@ -528,18 +429,3 @@ Encoder::encoder_thread (ServerDescription* server) _worker_condition.notify_all (); } } - -void -Encoder::link (string a, string b) const -{ -#ifdef DVDOMATIC_POSIX - int const r = symlink (a.c_str(), b.c_str()); - if (r) { - throw EncodeError (String::compose ("could not create symlink from %1 to %2", a, b)); - } -#endif - -#ifdef DVDOMATIC_WINDOWS - boost::filesystem::copy_file (a, b); -#endif -} diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 52ccfc166..8b02f7004 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -39,18 +39,18 @@ extern "C" { #include <libswresample/swresample.h> } #endif -#include <sndfile.h> #include "util.h" #include "video_sink.h" #include "audio_sink.h" -class EncodeOptions; class Image; class Subtitle; class AudioBuffers; class Film; class ServerDescription; class DCPVideoFrame; +class EncodedData; +class Writer; /** @class Encoder * @brief Encoder to J2K and WAV for DCP. @@ -62,7 +62,7 @@ class DCPVideoFrame; class Encoder : public VideoSink, public AudioSink { public: - Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o); + Encoder (boost::shared_ptr<Film> f); virtual ~Encoder (); /** Called to indicate that a processing run is about to begin */ @@ -82,20 +82,21 @@ public: virtual void process_end (); float current_frames_per_second () const; - bool skipping () const; - SourceFrame video_frame () const; + int video_frames_out () const; -protected: +private: void frame_done (); - void frame_skipped (); + void write_audio (boost::shared_ptr<const AudioBuffers> audio); + + void encoder_thread (ServerDescription *); + void terminate_worker_threads (); + /** Film that we are encoding */ - boost::shared_ptr<const Film> _film; - /** Options */ - boost::shared_ptr<const EncodeOptions> _opt; + boost::shared_ptr<Film> _film; - /** Mutex for _time_history, _just_skipped and _last_frame */ + /** Mutex for _time_history and _last_frame */ mutable boost::mutex _history_mutex; /** List of the times of completion of the last _history_size frames; first is the most recently completed. @@ -103,41 +104,24 @@ protected: std::list<struct timeval> _time_history; /** Number of frames that we should keep history for */ static int const _history_size; - /** true if the last frame we processed was skipped (because it was already done) */ - bool _just_skipped; /** Number of video frames received so far */ - SourceFrame _video_frame; - /** Number of audio frames received so far */ - int64_t _audio_frame; - -private: - void close_sound_files (); - void write_audio (boost::shared_ptr<const AudioBuffers> audio); - - void encoder_thread (ServerDescription *); - void terminate_worker_threads (); - void link (std::string, std::string) const; + SourceFrame _video_frames_in; + /** Number of video frames written for the DCP so far */ + int _video_frames_out; #if HAVE_SWRESAMPLE SwrContext* _swr_context; #endif - /** List of links that we need to create when all frames have been processed; - * such that we need to call link (first, second) for each member of this list. - * In other words, `first' is a `real' frame and `second' should be a link to `first'. - */ - std::list<std::pair<int, int> > _links_required; - - std::vector<SNDFILE*> _sound_files; - int64_t _audio_frames_written; - - boost::optional<int> _last_real_frame; - bool _process_end; - std::list<boost::shared_ptr<DCPVideoFrame> > _queue; + bool _have_a_real_frame; + bool _terminate_encoder; + std::list<boost::shared_ptr<DCPVideoFrame> > _encode_queue; std::list<boost::thread *> _worker_threads; mutable boost::mutex _worker_mutex; boost::condition _worker_condition; + + boost::shared_ptr<Writer> _writer; }; #endif diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc index a783cde33..69a757e2b 100644 --- a/src/lib/examine_content_job.cc +++ b/src/lib/examine_content_job.cc @@ -78,8 +78,8 @@ ExamineContentJob::run () _film->unset_length (); _film->set_crop (Crop ()); - shared_ptr<DecodeOptions> o (new DecodeOptions); - o->decode_audio = false; + DecodeOptions o; + o.decode_audio = false; Decoders decoders = decoder_factory (_film, o, this); @@ -96,8 +96,7 @@ ExamineContentJob::run () /* Get a quick decoder to get the content's length from its header */ - shared_ptr<DecodeOptions> o (new DecodeOptions); - Decoders d = decoder_factory (_film, o, 0); + Decoders d = decoder_factory (_film, DecodeOptions(), 0); _film->set_length (d.video->length()); _film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get())); diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc index 25c8068b6..366051418 100644 --- a/src/lib/external_audio_decoder.cc +++ b/src/lib/external_audio_decoder.cc @@ -31,7 +31,7 @@ using std::cout; using boost::shared_ptr; using boost::optional; -ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) +ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j) : Decoder (f, o, j) , AudioDecoder (f, o, j) { diff --git a/src/lib/external_audio_decoder.h b/src/lib/external_audio_decoder.h index 2558955eb..37e53bca7 100644 --- a/src/lib/external_audio_decoder.h +++ b/src/lib/external_audio_decoder.h @@ -44,7 +44,7 @@ private: class ExternalAudioDecoder : public AudioDecoder { public: - ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); + ExternalAudioDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *); bool pass (); diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index aff3ff666..81f405644 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -58,8 +58,9 @@ using std::list; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; +using libdcp::Size; -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) +FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j) : Decoder (f, o, j) , VideoDecoder (f, o, j) , AudioDecoder (f, o, j) @@ -78,7 +79,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions setup_audio (); setup_subtitle (); - if (!o->video_sync) { + if (!o.video_sync) { _first_video = 0; } } @@ -239,7 +240,7 @@ FFmpegDecoder::pass () filter_and_emit_video (_frame); } - if (_audio_stream && _opt->decode_audio) { + if (_audio_stream && _opt.decode_audio) { while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { int const data_size = av_samples_get_buffer_size ( 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1 @@ -267,14 +268,14 @@ FFmpegDecoder::pass () _film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size)); } - if (_opt->video_sync) { + if (_opt.video_sync) { out_with_sync (); } else { filter_and_emit_video (_frame); } } - } else if (ffa && _packet.stream_index == ffa->id() && _opt->decode_audio) { + } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) { int frame_finished; if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { @@ -323,7 +324,7 @@ FFmpegDecoder::pass () } } - } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) { + } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles && _first_video) { int got_subtitle; AVSubtitle sub; diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 3b564b826..9a4e65ebc 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -86,7 +86,7 @@ private: class FFmpegDecoder : public VideoDecoder, public AudioDecoder { public: - FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); + FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *); ~FFmpegDecoder (); float frames_per_second () const; diff --git a/src/lib/film.cc b/src/lib/film.cc index f5522b74a..bdc4cca73 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -39,7 +39,6 @@ #include "ab_transcode_job.h" #include "transcode_job.h" #include "scp_dcp_job.h" -#include "make_dcp_job.h" #include "log.h" #include "options.h" #include "exceptions.h" @@ -47,7 +46,6 @@ #include "scaler.h" #include "decoder_factory.h" #include "config.h" -#include "check_hashes_job.h" #include "version.h" #include "ui_signaller.h" #include "video_decoder.h" @@ -72,8 +70,9 @@ using boost::to_upper_copy; using boost::ends_with; using boost::starts_with; using boost::optional; +using libdcp::Size; -int const Film::state_version = 1; +int const Film::state_version = 2; /** Construct a Film object in a given directory, reading any metadata * file that exists in that directory. An exception will be thrown if @@ -89,8 +88,8 @@ Film::Film (string d, bool must_exist) , _dcp_content_type (0) , _format (0) , _scaler (Scaler::from_id ("bicubic")) - , _dcp_trim_start (0) - , _dcp_trim_end (0) + , _trim_start (0) + , _trim_end (0) , _dcp_ab (false) , _use_content_audio (true) , _audio_gain (0) @@ -156,8 +155,8 @@ Film::Film (Film const & o) , _crop (o._crop) , _filters (o._filters) , _scaler (o._scaler) - , _dcp_trim_start (o._dcp_trim_start) - , _dcp_trim_end (o._dcp_trim_end) + , _trim_start (o._trim_start) + , _trim_end (o._trim_end) , _dcp_ab (o._dcp_ab) , _content_audio_stream (o._content_audio_stream) , _external_audio (o._external_audio) @@ -174,6 +173,7 @@ Film::Film (Film const & o) , _dci_metadata (o._dci_metadata) , _size (o._size) , _length (o._length) + , _dcp_intrinsic_duration (o._dcp_intrinsic_duration) , _content_digest (o._content_digest) , _content_audio_streams (o._content_audio_streams) , _external_audio_stream (o._external_audio_stream) @@ -188,24 +188,14 @@ Film::~Film () { delete _log; } - -/** @return The path to the directory to write JPEG2000 files to */ + string -Film::j2k_dir () const +Film::video_state_identifier () const { - assert (format()); - - boost::filesystem::path p; - - /* Start with j2c */ - p /= "j2c"; + assert (format ()); pair<string, string> 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 << format()->id() << "_" << content_digest() @@ -215,19 +205,37 @@ Film::j2k_dir () const << "_" << j2k_bandwidth() << "_" << boost::lexical_cast<int> (colour_lut()); - p /= s.str (); - - /* Similarly for the A/B case */ if (dcp_ab()) { - stringstream s; pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; - p /= s.str (); } - + + return s.str (); +} + +/** @return The path to the directory to write video frame hash files to */ +string +Film::hash_dir () const +{ + boost::filesystem::path p; + p /= "hash"; + p /= video_state_identifier (); return dir (p.string()); } +string +Film::video_mxf_dir () const +{ + boost::filesystem::path p; + return dir ("video"); +} + +string +Film::video_mxf_filename () const +{ + return video_state_identifier() + ".mxf"; +} + /** 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. */ @@ -249,7 +257,9 @@ Film::make_dcp (bool transcode) } log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video"))); - log()->log (String::compose ("Content length %1", length().get())); + if (length()) { + log()->log (String::compose ("Content length %1", length().get())); + } log()->log (String::compose ("Content digest %1", content_digest())); log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads())); log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth())); @@ -282,47 +292,18 @@ Film::make_dcp (bool transcode) throw MissingSettingError ("name"); } - shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs"))); - oe->out_size = format()->dcp_size (); - oe->padding = format()->dcp_padding (shared_from_this ()); - if (dcp_length ()) { - oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get()); - if (audio_stream()) { - oe->audio_range = make_pair ( - - video_frames_to_audio_frames ( - oe->video_range.get().first, - dcp_audio_sample_rate (audio_stream()->sample_rate()), - dcp_frame_rate (frames_per_second()).frames_per_second - ), - - video_frames_to_audio_frames ( - oe->video_range.get().second, - dcp_audio_sample_rate (audio_stream()->sample_rate()), - dcp_frame_rate (frames_per_second()).frames_per_second - ) - ); - } - - } - - oe->video_skip = dcp_frame_rate (frames_per_second()).skip; - - shared_ptr<DecodeOptions> od (new DecodeOptions); - od->decode_subtitles = with_subtitles (); + DecodeOptions od; + od.decode_subtitles = with_subtitles (); shared_ptr<Job> r; if (transcode) { if (dcp_ab()) { - r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ()))); + r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, shared_ptr<Job> ()))); } else { - r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ()))); + r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, shared_ptr<Job> ()))); } } - - r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r))); - JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r))); } /** Start a job to examine our content file */ @@ -363,7 +344,7 @@ Film::encoded_frames () const } int N = 0; - for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) { + for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (hash_dir ()); i != boost::filesystem::directory_iterator(); ++i) { ++N; boost::this_thread::interruption_point (); } @@ -406,8 +387,8 @@ Film::write_metadata () const f << "filter " << (*i)->id () << "\n"; } f << "scaler " << _scaler->id () << "\n"; - f << "dcp_trim_start " << _dcp_trim_start << "\n"; - f << "dcp_trim_end " << _dcp_trim_end << "\n"; + f << "trim_start " << _trim_start << "\n"; + f << "trim_end " << _trim_end << "\n"; f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n"; if (_content_audio_stream) { f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n"; @@ -431,6 +412,7 @@ Film::write_metadata () const f << "width " << _size.width << "\n"; f << "height " << _size.height << "\n"; f << "length " << _length.get_value_or(0) << "\n"; + f << "dcp_intrinsic_duration " << _dcp_intrinsic_duration.get_value_or(0) << "\n"; f << "content_digest " << _content_digest << "\n"; for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { @@ -511,10 +493,10 @@ Film::read_metadata () _filters.push_back (Filter::from_id (v)); } else if (k == "scaler") { _scaler = Scaler::from_id (v); - } else if (k == "dcp_trim_start") { - _dcp_trim_start = atoi (v.c_str ()); - } else if (k == "dcp_trim_end") { - _dcp_trim_end = atoi (v.c_str ()); + } else if ( ((!version || version < 2) && k == "trim_start") || k == "trim_start") { + _trim_start = atoi (v.c_str ()); + } else if ( ((!version || version < 2) && k == "trim_end") || k == "trim_end") { + _trim_end = atoi (v.c_str ()); } else if (k == "dcp_ab") { _dcp_ab = (v == "1"); } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) { @@ -563,6 +545,11 @@ Film::read_metadata () if (vv) { _length = vv; } + } else if (k == "dcp_intrinsic_duration") { + int const vv = atoi (v.c_str ()); + if (vv) { + _dcp_intrinsic_duration = vv; + } } else if (k == "content_digest") { _content_digest = v; } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) { @@ -674,30 +661,25 @@ Film::target_audio_sample_rate () const /* Resample to a DCI-approved sample rate */ double t = dcp_audio_sample_rate (audio_stream()->sample_rate()); - DCPFrameRate dfr = dcp_frame_rate (frames_per_second ()); + DCPFrameRate dfr (frames_per_second ()); - /* Compensate for the fact that video will be rounded to the - nearest integer number of frames per second. + /* Compensate if the DCP is being run at a different frame rate + to the source; that is, if the video is run such that it will + look different in the DCP compared to the source (slower or faster). + skip/repeat doesn't come into effect here. */ - if (dfr.run_fast) { - t *= _frames_per_second * dfr.skip / dfr.frames_per_second; + + if (dfr.change_speed) { + t *= _frames_per_second * dfr.factor() / dfr.frames_per_second; } return rint (t); } -boost::optional<int> -Film::dcp_length () const +int +Film::still_duration_in_frames () const { - if (content_type() == STILL) { - return _still_duration * frames_per_second(); - } - - if (!length()) { - return boost::optional<int> (); - } - - return length().get() - dcp_trim_start() - dcp_trim_end(); + return still_duration() * frames_per_second(); } /** @return a DCI-compliant name for a DCP of this film */ @@ -871,8 +853,7 @@ Film::set_content (string c) */ try { - shared_ptr<DecodeOptions> o (new DecodeOptions); - Decoders d = decoder_factory (shared_from_this(), o, 0); + Decoders d = decoder_factory (shared_from_this(), DecodeOptions(), 0); set_size (d.video->native_size ()); set_frames_per_second (d.video->frames_per_second ()); @@ -1047,23 +1028,23 @@ Film::set_scaler (Scaler const * s) } void -Film::set_dcp_trim_start (int t) +Film::set_trim_start (int t) { { boost::mutex::scoped_lock lm (_state_mutex); - _dcp_trim_start = t; + _trim_start = t; } - signal_changed (DCP_TRIM_START); + signal_changed (TRIM_START); } void -Film::set_dcp_trim_end (int t) +Film::set_trim_end (int t) { { boost::mutex::scoped_lock lm (_state_mutex); - _dcp_trim_end = t; + _trim_end = t; } - signal_changed (DCP_TRIM_END); + signal_changed (TRIM_END); } void @@ -1094,8 +1075,7 @@ Film::set_external_audio (vector<string> a) _external_audio = a; } - shared_ptr<DecodeOptions> o (new DecodeOptions); - shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0)); + shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), DecodeOptions(), 0)); if (decoder->audio_stream()) { _external_audio_stream = decoder->audio_stream (); } @@ -1242,7 +1222,17 @@ Film::unset_length () _length = boost::none; } signal_changed (LENGTH); -} +} + +void +Film::set_dcp_intrinsic_duration (int d) +{ + { + boost::mutex::scoped_lock lm (_state_mutex); + _dcp_intrinsic_duration = d; + } + signal_changed (DCP_INTRINSIC_DURATION); +} void Film::set_content_digest (string d) @@ -1323,3 +1313,38 @@ Film::audio_stream () const return _external_audio_stream; } + +string +Film::hash_path (int f) const +{ + boost::filesystem::path p; + p /= hash_dir (); + + stringstream s; + s.width (8); + s << setfill('0') << f << ".md5"; + + p /= s.str(); + return p.string (); +} + +string +Film::j2c_path (int f, bool t) const +{ + boost::filesystem::path p; + p /= "j2c"; + p /= video_state_identifier (); + + stringstream s; + s.width (8); + s << setfill('0') << f << ".j2c"; + + if (t) { + s << ".tmp"; + } + + p /= s.str(); + return p.string (); +} + + diff --git a/src/lib/film.h b/src/lib/film.h index af7ec6701..7c4c72f7b 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -60,7 +60,11 @@ public: Film (Film const &); ~Film (); - std::string j2k_dir () const; + std::string hash_dir () const; + std::string j2c_path (int f, bool t) const; + std::string hash_path (int f) const; + std::string video_mxf_dir () const; + std::string video_mxf_filename () const; void examine_content (); void send_dcp_to_tms (); @@ -88,10 +92,13 @@ public: void read_metadata (); libdcp::Size cropped_size (libdcp::Size) const; - boost::optional<int> dcp_length () const; std::string dci_name () const; std::string dcp_name () const; + boost::optional<int> dcp_intrinsic_duration () const { + return _dcp_intrinsic_duration; + } + /** @return true if our state has changed since we last saved it */ bool dirty () const { return _dirty; @@ -115,8 +122,8 @@ public: CROP, FILTERS, SCALER, - DCP_TRIM_START, - DCP_TRIM_END, + TRIM_START, + TRIM_END, DCP_AB, CONTENT_AUDIO_STREAM, EXTERNAL_AUDIO, @@ -133,6 +140,7 @@ public: DCI_METADATA, SIZE, LENGTH, + DCP_INTRINSIC_DURATION, CONTENT_AUDIO_STREAMS, SUBTITLE_STREAMS, FRAMES_PER_SECOND, @@ -191,14 +199,14 @@ public: return _scaler; } - SourceFrame dcp_trim_start () const { + int trim_start () const { boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_trim_start; + return _trim_start; } - SourceFrame dcp_trim_end () const { + int trim_end () const { boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_trim_end; + return _trim_end; } bool dcp_ab () const { @@ -236,6 +244,8 @@ public: return _still_duration; } + int still_duration_in_frames () const; + boost::shared_ptr<SubtitleStream> subtitle_stream () const { boost::mutex::scoped_lock lm (_state_mutex); return _subtitle_stream; @@ -324,8 +334,8 @@ public: void set_bottom_crop (int); void set_filters (std::vector<Filter const *>); void set_scaler (Scaler const *); - void set_dcp_trim_start (int); - void set_dcp_trim_end (int); + void set_trim_start (int); + void set_trim_end (int); void set_dcp_ab (bool); void set_content_audio_stream (boost::shared_ptr<AudioStream>); void set_external_audio (std::vector<std::string>); @@ -343,6 +353,7 @@ public: void set_size (libdcp::Size); void set_length (SourceFrame); void unset_length (); + void set_dcp_intrinsic_duration (int); void set_content_digest (std::string); void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >); void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >); @@ -367,6 +378,7 @@ private: void signal_changed (Property); void examine_content_finished (); + std::string video_state_identifier () const; /** Complete path to directory containing the film metadata; * must not be relative. @@ -399,9 +411,9 @@ private: /** Scaler algorithm to use */ Scaler const * _scaler; /** Frames to trim off the start of the DCP */ - int _dcp_trim_start; + int _trim_start; /** Frames to trim off the end of the DCP */ - int _dcp_trim_end; + int _trim_end; /** true to create an A/B comparison DCP, where the left half of the image is the video without any filters or post-processing, and the right half has the specified filters and post-processing. @@ -443,10 +455,11 @@ private: /* Data which are cached to speed things up */ - /** libdcp::Size, in pixels, of the source (ignoring cropping) */ + /** Size, in pixels, of the source (ignoring cropping) */ libdcp::Size _size; /** The length of the source, in video frames (as far as we know) */ boost::optional<SourceFrame> _length; + boost::optional<int> _dcp_intrinsic_duration; /** MD5 digest of our content file */ std::string _content_digest; /** The audio streams in our content */ diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 6cd7dc2cb..3a13d93d0 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -47,6 +47,7 @@ using std::stringstream; using std::string; using std::list; using boost::shared_ptr; +using libdcp::Size; /** Construct a FilterGraph for the settings in a film. * @param film Film. diff --git a/src/lib/format.cc b/src/lib/format.cc index 6615e16e0..016c21fde 100644 --- a/src/lib/format.cc +++ b/src/lib/format.cc @@ -35,6 +35,7 @@ using std::setprecision; using std::stringstream; using std::vector; using boost::shared_ptr; +using libdcp::Size; vector<Format const *> Format::_formats; diff --git a/src/lib/image.cc b/src/lib/image.cc index feda09ec5..9223fdc5d 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -42,6 +42,7 @@ extern "C" { using namespace std; using namespace boost; +using libdcp::Size; void Image::swap (Image& other) @@ -95,11 +96,15 @@ Image::components () const } shared_ptr<Image> -Image::scale (libdcp::Size out_size, Scaler const * scaler, bool aligned) const +Image::scale (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) const { assert (scaler); + /* Empirical testing suggests that sws_scale() will crash if + the input image is not aligned. + */ + assert (aligned ()); - shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned)); + shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, result_aligned)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), @@ -124,14 +129,18 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, bool aligned) const * @param scaler Scaler to use. */ shared_ptr<Image> -Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool aligned) const +Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool result_aligned) const { assert (scaler); + /* Empirical testing suggests that sws_scale() will crash if + the input image is not aligned. + */ + assert (aligned ()); libdcp::Size content_size = out_size; content_size.width -= (padding * 2); - shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, aligned)); + shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, result_aligned)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), @@ -152,7 +161,7 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons scheme of things. */ if (padding > 0) { - shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, aligned)); + shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned)); padded_rgb->make_black (); /* XXX: we are cheating a bit here; we know the frame is RGB so we can @@ -244,13 +253,25 @@ Image::make_black () { switch (_pixel_format) { case PIX_FMT_YUV420P: - case PIX_FMT_YUV422P10LE: case PIX_FMT_YUV422P: memset (data()[0], 0, lines(0) * stride()[0]); - memset (data()[1], 0x80, lines(1) * stride()[1]); - memset (data()[2], 0x80, lines(2) * stride()[2]); + memset (data()[1], 0x7f, lines(1) * stride()[1]); + memset (data()[2], 0x7f, lines(2) * stride()[2]); break; + case PIX_FMT_YUV422P10LE: + memset (data()[0], 0, lines(0) * stride()[0]); + for (int i = 1; i < 3; ++i) { + int16_t* p = reinterpret_cast<int16_t*> (data()[i]); + for (int y = 0; y < size().height; ++y) { + for (int x = 0; x < line_size()[i] / 2; ++x) { + p[x] = (1 << 9) - 1; + } + p += stride()[i] / 2; + } + } + break; + case PIX_FMT_RGB24: memset (data()[0], 0, lines(0) * stride()[0]); break; @@ -349,7 +370,7 @@ Image::bytes_per_pixel (int c) const return 0.5; } case PIX_FMT_YUV422P10LE: - if (c == 1) { + if (c == 0) { return 2; } else { return 1; @@ -472,6 +493,12 @@ SimpleImage::size () const return _size; } +bool +SimpleImage::aligned () const +{ + return _aligned; +} + FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b) : Image (p) , _buffer (b) @@ -509,6 +536,13 @@ FilterBufferImage::size () const return libdcp::Size (_buffer->video->w, _buffer->video->h); } +bool +FilterBufferImage::aligned () const +{ + /* XXX? */ + return true; +} + RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im) : SimpleImage (im->pixel_format(), im->size(), false) { diff --git a/src/lib/image.h b/src/lib/image.h index adee8bc4d..23f13a648 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -65,9 +65,11 @@ public: /** @return Array of strides for each line (including any alignment padding bytes) */ virtual int * stride () const = 0; - /** @return libdcp::Size of the image, in pixels */ + /** @return Size of the image, in pixels */ virtual libdcp::Size size () const = 0; + virtual bool aligned () const = 0; + int components () const; int lines (int) const; @@ -107,6 +109,7 @@ public: int * line_size () const; int * stride () const; libdcp::Size size () const; + bool aligned () const; private: /* Not allowed */ @@ -131,6 +134,7 @@ public: int * line_size () const; int * stride () const; libdcp::Size size () const; + bool aligned () const; protected: void allocate (); diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index 5ebd6c8e1..99b9e1d34 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -27,9 +27,10 @@ using std::cout; using boost::shared_ptr; +using libdcp::Size; ImageMagickDecoder::ImageMagickDecoder ( - boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j) + boost::shared_ptr<Film> f, DecodeOptions o, Job* j) : Decoder (f, o, j) , VideoDecoder (f, o, j) { @@ -70,7 +71,7 @@ bool ImageMagickDecoder::pass () { if (_iter == _files.end()) { - if (!_film->dcp_length() || video_frame() >= _film->dcp_length().get()) { + if (video_frame() >= _film->still_duration_in_frames()) { return true; } @@ -97,7 +98,7 @@ ImageMagickDecoder::pass () delete magick_image; - image = image->crop (_film->crop(), false); + image = image->crop (_film->crop(), true); emit_video (image, 0); diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index c4795b003..84a6f15f9 100644 --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@ -26,7 +26,7 @@ namespace Magick { class ImageMagickDecoder : public VideoDecoder { public: - ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); + ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *); float frames_per_second () const { /* We don't know */ diff --git a/src/lib/log.cc b/src/lib/log.cc index 06cff0495..7459700ea 100644 --- a/src/lib/log.cc +++ b/src/lib/log.cc @@ -28,7 +28,7 @@ using namespace std; Log::Log () - : _level (VERBOSE) + : _level (STANDARD) { } diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc index 2b7a080fc..182fb306c 100644 --- a/src/lib/matcher.cc +++ b/src/lib/matcher.cc @@ -81,7 +81,7 @@ Matcher::process_end () _log->log (String::compose ("Emitting %1 frames of black video", black_video_frames)); - shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), false)); + shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true)); black->make_black (); for (int i = 0; i < black_video_frames; ++i) { Video (black, i != 0, shared_ptr<Subtitle>()); diff --git a/src/lib/options.h b/src/lib/options.h index 2f2f44b64..2cd7dffde 100644 --- a/src/lib/options.h +++ b/src/lib/options.h @@ -17,101 +17,12 @@ */ -/** @file src/options.h - * @brief Options for a transcoding operation. - */ - -#include <string> -#include <iomanip> -#include <sstream> -#include <boost/optional.hpp> -#include "util.h" +#ifndef DVDOMATIC_OPTIONS_H +#define DVDOMATIC_OPTIONS_H -/** @class EncodeOptions - * @brief EncodeOptions for an encoding operation. - * - * These are settings which may be different, in different circumstances, for - * the same film; ie they are options for a particular operation. +/** @file src/options.h + * @brief Options for a decoding operation. */ -class EncodeOptions -{ -public: - - EncodeOptions (std::string f, std::string e, std::string m) - : padding (0) - , video_skip (0) - , _frame_out_path (f) - , _frame_out_extension (e) - , _multichannel_audio_out_path (m) - {} - - /** @return The path to write video frames to */ - std::string frame_out_path () const { - return _frame_out_path; - } - - /** @param f Source frame index. - * @param t true to return a temporary file path, otherwise a permanent one. - * @return The path to write this video frame to. - */ - std::string frame_out_path (SourceFrame f, bool t) const { - std::stringstream s; - s << _frame_out_path << "/"; - s.width (8); - s << std::setfill('0') << f << _frame_out_extension; - - if (t) { - s << ".tmp"; - } - - return s.str (); - } - - std::string hash_out_path (SourceFrame f, bool t) const { - return frame_out_path (f, t) + ".md5"; - } - - /** @return Path to write multichannel audio data to */ - std::string multichannel_audio_out_path () const { - return _multichannel_audio_out_path; - } - - /** @param c Audio channel index. - * @param t true to return a temporary file path, otherwise a permanent one. - * @return The path to write this audio file to. - */ - std::string multichannel_audio_out_path (int c, bool t) const { - std::stringstream s; - s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav"; - if (t) { - s << ".tmp"; - } - - return s.str (); - } - - libdcp::Size out_size; ///< size of output images - int padding; ///< number of pixels of padding (in terms of the output size) each side of the image - - /** Range of video frames to encode (in DCP frames) */ - boost::optional<std::pair<int, int> > video_range; - /** Range of audio frames to decode (in the DCP's sampling rate) */ - boost::optional<std::pair<int64_t, int64_t> > audio_range; - - /** Skip frames such that we don't decode any frame where (index % decode_video_skip) != 0; e.g. - * 1 for every frame, 2 for every other frame, etc. - */ - SourceFrame video_skip; - -private: - /** Path of the directory to write video frames to */ - std::string _frame_out_path; - /** Extension to use for video frame files (including the leading .) */ - std::string _frame_out_extension; - /** Path of the directory to write audio files to */ - std::string _multichannel_audio_out_path; -}; - class DecodeOptions { @@ -126,3 +37,5 @@ public: bool decode_subtitles; bool video_sync; }; + +#endif diff --git a/src/lib/server.cc b/src/lib/server.cc index 1bb8f205e..d75ab0fb6 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -45,6 +45,7 @@ using boost::algorithm::is_any_of; using boost::algorithm::split; using boost::thread; using boost::bind; +using libdcp::Size; /** Create a server description from a string of metadata returned from as_metadata(). * @param v Metadata. diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc index b4ac14285..bd5f0c879 100644 --- a/src/lib/subtitle.cc +++ b/src/lib/subtitle.cc @@ -27,6 +27,7 @@ using namespace std; using namespace boost; +using libdcp::Size; /** Construct a TimedSubtitle. This is a subtitle image, position, * and a range of time over which it should be shown. diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index dfb9b1071..e9a59c743 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -37,13 +37,12 @@ using std::setprecision; using boost::shared_ptr; /** @param s Film to use. - * @param o Options. + * @param o Decode options. * @param req Job that must be completed before this job is run. */ -TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req) +TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req) : Job (f, req) - , _decode_opt (od) - , _encode_opt (oe) + , _decode_opt (o) { } @@ -62,13 +61,16 @@ TranscodeJob::run () _film->log()->log ("Transcode job starting"); _film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay())); - _encoder.reset (new Encoder (_film, _encode_opt)); + _encoder.reset (new Encoder (_film)); Transcoder w (_film, _decode_opt, this, _encoder); w.go (); set_progress (1); set_state (FINISHED_OK); + _film->set_dcp_intrinsic_duration (_encoder->video_frames_out ()); + _film->log()->log ("Transcode job completed successfully"); + _film->log()->log (String::compose ("DCP intrinsic duration is %1", _encoder->video_frames_out())); } catch (std::exception& e) { @@ -87,11 +89,6 @@ TranscodeJob::status () const return "0%"; } - if (_encoder->skipping () && !finished ()) { - return "skipping already-encoded frames"; - } - - float const fps = _encoder->current_frames_per_second (); if (fps == 0) { return Job::status (); @@ -116,11 +113,19 @@ TranscodeJob::remaining_time () const return 0; } - if (!_film->dcp_length()) { + if (!_film->length()) { return 0; } + /* Compute approximate proposed length here, as it's only here that we need it */ + int length = _film->length().get(); + DCPFrameRate const dfr (_film->frames_per_second ()); + if (dfr.skip) { + length /= 2; + } + /* If we are repeating it shouldn't affect transcode time, so don't take it into account */ + /* We assume that dcp_length() is valid, if it is set */ - SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame(); + int const left = length - _encoder->video_frames_out(); return left / fps; } diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h index 97f655e15..8f78e7f6a 100644 --- a/src/lib/transcode_job.h +++ b/src/lib/transcode_job.h @@ -23,10 +23,9 @@ #include <boost/shared_ptr.hpp> #include "job.h" +#include "options.h" class Encoder; -class DecodeOptions; -class EncodeOptions; /** @class TranscodeJob * @brief A job which transcodes from one format to another. @@ -34,7 +33,7 @@ class EncodeOptions; class TranscodeJob : public Job { public: - TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> od, boost::shared_ptr<const EncodeOptions> oe, boost::shared_ptr<Job> req); + TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od, boost::shared_ptr<Job> req); std::string name () const; void run (); @@ -44,7 +43,6 @@ protected: int remaining_time () const; private: - boost::shared_ptr<const DecodeOptions> _decode_opt; - boost::shared_ptr<const EncodeOptions> _encode_opt; + DecodeOptions _decode_opt; boost::shared_ptr<Encoder> _encoder; }; diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index 87a1fb3f2..93963761e 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -48,7 +48,7 @@ using boost::dynamic_pointer_cast; * @param j Job that we are running under, or 0. * @param e Encoder to use. */ -Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e) +Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e) : _job (j) , _encoder (e) , _decoders (decoder_factory (f, o, j)) diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index b50113742..786010869 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -36,8 +36,6 @@ class Gain; class VideoDecoder; class AudioDecoder; class DelayLine; -class EncodeOptions; -class DecodeOptions; /** @class Transcoder * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. @@ -50,7 +48,7 @@ class Transcoder public: Transcoder ( boost::shared_ptr<Film> f, - boost::shared_ptr<const DecodeOptions> o, + DecodeOptions o, Job* j, boost::shared_ptr<Encoder> e ); diff --git a/src/lib/util.cc b/src/lib/util.cc index 7f370b896..872985024 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -26,6 +26,7 @@ #include <iomanip> #include <iostream> #include <fstream> +#include <climits> #ifdef DVDOMATIC_POSIX #include <execinfo.h> #include <cxxabi.h> @@ -58,9 +59,11 @@ extern "C" { #include "dcp_content_type.h" #include "filter.h" #include "sound_processor.h" +#include "config.h" using namespace std; using namespace boost; +using libdcp::Size; thread::id ui_thread; @@ -330,25 +333,100 @@ md5_digest (string file) return s.str (); } -/** @param fps Arbitrary frames-per-second value. - * @return DCPFrameRate for this frames-per-second. - */ -DCPFrameRate -dcp_frame_rate (float fps) +static bool about_equal (float a, float b) +{ + /* A film of F seconds at f FPS will be Ff frames; + Consider some delta FPS d, so if we run the same + film at (f + d) FPS it will last F(f + d) seconds. + + Hence the difference in length over the length of the film will + be F(f + d) - Ff frames + = Ff + Fd - Ff frames + = Fd frames + = Fd/f seconds + + So if we accept a difference of 1 frame, ie 1/f seconds, we can + say that + + 1/f = Fd/f + ie 1 = Fd + ie d = 1/F + + So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable + FPS error is 1/F ~= 0.0001 ~= 10-e4 + */ + + return (fabs (a - b) < 1e-4); +} + +class FrameRateCandidate { - DCPFrameRate dfr; +public: + FrameRateCandidate (float source_, int dcp_) + : source (source_) + , dcp (dcp_) + {} - dfr.run_fast = (fps != rint (fps)); - dfr.frames_per_second = rint (fps); - dfr.skip = 1; + bool skip () const { + return !about_equal (source, dcp) && source > dcp; + } + + bool repeat () const { + return !about_equal (source, dcp) && source < dcp; + } + + float source; + int dcp; +}; + +/** @param fps Arbitrary source frames-per-second value */ +/** XXX: this could be slow-ish */ +DCPFrameRate::DCPFrameRate (float source_fps) +{ + list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); + + /* Work out what rates we could manage, including those achieved by using skip / repeat. */ + list<FrameRateCandidate> candidates; + + /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ + for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (*i, *i)); + } + + /* Then the skip/repeat ones */ + for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); + candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); + } + + /* Pick the best one, bailing early if we hit an exact match */ + float error = numeric_limits<float>::max (); + boost::optional<FrameRateCandidate> best; + list<FrameRateCandidate>::iterator i = candidates.begin(); + while (i != candidates.end()) { + + if (about_equal (i->source, source_fps)) { + best = *i; + break; + } + + float const e = fabs (i->source - source_fps); + if (e < error) { + error = e; + best = *i; + } + + ++i; + } - /* XXX: somewhat arbitrary */ - if (fps == 50) { - dfr.frames_per_second = 25; - dfr.skip = 2; + if (!best) { + throw EncodeError ("cannot find a suitable DCP frame rate for this source"); } - return dfr; + frames_per_second = best->dcp; + skip = best->skip (); + repeat = best->repeat (); + change_speed = !about_equal (source_fps * factor(), frames_per_second); } /** @param An arbitrary sampling rate. diff --git a/src/lib/util.h b/src/lib/util.h index 77fb943e0..c4940a5d7 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -62,16 +62,37 @@ typedef int SourceFrame; struct DCPFrameRate { + DCPFrameRate (float); + + /** @return factor by which to multiply a source frame rate + to get the effective rate after any skip or repeat has happened. + */ + float factor () const { + if (skip) { + return 0.5; + } else if (repeat) { + return 2; + } + + return 1; + } + /** frames per second for the DCP */ int frames_per_second; - /** Skip every `skip' frames. e.g. if this is 1, we skip nothing; - * if it's 2, we skip every other frame. - */ - int skip; - /** true if this DCP will run its video faster than the source - * (e.g. if the source is 29.97fps and we will run the DCP at 30fps) + /** true to skip every other frame */ + bool skip; + /** true to repeat every frame once */ + bool repeat; + /** true if this DCP will run its video faster or slower than the source + * without taking into account `repeat'. + * (e.g. change_speed will be true if + * source is 29.97fps, DCP is 30fps + * source is 14.50fps, DCP is 30fps + * but not if + * source is 15.00fps, DCP is 30fps + * source is 12.50fps, DCP is 25fps) */ - bool run_fast; + bool change_speed; }; enum ContentType { @@ -157,7 +178,6 @@ struct Rect extern std::string crop_string (Position, libdcp::Size); extern int dcp_audio_sample_rate (int); -extern DCPFrameRate dcp_frame_rate (float); extern int dcp_audio_channels (int); extern std::string colour_lut_index_to_name (int index); extern int stride_round_up (int, int const *, int); diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index e0a7576ee..0fa16bc32 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -28,7 +28,7 @@ using boost::shared_ptr; using boost::optional; -VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) +VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j) : Decoder (f, o, j) , _video_frame (0) , _last_source_time (0) @@ -57,7 +57,7 @@ void VideoDecoder::repeat_last_video () { if (!_last_image) { - _last_image.reset (new SimpleImage (pixel_format(), native_size(), false)); + _last_image.reset (new SimpleImage (pixel_format(), native_size(), true)); _last_image->make_black (); } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index b18082c69..ef1ab041a 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -27,7 +27,7 @@ class VideoDecoder : public VideoSource, public virtual Decoder { public: - VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); + VideoDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *); /** @return video frames per second, or 0 if unknown */ virtual float frames_per_second () const = 0; diff --git a/src/lib/writer.cc b/src/lib/writer.cc new file mode 100644 index 000000000..3ec88860a --- /dev/null +++ b/src/lib/writer.cc @@ -0,0 +1,302 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <fstream> +#include <libdcp/picture_asset.h> +#include <libdcp/sound_asset.h> +#include <libdcp/picture_frame.h> +#include <libdcp/reel.h> +#include "writer.h" +#include "compose.hpp" +#include "film.h" +#include "format.h" +#include "log.h" +#include "dcp_video_frame.h" + +using std::make_pair; +using std::pair; +using std::string; +using std::ifstream; +using std::cout; +using boost::shared_ptr; + +unsigned int const Writer::_maximum_frames_in_memory = 8; + +Writer::Writer (shared_ptr<Film> f) + : _film (f) + , _first_nonexistant_frame (0) + , _thread (0) + , _finish (false) + , _last_written_frame (-1) +{ + check_existing_picture_mxf (); + + /* Create our picture asset in a subdirectory, named according to the + film's parameters which affect the video output. We will hard-link + it into the DCP later. + */ + + _picture_asset.reset ( + new libdcp::MonoPictureAsset ( + _film->video_mxf_dir (), + _film->video_mxf_filename (), + DCPFrameRate (_film->frames_per_second()).frames_per_second, + _film->format()->dcp_size() + ) + ); + + _picture_asset_writer = _picture_asset->start_write (); + + if (_film->audio_channels() > 0) { + _sound_asset.reset ( + new libdcp::SoundAsset ( + _film->dir (_film->dcp_name()), + "audio.mxf", + DCPFrameRate (_film->frames_per_second()).frames_per_second, + _film->audio_channels(), + dcp_audio_sample_rate (_film->audio_stream()->sample_rate()) + ) + ); + + _sound_asset_writer = _sound_asset->start_write (); + } + + _thread = new boost::thread (boost::bind (&Writer::thread, this)); +} + +void +Writer::write (shared_ptr<const EncodedData> encoded, int frame) +{ + boost::mutex::scoped_lock lock (_mutex); + _queue.push_back (make_pair (encoded, frame)); + _condition.notify_all (); +} + +/** This method is not thread safe */ +void +Writer::write (shared_ptr<const AudioBuffers> audio) +{ + _sound_asset_writer->write (audio->data(), audio->frames()); +} + +struct QueueSorter +{ + bool operator() (pair<shared_ptr<const EncodedData>, int> const & a, pair<shared_ptr<const EncodedData>, int> const & b) { + return a.second < b.second; + } +}; + +void +Writer::thread () +{ + while (1) + { + boost::mutex::scoped_lock lock (_mutex); + + while (1) { + if (_finish || + _queue.size() > _maximum_frames_in_memory || + (!_queue.empty() && _queue.front().second == (_last_written_frame + 1))) { + + break; + } + + TIMING ("writer sleeps with a queue of %1; %2 pending", _queue.size(), _pending.size()); + _condition.wait (lock); + TIMING ("writer wakes with a queue of %1", _queue.size()); + + _queue.sort (QueueSorter ()); + } + + if (_finish && _queue.empty() && _pending.empty()) { + return; + } + + /* Write any frames that we can write; i.e. those that are in sequence */ + while (!_queue.empty() && _queue.front().second == (_last_written_frame + 1)) { + pair<boost::shared_ptr<const EncodedData>, int> encoded = _queue.front (); + _queue.pop_front (); + + lock.unlock (); + _film->log()->log (String::compose ("Writer writes %1 to MXF", encoded.second)); + if (encoded.first) { + _picture_asset_writer->write (encoded.first->data(), encoded.first->size()); + encoded.first->write_hash (_film, encoded.second); + _last_written = encoded.first; + } else { + _picture_asset_writer->write (_last_written->data(), _last_written->size()); + _last_written->write_hash (_film, encoded.second); + } + lock.lock (); + + ++_last_written_frame; + } + + while (_queue.size() > _maximum_frames_in_memory) { + /* Too many frames in memory which can't yet be written to the stream. + Put some to disk. + */ + + pair<boost::shared_ptr<const EncodedData>, int> encoded = _queue.back (); + _queue.pop_back (); + if (!encoded.first) { + /* This is a `repeat-last' frame, so no need to write it to disk */ + continue; + } + + lock.unlock (); + _film->log()->log (String::compose ("Writer full (awaiting %1); pushes %2 to disk", _last_written_frame + 1, encoded.second)); + encoded.first->write (_film, encoded.second); + lock.lock (); + + _pending.push_back (encoded.second); + } + + while (_queue.size() < _maximum_frames_in_memory && !_pending.empty()) { + /* We have some space in memory. Fetch some frames back off disk. */ + + _pending.sort (); + int const fetch = _pending.front (); + + lock.unlock (); + _film->log()->log (String::compose ("Writer pulls %1 back from disk", fetch)); + shared_ptr<const EncodedData> encoded; + if (boost::filesystem::exists (_film->j2c_path (fetch, false))) { + /* It's an actual frame (not a repeat-last); load it in */ + encoded.reset (new EncodedData (_film->j2c_path (fetch, false))); + } + lock.lock (); + + _queue.push_back (make_pair (encoded, fetch)); + _pending.remove (fetch); + } + } + +} + +void +Writer::finish () +{ + if (!_thread) { + return; + } + + boost::mutex::scoped_lock lock (_mutex); + _finish = true; + _condition.notify_all (); + lock.unlock (); + + _thread->join (); + delete _thread; + _thread = 0; + + _picture_asset_writer->finalize (); + + if (_sound_asset_writer) { + _sound_asset_writer->finalize (); + } + + int const frames = _last_written_frame + 1; + int const duration = frames - _film->trim_start() - _film->trim_end(); + + _film->set_dcp_intrinsic_duration (frames); + + _picture_asset->set_entry_point (_film->trim_start ()); + _picture_asset->set_duration (duration); + + /* Hard-link the video MXF into the DCP */ + + boost::filesystem::path from; + from /= _film->video_mxf_dir(); + from /= _film->video_mxf_filename(); + + boost::filesystem::path to; + to /= _film->dir (_film->dcp_name()); + to /= "video.mxf"; + + boost::filesystem::create_hard_link (from, to); + + /* And update the asset */ + + _picture_asset->set_directory (_film->dir (_film->dcp_name ())); + _picture_asset->set_file_name ("video.mxf"); + + if (_sound_asset) { + _sound_asset->set_entry_point (_film->trim_start ()); + _sound_asset->set_duration (duration); + } + + libdcp::DCP dcp (_film->dir (_film->dcp_name())); + DCPFrameRate dfr (_film->frames_per_second ()); + + shared_ptr<libdcp::CPL> cpl ( + new libdcp::CPL (_film->dir (_film->dcp_name()), _film->dcp_name(), _film->dcp_content_type()->libdcp_kind (), frames, dfr.frames_per_second) + ); + + dcp.add_cpl (cpl); + + cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel ( + _picture_asset, + _sound_asset, + shared_ptr<libdcp::SubtitleAsset> () + ) + )); + + dcp.write_xml (); +} + +/** Tell the writer that frame `f' should be a repeat of the frame before it */ +void +Writer::repeat (int f) +{ + boost::mutex::scoped_lock lock (_mutex); + _queue.push_back (make_pair (shared_ptr<const EncodedData> (), f)); +} + +void +Writer::check_existing_picture_mxf () +{ + boost::filesystem::path p; + p /= _film->video_mxf_dir (); + p /= _film->video_mxf_filename (); + if (!boost::filesystem::exists (p)) { + return; + } + + libdcp::MonoPictureAsset existing (_film->video_mxf_dir(), _film->video_mxf_filename()); + + while (_first_nonexistant_frame < existing.intrinsic_duration()) { + + shared_ptr<const libdcp::MonoPictureFrame> f = existing.get_frame (_first_nonexistant_frame); + string const existing_hash = md5_digest (f->j2k_data(), f->j2k_size()); + + ifstream hf (_film->hash_path (_first_nonexistant_frame).c_str ()); + string reference_hash; + hf >> reference_hash; + + if (existing_hash != reference_hash) { + cout << "frame " << _first_nonexistant_frame << " failed hash check.\n"; + break; + } + + cout << "frame " << _first_nonexistant_frame << " ok.\n"; + ++_first_nonexistant_frame; + } +} + diff --git a/src/lib/writer.h b/src/lib/writer.h new file mode 100644 index 000000000..768d63448 --- /dev/null +++ b/src/lib/writer.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <list> +#include <boost/shared_ptr.hpp> +#include <boost/thread.hpp> +#include <boost/thread/condition.hpp> + +class Film; +class EncodedData; +class AudioBuffers; + +namespace libdcp { + class MonoPictureAsset; + class MonoPictureAssetWriter; + class SoundAsset; + class SoundAssetWriter; +} + +class Writer +{ +public: + Writer (boost::shared_ptr<Film>); + + void write (boost::shared_ptr<const EncodedData>, int); + void write (boost::shared_ptr<const AudioBuffers>); + void repeat (int f); + void finish (); + +private: + + void thread (); + void check_existing_picture_mxf (); + + boost::shared_ptr<Film> _film; + int _first_nonexistant_frame; + + boost::thread* _thread; + bool _finish; + std::list<std::pair<boost::shared_ptr<const EncodedData>, int> > _queue; + mutable boost::mutex _mutex; + boost::condition _condition; + boost::shared_ptr<const EncodedData> _last_written; + std::list<int> _pending; + int _last_written_frame; + static const unsigned int _maximum_frames_in_memory; + + boost::shared_ptr<libdcp::MonoPictureAsset> _picture_asset; + boost::shared_ptr<libdcp::MonoPictureAssetWriter> _picture_asset_writer; + boost::shared_ptr<libdcp::SoundAsset> _sound_asset; + boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer; +}; diff --git a/src/lib/wscript b/src/lib/wscript index cada2b0c3..454565cdc 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -14,7 +14,6 @@ def build(bld): ab_transcoder.cc audio_decoder.cc audio_source.cc - check_hashes_job.cc config.cc combiner.cc cross.cc @@ -41,7 +40,6 @@ def build(bld): job_manager.cc log.cc lut.cc - make_dcp_job.cc matcher.cc scp_dcp_job.cc scaler.cc @@ -57,6 +55,7 @@ def build(bld): version.cc video_decoder.cc video_source.cc + writer.cc """ obj.target = 'dvdomatic' |
