diff options
Diffstat (limited to 'src')
69 files changed, 1318 insertions, 977 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..4f3fda44a 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); - ofstream h (hash.c_str()); - h << md5_digest (_data, _size) << "\n"; - h.close (); +void +EncodedData::write_info (shared_ptr<const Film> film, int frame, libdcp::FrameInfo fin) const +{ + string const info = film->info_path (frame); + ofstream h (info.c_str()); + fin.write (h); } /** 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..be8a559b2 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -19,6 +19,7 @@ */ #include <openjpeg.h> +#include <libdcp/picture_asset.h> #include "util.h" /** @file src/dcp_video_frame.h @@ -26,7 +27,7 @@ */ class FilmState; -class EncodeOptions; +class Film; class ServerDescription; class Scaler; class Image; @@ -39,18 +40,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_info (boost::shared_ptr<const Film>, int, libdcp::FrameInfo) const; /** @return data */ uint8_t* data () const { @@ -65,6 +64,10 @@ public: protected: uint8_t* _data; ///< data int _size; ///< data size in bytes + +private: + /* No copy construction */ + EncodedData (EncodedData const &); }; /** @class LocallyEncodedData @@ -75,12 +78,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 +92,6 @@ class RemotelyEncodedData : public EncodedData { public: RemotelyEncodedData (int s); - ~RemotelyEncodedData (); }; /** @class DCPVideoFrame @@ -108,7 +108,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 +116,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 +125,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/delay_line.cc b/src/lib/delay_line.cc index 45d8e9d9d..4ad172781 100644 --- a/src/lib/delay_line.cc +++ b/src/lib/delay_line.cc @@ -91,12 +91,3 @@ DelayLine::process_audio (shared_ptr<AudioBuffers> data) Audio (data); } - -void -DelayLine::process_end () -{ - if (_frames < 0) { - _buffers->make_silent (); - Audio (_buffers); - } -} diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h index fa2870ae7..4d6f1313b 100644 --- a/src/lib/delay_line.h +++ b/src/lib/delay_line.h @@ -29,7 +29,6 @@ public: DelayLine (Log* log, int channels, int frames); void process_audio (boost::shared_ptr<AudioBuffers>); - void process_end (); private: boost::shared_ptr<AudioBuffers> _buffers; diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 910d7c58e..b92be84a8 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,67 @@ 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 (_writer->can_fake_write (_video_frames_out)) { + _writer->fake_write (_video_frames_out); + _have_a_real_frame = false; + } else 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 +332,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 +362,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 +413,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 +432,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..8720e79e4 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 info files to */ +string +Film::info_dir () const +{ + boost::filesystem::path p; + p /= "info"; + 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 (info_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 */ @@ -828,16 +810,8 @@ Film::set_content (string c) { string check = directory (); -#if BOOST_FILESYSTEM_VERSION == 3 boost::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; @@ -871,8 +845,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 +1020,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 +1067,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 +1214,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 +1305,38 @@ Film::audio_stream () const return _external_audio_stream; } + +string +Film::info_path (int f) const +{ + boost::filesystem::path p; + p /= info_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..5b65a099d 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 info_dir () const; + std::string j2c_path (int f, bool t) const; + std::string info_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.cc b/src/lib/filter.cc index 446cc111d..9a662f90f 100644 --- a/src/lib/filter.cc +++ b/src/lib/filter.cc @@ -22,6 +22,10 @@ */ #include "filter.h" +extern "C" { +#include <libavfilter/avfilter.h> +#include <libpostproc/postprocess.h> +} using namespace std; @@ -29,12 +33,14 @@ vector<Filter const *> Filter::_filters; /** @param i Our id. * @param n User-visible name. + * @param c User-visible category. * @param v String for a FFmpeg video filter descriptor, or "". * @param p String for a FFmpeg post-processing descriptor, or "". */ -Filter::Filter (string i, string n, string v, string p) +Filter::Filter (string i, string n, string c, string v, string p) : _id (i) , _name (n) + , _category (c) , _vf (v) , _pp (p) { @@ -57,30 +63,46 @@ Filter::setup_filters () { /* Note: "none" is a magic id name, so don't use it here */ - _filters.push_back (new Filter ("pphb", "Horizontal deblocking filter", "", "hb")); - _filters.push_back (new Filter ("ppvb", "Vertical deblocking filter", "", "vb")); - _filters.push_back (new Filter ("ppha", "Horizontal deblocking filter A", "", "ha")); - _filters.push_back (new Filter ("ppva", "Vertical deblocking filter A", "", "va")); - _filters.push_back (new Filter ("pph1", "Experimental horizontal deblocking filter 1", "", "h1")); - _filters.push_back (new Filter ("pphv", "Experimental vertical deblocking filter 1", "", "v1")); - _filters.push_back (new Filter ("ppdr", "Deringing filter", "", "dr")); - _filters.push_back (new Filter ("pplb", "Linear blend deinterlacer", "", "lb")); - _filters.push_back (new Filter ("ppli", "Linear interpolating deinterlacer", "", "li")); - _filters.push_back (new Filter ("ppci", "Cubic interpolating deinterlacer", "", "ci")); - _filters.push_back (new Filter ("ppmd", "Median deinterlacer", "", "md")); - _filters.push_back (new Filter ("ppfd", "FFMPEG deinterlacer", "", "fd")); - _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5")); - _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", "")); - _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", "")); - _filters.push_back (new Filter ("yadif", "Yet Another Deinterlacing Filter", "yadif", "")); - _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn")); - _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq")); - _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", "")); - _filters.push_back (new Filter ("unsharp", "Unsharp mask and Gaussian blur", "unsharp", "")); - _filters.push_back (new Filter ("denoise3d", "3D denoiser", "denoise3d", "")); - _filters.push_back (new Filter ("hqdn3d", "High quality 3D denoiser", "hqdn3d", "")); - _filters.push_back (new Filter ("telecine", "Telecine filter", "telecine", "")); - _filters.push_back (new Filter ("ow", "Overcomplete wavelet denoiser", "mp=ow", "")); + maybe_add ("pphb", "Horizontal deblocking filter", "De-blocking", "", "hb"); + maybe_add ("ppvb", "Vertical deblocking filter", "De-blocking", "", "vb"); + maybe_add ("ppha", "Horizontal deblocking filter A", "De-blocking", "", "ha"); + maybe_add ("ppva", "Vertical deblocking filter A", "De-blocking", "", "va"); + maybe_add ("pph1", "Experimental horizontal deblocking filter 1", "De-blocking", "", "h1"); + maybe_add ("pphv", "Experimental vertical deblocking filter 1", "De-blocking", "", "v1"); + maybe_add ("ppdr", "Deringing filter", "Misc", "", "dr"); + maybe_add ("pplb", "Linear blend deinterlacer", "De-interlacing", "", "lb"); + maybe_add ("ppli", "Linear interpolating deinterlacer", "De-interlacing", "", "li"); + maybe_add ("ppci", "Cubic interpolating deinterlacer", "De-interlacing", "", "ci"); + maybe_add ("ppmd", "Median deinterlacer", "De-interlacing", "", "md"); + maybe_add ("ppfd", "FFMPEG deinterlacer", "De-interlacing", "", "fd"); + maybe_add ("ppl5", "FIR low-pass deinterlacer", "De-interlacing", "", "l5"); + maybe_add ("mcdeint", "Motion compensating deinterlacer", "De-interlacing", "mcdeint", ""); + maybe_add ("kerndeint", "Kernel deinterlacer", "De-interlacing", "kerndeint", ""); + maybe_add ("yadif", "Yet Another Deinterlacing Filter", "De-interlacing", "yadif", ""); + maybe_add ("pptn", "Temporal noise reducer", "Noise reduction", "", "tn"); + maybe_add ("ppfq", "Force quantizer", "Misc", "", "fq"); + maybe_add ("gradfun", "Gradient debander", "Misc", "gradfun", ""); + maybe_add ("unsharp", "Unsharp mask and Gaussian blur", "Misc", "unsharp", ""); + maybe_add ("denoise3d", "3D denoiser", "Noise reduction", "denoise3d", ""); + maybe_add ("hqdn3d", "High quality 3D denoiser", "Noise reduction", "hqdn3d", ""); + maybe_add ("telecine", "Telecine filter", "Misc", "telecine", ""); + maybe_add ("ow", "Overcomplete wavelet denoiser", "Noise reduction", "mp=ow", ""); +} + +void +Filter::maybe_add (string i, string n, string c, string v, string p) +{ + if (!v.empty ()) { + if (avfilter_get_by_name (i.c_str())) { + _filters.push_back (new Filter (i, n, c, v, p)); + } + } else if (!p.empty ()) { + pp_mode* m = pp_get_mode_by_name_and_quality (p.c_str(), PP_QUALITY_MAX); + if (m) { + _filters.push_back (new Filter (i, n, c, v, p)); + pp_free_mode (m); + } + } } /** @param filters Set of filters. diff --git a/src/lib/filter.h b/src/lib/filter.h index 20c55049c..205d92482 100644 --- a/src/lib/filter.h +++ b/src/lib/filter.h @@ -33,7 +33,7 @@ class Filter { public: - Filter (std::string, std::string, std::string, std::string); + Filter (std::string, std::string, std::string, std::string, std::string); /** @return our id */ std::string id () const { @@ -54,6 +54,10 @@ public: std::string pp () const { return _pp; } + + std::string category () const { + return _category; + } static std::vector<Filter const *> all (); static Filter const * from_id (std::string); @@ -66,6 +70,7 @@ private: std::string _id; /** user-visible name */ std::string _name; + std::string _category; /** string for a FFmpeg video filter descriptor */ std::string _vf; /** string for a FFmpeg post-processing descriptor */ @@ -73,6 +78,7 @@ private: /** all available filters */ static std::vector<Filter const *> _filters; + static void maybe_add (std::string, std::string, std::string, std::string, std::string); }; #endif diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 6cd7dc2cb..b0991a2da 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. @@ -67,8 +68,6 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp: filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size())); - avfilter_register_all (); - AVFilterGraph* graph = avfilter_graph_alloc(); if (graph == 0) { throw DecodeError ("Could not create filter graph."); 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..0ec6bd26c 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 @@ -231,8 +240,8 @@ Image::crop (Crop crop, bool aligned) const for (int y = 0; y < cropped_size.height; ++y) { memcpy (out_p, in_p + crop_left_in_bytes, cropped_width_in_bytes); - in_p += line_size()[c]; - out_p += out->line_size()[c]; + in_p += stride()[c]; + out_p += out->stride()[c]; } } @@ -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/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc index 22129f56c..3d941888e 100644 --- a/src/lib/scp_dcp_job.cc +++ b/src/lib/scp_dcp_job.cc @@ -161,12 +161,7 @@ SCPDCPJob::run () for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (dcp_dir); i != boost::filesystem::directory_iterator(); ++i) { - /* Aah, the sweet smell of progress */ -#if BOOST_FILESYSTEM_VERSION == 3 string const leaf = boost::filesystem::path(*i).leaf().generic_string (); -#else - string const leaf = i->leaf (); -#endif set_status ("copying " + leaf); 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..340b76b57 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; @@ -230,6 +233,8 @@ seconds (struct timeval t) void dvdomatic_setup () { + avfilter_register_all (); + Format::setup_formats (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); @@ -330,25 +335,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 +{ +public: + FrameRateCandidate (float source_, int dcp_) + : source (source_) + , dcp (dcp_) + {} + + 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) { - DCPFrameRate dfr; + 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; + } - dfr.run_fast = (fps != rint (fps)); - dfr.frames_per_second = rint (fps); - dfr.skip = 1; + float const e = fabs (i->source - source_fps); + if (e < error) { + error = e; + best = *i; + } - /* XXX: somewhat arbitrary */ - if (fps == 50) { - dfr.frames_per_second = 25; - dfr.skip = 2; + ++i; } - return dfr; + if (!best) { + throw EncodeError ("cannot find a suitable DCP frame rate for this source"); + } + + 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. @@ -542,6 +622,9 @@ Socket::read_definite_and_consume (uint8_t* data, int size, int timeout) /** Read as much data as is available, up to some limit. * @param data Where to put the data. * @param size Maximum amount of data to read. + * + * XXX This method assumes that there is always lots of data to read(); + * if there isn't, it will hang waiting for data that will never arrive. */ void Socket::read_indefinite (uint8_t* data, int size, int timeout) @@ -854,11 +937,7 @@ video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float fram bool still_image_file (string f) { -#if BOOST_FILESYSTEM_VERSION == 3 string ext = boost::filesystem::path(f).extension().string(); -#else - string ext = boost::filesystem::path(f).extension(); -#endif transform (ext.begin(), ext.end(), ext.begin(), ::tolower); 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..5bd32147a --- /dev/null +++ b/src/lib/writer.cc @@ -0,0 +1,361 @@ +/* + 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 those + 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 (_first_nonexistant_frame > 0); + + 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); + + QueueItem qi; + qi.type = QueueItem::FULL; + qi.encoded = encoded; + qi.frame = frame; + _queue.push_back (qi); + + _condition.notify_all (); +} + +void +Writer::fake_write (int frame) +{ + boost::mutex::scoped_lock lock (_mutex); + + ifstream ifi (_film->info_path (frame).c_str()); + libdcp::FrameInfo info (ifi); + + QueueItem qi; + qi.type = QueueItem::FAKE; + qi.size = info.size; + qi.frame = frame; + _queue.push_back (qi); + + _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()); +} + +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().frame == (_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 (); + } + + 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().frame == (_last_written_frame + 1)) { + QueueItem qi = _queue.front (); + _queue.pop_front (); + + lock.unlock (); + switch (qi.type) { + case QueueItem::FULL: + { + _film->log()->log (String::compose ("Writer FULL-writes %1 to MXF", qi.frame)); + libdcp::FrameInfo const fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size()); + qi.encoded->write_info (_film, qi.frame, fin); + _last_written = qi.encoded; + break; + } + case QueueItem::FAKE: + _film->log()->log (String::compose ("Writer FAKE-writes %1 to MXF", qi.frame)); + _picture_asset_writer->fake_write (qi.size); + _last_written.reset (); + break; + case QueueItem::REPEAT: + { + _film->log()->log (String::compose ("Writer REPEAT-writes %1 to MXF", qi.frame)); + libdcp::FrameInfo const fin = _picture_asset_writer->write (_last_written->data(), _last_written->size()); + _last_written->write_info (_film, qi.frame, fin); + break; + } + } + 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 in our pending list (and write FULL queue items' data to disk) + */ + + QueueItem qi = _queue.back (); + _queue.pop_back (); + + if (qi.type == QueueItem::FULL) { + lock.unlock (); + _film->log()->log (String::compose ("Writer full (awaiting %1); pushes %2 to disk", _last_written_frame + 1, qi.frame)); + qi.encoded->write (_film, qi.frame); + lock.lock (); + qi.encoded.reset (); + } + + _pending.push_back (qi); + } + + while (_queue.size() < _maximum_frames_in_memory && !_pending.empty()) { + /* We have some space in memory. Fetch some frames back off disk. */ + + _pending.sort (); + QueueItem qi = _pending.front (); + + if (qi.type == QueueItem::FULL) { + lock.unlock (); + _film->log()->log (String::compose ("Writer pulls %1 back from disk", qi.frame)); + shared_ptr<const EncodedData> encoded; + qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, false))); + lock.lock (); + } + + _queue.push_back (qi); + _pending.remove (qi); + } + } + +} + +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); + + QueueItem qi; + qi.type = QueueItem::REPEAT; + qi.frame = f; + + _queue.push_back (qi); + + _condition.notify_all (); +} + + +void +Writer::check_existing_picture_mxf () +{ + /* Try to open the existing MXF */ + boost::filesystem::path p; + p /= _film->video_mxf_dir (); + p /= _film->video_mxf_filename (); + FILE* mxf = fopen (p.string().c_str(), "rb"); + if (!mxf) { + return; + } + + while (1) { + + /* Read the frame info as written */ + ifstream ifi (_film->info_path (_first_nonexistant_frame).c_str()); + libdcp::FrameInfo info (ifi); + + /* Read the data from the MXF and hash it */ + fseek (mxf, info.offset, SEEK_SET); + EncodedData data (info.size); + fread (data.data(), 1, data.size(), mxf); + string const existing_hash = md5_digest (data.data(), data.size()); + + if (existing_hash != info.hash) { + _film->log()->log (String::compose ("Existing frame %1 failed hash check", _first_nonexistant_frame)); + break; + } + + _film->log()->log (String::compose ("Have existing frame %1", _first_nonexistant_frame)); + ++_first_nonexistant_frame; + } + + fclose (mxf); +} + +/** @return true if the fake write succeeded, otherwise false */ +bool +Writer::can_fake_write (int frame) const +{ + return (frame != 0 && frame < _first_nonexistant_frame); +} + + +bool +operator< (QueueItem const & a, QueueItem const & b) +{ + return a.frame < b.frame; +} + +bool +operator== (QueueItem const & a, QueueItem const & b) +{ + return a.frame == b.frame; +} diff --git a/src/lib/writer.h b/src/lib/writer.h new file mode 100644 index 000000000..57609825d --- /dev/null +++ b/src/lib/writer.h @@ -0,0 +1,91 @@ +/* + 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; +} + +struct QueueItem +{ +public: + enum Type { + FULL, + FAKE, + REPEAT + } type; + + /** encoded data for FULL */ + boost::shared_ptr<const EncodedData> encoded; + /** size of data for FAKE */ + int size; + /** frame index */ + int frame; +}; + +bool operator< (QueueItem const & a, QueueItem const & b); +bool operator== (QueueItem const & a, QueueItem const & b); + +class Writer +{ +public: + Writer (boost::shared_ptr<Film>); + + bool can_fake_write (int) const; + + void write (boost::shared_ptr<const EncodedData>, int); + void fake_write (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<QueueItem> _queue; + mutable boost::mutex _mutex; + boost::condition _condition; + boost::shared_ptr<const EncodedData> _last_written; + std::list<QueueItem> _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' diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc index d5dca2ab4..0565d3a1c 100644 --- a/src/tools/dvdomatic.cc +++ b/src/tools/dvdomatic.cc @@ -64,7 +64,7 @@ public: { stringstream s; s << "Save changes to film \"" << film->name() << "\" before closing?"; - _dialog = new wxMessageDialog (0, std_to_wx (s.str()), wxT ("Film changed"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); + _dialog = new wxMessageDialog (0, std_to_wx (s.str()), _("Film changed"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); } ~FilmChangedDialog () @@ -263,18 +263,14 @@ public: if (r == wxID_OK) { if (boost::filesystem::exists (d->get_path())) { - error_dialog (this, String::compose ("The directory %1 already exists.", d->get_path())); + error_dialog (this, wxString::Format (_("The directory %s already exists"), d->get_path().c_str())); return; } maybe_save_then_delete_film (); film.reset (new Film (d->get_path (), false)); film->log()->set_level (log_level); -#if BOOST_FILESYSTEM_VERSION == 3 film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string()); -#else - film->set_name (boost::filesystem::path (d->get_path()).filename()); -#endif set_film (); } @@ -283,7 +279,7 @@ public: void file_open (wxCommandEvent &) { - wxDirDialog* c = new wxDirDialog (this, wxT ("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST); + wxDirDialog* c = new wxDirDialog (this, _("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST); int const r = c->ShowModal (); if (r == wxID_OK) { @@ -293,7 +289,9 @@ public: film->log()->set_level (log_level); set_film (); } catch (std::exception& e) { - error_dialog (this, String::compose ("Could not open film at %1 (%2)", wx_to_std (c->GetPath()), e.what())); + wxString p = c->GetPath (); + wxCharBuffer b = p.ToUTF8 (); + error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), e.what())); } } diff --git a/src/tools/makedcp.cc b/src/tools/makedcp.cc index 900c31bfc..892bed3b8 100644 --- a/src/tools/makedcp.cc +++ b/src/tools/makedcp.cc @@ -26,7 +26,6 @@ #include "film.h" #include "filter.h" #include "transcode_job.h" -#include "make_dcp_job.h" #include "job_manager.h" #include "ab_transcode_job.h" #include "util.h" @@ -160,6 +159,7 @@ main (int argc, char* argv[]) bool should_stop = false; bool first = true; + bool error = false; while (!should_stop) { dvdomatic_sleep (5); @@ -195,6 +195,7 @@ main (int argc, char* argv[]) if ((*i)->finished_in_error ()) { ++finished_in_error; + error = true; } if (!progress && (*i)->finished_in_error ()) { @@ -210,7 +211,7 @@ main (int argc, char* argv[]) } } - return 0; + return error ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index b656a5278..07e32a457 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -46,32 +46,32 @@ ConfigDialog::ConfigDialog (wxWindow* parent) wxFlexGridSizer* table = new wxFlexGridSizer (3, 6, 6); table->AddGrowableCol (1, 1); - add_label_to_sizer (table, this, "TMS IP address"); + add_label_to_sizer (table, this, _("TMS IP address")); _tms_ip = new wxTextCtrl (this, wxID_ANY); table->Add (_tms_ip, 1, wxEXPAND); table->AddSpacer (0); - add_label_to_sizer (table, this, "TMS target path"); + add_label_to_sizer (table, this, _("TMS target path")); _tms_path = new wxTextCtrl (this, wxID_ANY); table->Add (_tms_path, 1, wxEXPAND); table->AddSpacer (0); - add_label_to_sizer (table, this, "TMS user name"); + add_label_to_sizer (table, this, _("TMS user name")); _tms_user = new wxTextCtrl (this, wxID_ANY); table->Add (_tms_user, 1, wxEXPAND); table->AddSpacer (0); - add_label_to_sizer (table, this, "TMS password"); + add_label_to_sizer (table, this, _("TMS password")); _tms_password = new wxTextCtrl (this, wxID_ANY); table->Add (_tms_password, 1, wxEXPAND); table->AddSpacer (0); - add_label_to_sizer (table, this, "Threads to use for encoding on this host"); + add_label_to_sizer (table, this, _("Threads to use for encoding on this host")); _num_local_encoding_threads = new wxSpinCtrl (this); table->Add (_num_local_encoding_threads, 1, wxEXPAND); table->AddSpacer (0); - add_label_to_sizer (table, this, "Default directory for new films"); + add_label_to_sizer (table, this, _("Default directory for new films")); #ifdef __WXMSW__ _default_directory = new DirPickerCtrl (this); #else @@ -80,12 +80,12 @@ ConfigDialog::ConfigDialog (wxWindow* parent) table->Add (_default_directory, 1, wxEXPAND); table->AddSpacer (0); - add_label_to_sizer (table, this, "Default DCI name details"); + add_label_to_sizer (table, this, _("Default DCI name details")); _default_dci_metadata_button = new wxButton (this, wxID_ANY, _("Edit...")); table->Add (_default_dci_metadata_button); table->AddSpacer (1); - add_label_to_sizer (table, this, "Reference scaler for A/B"); + add_label_to_sizer (table, this, _("Reference scaler for A/B")); _reference_scaler = new wxChoice (this, wxID_ANY); vector<Scaler const *> const sc = Scaler::all (); for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { @@ -96,7 +96,7 @@ ConfigDialog::ConfigDialog (wxWindow* parent) table->AddSpacer (0); { - add_label_to_sizer (table, this, "Reference filters for A/B"); + add_label_to_sizer (table, this, _("Reference filters for A/B")); wxSizer* s = new wxBoxSizer (wxHORIZONTAL); _reference_filters = new wxStaticText (this, wxID_ANY, wxT ("")); s->Add (_reference_filters, 1, wxEXPAND); @@ -106,7 +106,7 @@ ConfigDialog::ConfigDialog (wxWindow* parent) table->AddSpacer (0); } - add_label_to_sizer (table, this, "Encoding Servers"); + add_label_to_sizer (table, this, _("Encoding Servers")); _servers = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (220, 100), wxLC_REPORT | wxLC_SINGLE_SEL); wxListItem ip; ip.SetId (0); diff --git a/src/wx/dci_metadata_dialog.cc b/src/wx/dci_metadata_dialog.cc index c5682e19e..c08c58ed4 100644 --- a/src/wx/dci_metadata_dialog.cc +++ b/src/wx/dci_metadata_dialog.cc @@ -30,31 +30,31 @@ DCIMetadataDialog::DCIMetadataDialog (wxWindow* parent, DCIMetadata dm) wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6); table->AddGrowableCol (1, 1); - add_label_to_sizer (table, this, "Audio Language (e.g. EN)"); + add_label_to_sizer (table, this, _("Audio Language (e.g. EN)")); _audio_language = new wxTextCtrl (this, wxID_ANY); table->Add (_audio_language, 1, wxEXPAND); - add_label_to_sizer (table, this, "Subtitle Language (e.g. FR)"); + add_label_to_sizer (table, this, _("Subtitle Language (e.g. FR)")); _subtitle_language = new wxTextCtrl (this, wxID_ANY); table->Add (_subtitle_language, 1, wxEXPAND); - add_label_to_sizer (table, this, "Territory (e.g. UK)"); + add_label_to_sizer (table, this, _("Territory (e.g. UK)")); _territory = new wxTextCtrl (this, wxID_ANY); table->Add (_territory, 1, wxEXPAND); - add_label_to_sizer (table, this, "Rating (e.g. 15)"); + add_label_to_sizer (table, this, _("Rating (e.g. 15)")); _rating = new wxTextCtrl (this, wxID_ANY); table->Add (_rating, 1, wxEXPAND); - add_label_to_sizer (table, this, "Studio (e.g. TCF)"); + add_label_to_sizer (table, this, _("Studio (e.g. TCF)")); _studio = new wxTextCtrl (this, wxID_ANY); table->Add (_studio, 1, wxEXPAND); - add_label_to_sizer (table, this, "Facility (e.g. DLA)"); + add_label_to_sizer (table, this, _("Facility (e.g. DLA)")); _facility = new wxTextCtrl (this, wxID_ANY); table->Add (_facility, 1, wxEXPAND); - add_label_to_sizer (table, this, "Package Type (e.g. OV)"); + add_label_to_sizer (table, this, _("Package Type (e.g. OV)")); _package_type = new wxTextCtrl (this, wxID_ANY); table->Add (_package_type, 1, wxEXPAND); diff --git a/src/wx/dir_picker_ctrl.cc b/src/wx/dir_picker_ctrl.cc index cb811fc10..b6558a881 100644 --- a/src/wx/dir_picker_ctrl.cc +++ b/src/wx/dir_picker_ctrl.cc @@ -50,11 +50,7 @@ DirPickerCtrl::SetPath (wxString p) if (_path == wxStandardPaths::Get().GetDocumentsDir()) { _folder->SetLabel (_("My Documents")); } else { -#if BOOST_FILESYSTEM_VERSION == 3 _folder->SetLabel (std_to_wx (filesystem::path (wx_to_std (_path)).leaf().string())); -#else - _folder->SetLabel (std_to_wx (filesystem::path (wx_to_std (_path)).leaf())); -#endif } } diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index f058afa54..634e417df 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -92,77 +92,79 @@ void FilmEditor::make_film_panel () { _film_panel = new wxPanel (_notebook); - _film_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_film_sizer, 0, wxALL, 8); - _film_panel->SetSizer (pad); + _film_sizer = new wxBoxSizer (wxVERTICAL); + _film_panel->SetSizer (_film_sizer); - add_label_to_sizer (_film_sizer, _film_panel, "Name"); + wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4); + _film_sizer->Add (grid, 0, wxALL, 8); + + add_label_to_sizer (grid, _film_panel, _("Name")); _name = new wxTextCtrl (_film_panel, wxID_ANY); - _film_sizer->Add (_name, 1, wxEXPAND); + grid->Add (_name, 1, wxEXPAND); - add_label_to_sizer (_film_sizer, _film_panel, "DCP Name"); + add_label_to_sizer (grid, _film_panel, _("DCP Name")); _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK); + grid->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK); - _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Use DCI name")); - _film_sizer->Add (_use_dci_name, 1, wxEXPAND); - _edit_dci_button = new wxButton (_film_panel, wxID_ANY, wxT ("Details...")); - _film_sizer->Add (_edit_dci_button, 0); + _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name")); + grid->Add (_use_dci_name, 1, wxEXPAND); + _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details...")); + grid->Add (_edit_dci_button, 0); - add_label_to_sizer (_film_sizer, _film_panel, "Content"); - _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*")); - _film_sizer->Add (_content, 1, wxEXPAND); + add_label_to_sizer (grid, _film_panel, _("Content")); + _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*")); + grid->Add (_content, 1, wxEXPAND); - _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header")); + _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header")); video_control (_trust_content_header); - _film_sizer->Add (_trust_content_header, 1); - _film_sizer->AddSpacer (0); + grid->Add (_trust_content_header, 1); + grid->AddSpacer (0); - add_label_to_sizer (_film_sizer, _film_panel, "Content Type"); + add_label_to_sizer (grid, _film_panel, _("Content Type")); _dcp_content_type = new wxChoice (_film_panel, wxID_ANY); - _film_sizer->Add (_dcp_content_type); + grid->Add (_dcp_content_type); - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second")); + video_control (add_label_to_sizer (grid, _film_panel, _("Frames Per Second"))); _frames_per_second = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL); + grid->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL); - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Original Size")); + video_control (add_label_to_sizer (grid, _film_panel, _("Original Size"))); _original_size = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL); + grid->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL); - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Length")); + video_control (add_label_to_sizer (grid, _film_panel, _("Length"))); _length = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL); + grid->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL); { - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames")); + video_control (add_label_to_sizer (grid, _film_panel, _("Trim frames"))); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - video_control (add_label_to_sizer (s, _film_panel, "Start")); - _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_dcp_trim_start)); - video_control (add_label_to_sizer (s, _film_panel, "End")); - _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_dcp_trim_end)); + video_control (add_label_to_sizer (s, _film_panel, _("Start"))); + _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); + s->Add (video_control (_trim_start)); + video_control (add_label_to_sizer (s, _film_panel, _("End"))); + _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); + s->Add (video_control (_trim_end)); - _film_sizer->Add (s); + grid->Add (s); } - _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, wxT ("A/B")); + _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B")); video_control (_dcp_ab); - _film_sizer->Add (_dcp_ab, 1); - _film_sizer->AddSpacer (0); + grid->Add (_dcp_ab, 1); + grid->AddSpacer (0); /* STILL-only stuff */ { - still_control (add_label_to_sizer (_film_sizer, _film_panel, "Duration")); + still_control (add_label_to_sizer (grid, _film_panel, _("Duration"))); wxSizer* s = new wxBoxSizer (wxHORIZONTAL); _still_duration = new wxSpinCtrl (_film_panel); still_control (_still_duration); s->Add (_still_duration, 1, wxEXPAND); - still_control (add_label_to_sizer (s, _film_panel, "s")); - _film_sizer->Add (s); + /* TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time */ + still_control (add_label_to_sizer (s, _film_panel, _("s"))); + grid->Add (s); } vector<DCPContentType const *> const ct = DCPContentType::all (); @@ -189,8 +191,8 @@ FilmEditor::connect_to_widgets () _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this); _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this); _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this); - _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this); - _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this); + _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this); + _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this); _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this); _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this); _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this); @@ -216,72 +218,76 @@ void FilmEditor::make_video_panel () { _video_panel = new wxPanel (_notebook); - _video_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_video_sizer, 0, wxALL, 8); - _video_panel->SetSizer (pad); + _video_sizer = new wxBoxSizer (wxVERTICAL); + _video_panel->SetSizer (_video_sizer); + + wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4); + _video_sizer->Add (grid, 0, wxALL, 8); - add_label_to_sizer (_video_sizer, _video_panel, "Format"); + add_label_to_sizer (grid, _video_panel, _("Format")); _format = new wxChoice (_video_panel, wxID_ANY); - _video_sizer->Add (_format); + grid->Add (_format); { - add_label_to_sizer (_video_sizer, _video_panel, "Crop"); + add_label_to_sizer (grid, _video_panel, _("Crop")); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - add_label_to_sizer (s, _video_panel, "L"); + /* TRANSLATORS: L, R, T and B are abbreviations for Left, Right, Top, Bottom, the four edges + of the picture frame. + */ + add_label_to_sizer (s, _video_panel, _("L")); _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); s->Add (_left_crop, 0); - add_label_to_sizer (s, _video_panel, "R"); + add_label_to_sizer (s, _video_panel, _("R")); _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); s->Add (_right_crop, 0); - add_label_to_sizer (s, _video_panel, "T"); + add_label_to_sizer (s, _video_panel, _("T")); _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); s->Add (_top_crop, 0); - add_label_to_sizer (s, _video_panel, "B"); + add_label_to_sizer (s, _video_panel, _("B")); _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); s->Add (_bottom_crop, 0); - _video_sizer->Add (s); + grid->Add (s); } /* VIDEO-only stuff */ { - video_control (add_label_to_sizer (_video_sizer, _video_panel, "Filters")); + video_control (add_label_to_sizer (grid, _video_panel, _("Filters"))); wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _filters = new wxStaticText (_video_panel, wxID_ANY, wxT ("None")); + _filters = new wxStaticText (_video_panel, wxID_ANY, _("None")); video_control (_filters); s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6); - _filters_button = new wxButton (_video_panel, wxID_ANY, wxT ("Edit...")); + _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit...")); video_control (_filters_button); s->Add (_filters_button, 0); - _video_sizer->Add (s, 1); + grid->Add (s, 1); } - video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler")); + video_control (add_label_to_sizer (grid, _video_panel, _("Scaler"))); _scaler = new wxChoice (_video_panel, wxID_ANY); - _video_sizer->Add (video_control (_scaler), 1); + grid->Add (video_control (_scaler), 1); vector<Scaler const *> const sc = Scaler::all (); for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { _scaler->Append (std_to_wx ((*i)->name())); } - add_label_to_sizer (_video_sizer, _video_panel, "Colour look-up table"); + add_label_to_sizer (grid, _video_panel, _("Colour look-up table")); _colour_lut = new wxChoice (_video_panel, wxID_ANY); for (int i = 0; i < 2; ++i) { _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i))); } _colour_lut->SetSelection (0); - _video_sizer->Add (_colour_lut, 1, wxEXPAND); + grid->Add (_colour_lut, 1, wxEXPAND); { - add_label_to_sizer (_video_sizer, _video_panel, "JPEG2000 bandwidth"); + add_label_to_sizer (grid, _video_panel, _("JPEG2000 bandwidth")); wxSizer* s = new wxBoxSizer (wxHORIZONTAL); _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY); s->Add (_j2k_bandwidth, 1); - add_label_to_sizer (s, _video_panel, "MBps"); - _video_sizer->Add (s, 1); + add_label_to_sizer (s, _video_panel, _("MBps")); + grid->Add (s, 1); } _left_crop->SetRange (0, 1024); @@ -289,8 +295,8 @@ FilmEditor::make_video_panel () _right_crop->SetRange (0, 1024); _bottom_crop->SetRange (0, 1024); _still_duration->SetRange (1, 60 * 60); - _dcp_trim_start->SetRange (0, 100); - _dcp_trim_end->SetRange (0, 100); + _trim_start->SetRange (0, 100); + _trim_end->SetRange (0, 100); _j2k_bandwidth->SetRange (50, 250); } @@ -298,62 +304,67 @@ void FilmEditor::make_audio_panel () { _audio_panel = new wxPanel (_notebook); - _audio_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_audio_sizer, 0, wxALL, 8); - _audio_panel->SetSizer (pad); + _audio_sizer = new wxBoxSizer (wxVERTICAL); + _audio_panel->SetSizer (_audio_sizer); + + wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4); + _audio_sizer->Add (grid, 0, wxALL, 8); { - video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Gain")); + video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain"))); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_gain = new wxSpinCtrl (_audio_panel); s->Add (video_control (_audio_gain), 1); - video_control (add_label_to_sizer (s, _audio_panel, "dB")); + video_control (add_label_to_sizer (s, _audio_panel, _("dB"))); _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate...")); video_control (_audio_gain_calculate_button); s->Add (_audio_gain_calculate_button, 1, wxEXPAND); - _audio_sizer->Add (s); + grid->Add (s); } { - video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Delay")); + video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay"))); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_delay = new wxSpinCtrl (_audio_panel); s->Add (video_control (_audio_delay), 1); - video_control (add_label_to_sizer (s, _audio_panel, "ms")); - _audio_sizer->Add (s); + /* TRANSLATORS: this is an abbreviation for milliseconds, the unit of time */ + video_control (add_label_to_sizer (s, _audio_panel, _("ms"))); + grid->Add (s); } { _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); - _audio_sizer->Add (video_control (_use_content_audio)); + grid->Add (video_control (_use_content_audio)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_stream = new wxChoice (_audio_panel, wxID_ANY); s->Add (video_control (_audio_stream), 1); _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT ("")); s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); - _audio_sizer->Add (s, 1, wxEXPAND); + grid->Add (s, 1, wxEXPAND); } _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio")); - _audio_sizer->Add (_use_external_audio); - _audio_sizer->AddSpacer (0); + grid->Add (_use_external_audio); + grid->AddSpacer (0); assert (MAX_AUDIO_CHANNELS == 6); - char const * channels[] = { - "Left", - "Right", - "Centre", - "Lfe (sub)", - "Left surround", - "Right surround" + /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency + enhancement channel (sub-woofer)./ + */ + wxString const channels[] = { + _("Left"), + _("Right"), + _("Centre"), + _("Lfe (sub)"), + _("Left surround"), + _("Right surround"), }; for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]); - _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav")); - _audio_sizer->Add (_external_audio[i], 1, wxEXPAND); + add_label_to_sizer (grid, _audio_panel, channels[i]); + _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav")); + grid->Add (_external_audio[i], 1, wxEXPAND); } _audio_gain->SetRange (-60, 60); @@ -364,29 +375,29 @@ void FilmEditor::make_subtitle_panel () { _subtitle_panel = new wxPanel (_notebook); - _subtitle_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_subtitle_sizer, 0, wxALL, 8); - _subtitle_panel->SetSizer (pad); + _subtitle_sizer = new wxBoxSizer (wxVERTICAL); + _subtitle_panel->SetSizer (_subtitle_sizer); + wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4); + _subtitle_sizer->Add (grid, 0, wxALL, 8); - _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, wxT("With Subtitles")); + _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles")); video_control (_with_subtitles); - _subtitle_sizer->Add (_with_subtitles, 1); + grid->Add (_with_subtitles, 1); _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY); - _subtitle_sizer->Add (video_control (_subtitle_stream)); + grid->Add (video_control (_subtitle_stream)); - video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset")); + video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"))); _subtitle_offset = new wxSpinCtrl (_subtitle_panel); - _subtitle_sizer->Add (video_control (_subtitle_offset), 1); + grid->Add (video_control (_subtitle_offset), 1); { - video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Scale")); + video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"))); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _subtitle_scale = new wxSpinCtrl (_subtitle_panel); s->Add (video_control (_subtitle_scale)); - video_control (add_label_to_sizer (s, _subtitle_panel, "%")); - _subtitle_sizer->Add (s); + video_control (add_label_to_sizer (s, _subtitle_panel, _("%"))); + grid->Add (s); } _subtitle_offset->SetRange (-1024, 1024); @@ -449,7 +460,7 @@ FilmEditor::content_changed (wxCommandEvent &) _film->set_content (wx_to_std (_content->GetPath ())); } catch (std::exception& e) { _content->SetPath (std_to_wx (_film->directory ())); - error_dialog (this, String::compose ("Could not set content: %1", e.what ())); + error_dialog (this, wxString::Format (_("Could not set content: %s"), e.what ())); } } @@ -620,10 +631,12 @@ FilmEditor::film_changed (Film::Property p) } _length->SetLabel (std_to_wx (s.str ())); if (_film->length()) { - _dcp_trim_start->SetRange (0, _film->length().get()); - _dcp_trim_end->SetRange (0, _film->length().get()); + _trim_start->SetRange (0, _film->length().get()); + _trim_end->SetRange (0, _film->length().get()); } break; + case Film::DCP_INTRINSIC_DURATION: + break; case Film::DCP_CONTENT_TYPE: checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ())); _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); @@ -634,11 +647,11 @@ FilmEditor::film_changed (Film::Property p) case Film::SCALER: checked_set (_scaler, Scaler::as_index (_film->scaler ())); break; - case Film::DCP_TRIM_START: - checked_set (_dcp_trim_start, _film->dcp_trim_start()); + case Film::TRIM_START: + checked_set (_trim_start, _film->trim_start()); break; - case Film::DCP_TRIM_END: - checked_set (_dcp_trim_end, _film->dcp_trim_end()); + case Film::TRIM_END: + checked_set (_trim_end, _film->trim_end()); break; case Film::AUDIO_GAIN: checked_set (_audio_gain, _film->audio_gain ()); @@ -761,8 +774,8 @@ FilmEditor::set_film (shared_ptr<Film> f) film_changed (Film::CROP); film_changed (Film::FILTERS); film_changed (Film::SCALER); - film_changed (Film::DCP_TRIM_START); - film_changed (Film::DCP_TRIM_END); + film_changed (Film::TRIM_START); + film_changed (Film::TRIM_END); film_changed (Film::DCP_AB); film_changed (Film::CONTENT_AUDIO_STREAM); film_changed (Film::EXTERNAL_AUDIO); @@ -805,8 +818,8 @@ FilmEditor::set_things_sensitive (bool s) _scaler->Enable (s); _audio_stream->Enable (s); _dcp_content_type->Enable (s); - _dcp_trim_start->Enable (s); - _dcp_trim_end->Enable (s); + _trim_start->Enable (s); + _trim_end->Enable (s); _dcp_ab->Enable (s); _colour_lut->Enable (s); _j2k_bandwidth->Enable (s); @@ -920,23 +933,23 @@ FilmEditor::still_duration_changed (wxCommandEvent &) } void -FilmEditor::dcp_trim_start_changed (wxCommandEvent &) +FilmEditor::trim_start_changed (wxCommandEvent &) { if (!_film) { return; } - _film->set_dcp_trim_start (_dcp_trim_start->GetValue ()); + _film->set_trim_start (_trim_start->GetValue ()); } void -FilmEditor::dcp_trim_end_changed (wxCommandEvent &) +FilmEditor::trim_end_changed (wxCommandEvent &) { if (!_film) { return; } - _film->set_dcp_trim_end (_dcp_trim_end->GetValue ()); + _film->set_trim_end (_trim_end->GetValue ()); } void diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index 581ae3f69..90be752d8 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -63,8 +63,8 @@ private: void content_changed (wxCommandEvent &); void trust_content_header_changed (wxCommandEvent &); void format_changed (wxCommandEvent &); - void dcp_trim_start_changed (wxCommandEvent &); - void dcp_trim_end_changed (wxCommandEvent &); + void trim_start_changed (wxCommandEvent &); + void trim_end_changed (wxCommandEvent &); void dcp_content_type_changed (wxCommandEvent &); void dcp_ab_toggled (wxCommandEvent &); void scaler_changed (wxCommandEvent &); @@ -165,8 +165,8 @@ private: /** The Film's duration for still sources */ wxSpinCtrl* _still_duration; - wxSpinCtrl* _dcp_trim_start; - wxSpinCtrl* _dcp_trim_end; + wxSpinCtrl* _trim_start; + wxSpinCtrl* _trim_end; /** Selector to generate an A/B comparison DCP */ wxCheckBox* _dcp_ab; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 16b3ccd9a..4e5f38300 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -34,6 +34,7 @@ #include "lib/scaler.h" #include "lib/exceptions.h" #include "lib/examine_content_job.h" +#include "lib/filter.h" #include "film_viewer.h" #include "wx_util.h" #include "video_decoder.h" @@ -44,6 +45,7 @@ using std::max; using std::cout; using std::list; using boost::shared_ptr; +using libdcp::Size; FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) : wxPanel (p) @@ -95,10 +97,10 @@ FilmViewer::film_changed (Film::Property p) break; case Film::CONTENT: { - shared_ptr<DecodeOptions> o (new DecodeOptions); - o->decode_audio = false; - o->decode_subtitles = true; - o->video_sync = false; + DecodeOptions o; + o.decode_audio = false; + o.decode_subtitles = true; + o.video_sync = false; _decoders = decoder_factory (_film, o, 0); _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3)); _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this)); @@ -115,6 +117,7 @@ FilmViewer::film_changed (Film::Property p) case Film::SUBTITLE_OFFSET: case Film::SUBTITLE_SCALE: case Film::SCALER: + case Film::FILTERS: update_from_raw (); break; case Film::SUBTITLE_STREAM: @@ -266,8 +269,15 @@ FilmViewer::raw_to_display () old_size = _display_frame->size(); } + boost::shared_ptr<Image> input = _raw_frame; + + pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); + if (!s.second.empty ()) { + input = input->post_process (s.second, true); + } + /* Get a compacted image as we have to feed it to wxWidgets */ - _display_frame = _raw_frame->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false); + _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false); if (old_size != _display_frame->size()) { _clear_required = true; @@ -376,7 +386,9 @@ FilmViewer::get_frame () } } } catch (DecodeError& e) { - error_dialog (this, String::compose ("Could not decode video for view (%1)", e.what())); + _play_button->SetValue (false); + check_play_state (); + error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), e.what())); } } diff --git a/src/wx/filter_view.cc b/src/wx/filter_view.cc index 8d9535d81..db6728ba5 100644 --- a/src/wx/filter_view.cc +++ b/src/wx/filter_view.cc @@ -37,13 +37,38 @@ FilterView::FilterView (wxWindow* parent, vector<Filter const *> const & active) vector<Filter const *> filters = Filter::all (); + typedef map<string, list<Filter const *> > CategoryMap; + CategoryMap categories; + for (vector<Filter const *>::iterator i = filters.begin(); i != filters.end(); ++i) { - wxCheckBox* b = new wxCheckBox (this, wxID_ANY, std_to_wx ((*i)->name ())); - bool const a = find (active.begin(), active.end(), *i) != active.end (); - b->SetValue (a); - _filters[*i] = b; - b->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilterView::filter_toggled), 0, this); - sizer->Add (b); + CategoryMap::iterator j = categories.find ((*i)->category ()); + if (j == categories.end ()) { + list<Filter const *> c; + c.push_back (*i); + categories[(*i)->category()] = c; + } else { + j->second.push_back (*i); + } + } + + for (CategoryMap::iterator i = categories.begin(); i != categories.end(); ++i) { + + wxStaticText* c = new wxStaticText (this, wxID_ANY, std_to_wx (i->first)); + wxFont font = c->GetFont(); + font.SetWeight(wxFONTWEIGHT_BOLD); + c->SetFont(font); + sizer->Add (c); + + for (list<Filter const *>::iterator j = i->second.begin(); j != i->second.end(); ++j) { + wxCheckBox* b = new wxCheckBox (this, wxID_ANY, std_to_wx ((*j)->name ())); + bool const a = find (active.begin(), active.end(), *j) != active.end (); + b->SetValue (a); + _filters[*j] = b; + b->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilterView::filter_toggled), 0, this); + sizer->Add (b); + } + + sizer->AddSpacer (6); } } diff --git a/src/wx/gain_calculator_dialog.cc b/src/wx/gain_calculator_dialog.cc index 3f07faf06..7f4b774c0 100644 --- a/src/wx/gain_calculator_dialog.cc +++ b/src/wx/gain_calculator_dialog.cc @@ -29,11 +29,11 @@ GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent) wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6); table->AddGrowableCol (1, 1); - add_label_to_sizer (table, this, "I want to play this back at fader"); + add_label_to_sizer (table, this, _("I want to play this back at fader")); _wanted = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC)); table->Add (_wanted, 1, wxEXPAND); - add_label_to_sizer (table, this, "But I have to use fader"); + add_label_to_sizer (table, this, _("But I have to use fader")); _actual = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC)); table->Add (_actual, 1, wxEXPAND); diff --git a/src/wx/job_manager_view.cc b/src/wx/job_manager_view.cc index 9c7040584..a5c02b163 100644 --- a/src/wx/job_manager_view.cc +++ b/src/wx/job_manager_view.cc @@ -95,7 +95,7 @@ JobManagerView::update () _job_records[*i].message->SetLabel (std_to_wx (st)); _job_records[*i].gauge->SetValue (p * 100); } else { - _job_records[*i].message->SetLabel (wxT ("Running")); + _job_records[*i].message->SetLabel (_("Running")); _job_records[*i].gauge->Pulse (); } } diff --git a/src/wx/job_wrapper.cc b/src/wx/job_wrapper.cc index f2056cf49..cb02ecd02 100644 --- a/src/wx/job_wrapper.cc +++ b/src/wx/job_wrapper.cc @@ -35,8 +35,8 @@ JobWrapper::make_dcp (wxWindow* parent, shared_ptr<Film> film, bool transcode) try { film->make_dcp (transcode); } catch (BadSettingError& e) { - error_dialog (parent, String::compose ("Bad setting for %1 (%2)", e.setting(), e.what ())); + error_dialog (parent, wxString::Format (_("Bad setting for %s (%s)"), e.setting().c_str(), e.what())); } catch (std::exception& e) { - error_dialog (parent, String::compose ("Could not make DCP: %1", e.what ())); + error_dialog (parent, wxString::Format (_("Could not make DCP: %s"), e.what ())); } } diff --git a/src/wx/new_film_dialog.cc b/src/wx/new_film_dialog.cc index eb6f2849b..90c2d727e 100644 --- a/src/wx/new_film_dialog.cc +++ b/src/wx/new_film_dialog.cc @@ -30,7 +30,7 @@ using namespace std; using namespace boost; NewFilmDialog::NewFilmDialog (wxWindow* parent) - : wxDialog (parent, wxID_ANY, wxString (_("New Film"))) + : wxDialog (parent, wxID_ANY, _("New Film")) { wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); SetSizer (overall_sizer); @@ -39,11 +39,11 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent) table->AddGrowableCol (1, 1); overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6); - add_label_to_sizer (table, this, "Film name"); + add_label_to_sizer (table, this, _("Film name")); _name = new wxTextCtrl (this, wxID_ANY); table->Add (_name, 1, wxEXPAND); - add_label_to_sizer (table, this, "Create in folder"); + add_label_to_sizer (table, this, _("Create in folder")); #ifdef __WXMSW__ _folder = new DirPickerCtrl (this); #else diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index b03c6b32c..338d0f972 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -38,20 +38,20 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) { wxFlexGridSizer* table = new wxFlexGridSizer (2, 3, 6); - add_label_to_sizer (table, this, "Frames"); - _frames = new wxStaticText (this, wxID_ANY, std_to_wx ("")); + add_label_to_sizer (table, this, _("Frames")); + _frames = new wxStaticText (this, wxID_ANY, wxT ("")); table->Add (_frames, 1, wxALIGN_CENTER_VERTICAL); - add_label_to_sizer (table, this, "Disk space required for frames"); - _disk_for_frames = new wxStaticText (this, wxID_ANY, std_to_wx ("")); + add_label_to_sizer (table, this, _("Disk space required for frames")); + _disk_for_frames = new wxStaticText (this, wxID_ANY, wxT ("")); table->Add (_disk_for_frames, 1, wxALIGN_CENTER_VERTICAL); - add_label_to_sizer (table, this, "Total disk space required"); - _total_disk = new wxStaticText (this, wxID_ANY, std_to_wx ("")); + add_label_to_sizer (table, this, _("Total disk space required")); + _total_disk = new wxStaticText (this, wxID_ANY, wxT ("")); table->Add (_total_disk, 1, wxALIGN_CENTER_VERTICAL); - add_label_to_sizer (table, this, "Frames already encoded"); - _encoded = new ThreadedStaticText (this, "counting...", boost::bind (&PropertiesDialog::frames_already_encoded, this)); + add_label_to_sizer (table, this, _("Frames already encoded")); + _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)); table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL); if (_film->length()) { @@ -91,9 +91,9 @@ PropertiesDialog::frames_already_encoded () const return ""; } - if (_film->dcp_length()) { + if (_film->length()) { /* XXX: encoded_frames() should check which frames have been encoded */ - u << " (" << ((_film->encoded_frames() - _film->dcp_trim_start()) * 100 / _film->dcp_length().get()) << "%)"; + u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)"; } return u.str (); } diff --git a/src/wx/server_dialog.cc b/src/wx/server_dialog.cc index 7b394a484..1b5b71dc9 100644 --- a/src/wx/server_dialog.cc +++ b/src/wx/server_dialog.cc @@ -22,7 +22,7 @@ #include "wx_util.h" ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server) - : wxDialog (parent, wxID_ANY, wxString (_("Server"))) + : wxDialog (parent, wxID_ANY, _("Server")) { if (server) { _server = server; @@ -33,11 +33,11 @@ ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server) wxFlexGridSizer* table = new wxFlexGridSizer (2, 4, 4); table->AddGrowableCol (1, 1); - add_label_to_sizer (table, this, "Host name or IP address"); + add_label_to_sizer (table, this, _("Host name or IP address")); _host = new wxTextCtrl (this, wxID_ANY); table->Add (_host, 1, wxEXPAND); - add_label_to_sizer (table, this, "Threads to use"); + add_label_to_sizer (table, this, _("Threads to use")); _threads = new wxSpinCtrl (this, wxID_ANY); table->Add (_threads, 1, wxEXPAND); diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index 413071ea6..632dbc32e 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -36,9 +36,9 @@ using namespace boost; * @param prop Proportion to pass when calling Add() on the wxSizer. */ wxStaticText * -add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop) +add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, int prop) { - wxStaticText* m = new wxStaticText (p, wxID_ANY, std_to_wx (t)); + wxStaticText* m = new wxStaticText (p, wxID_ANY, t); s->Add (m, prop, wxALIGN_CENTER_VERTICAL | wxALL, 6); return m; } @@ -48,9 +48,9 @@ add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop) * @param m Message. */ void -error_dialog (wxWindow* parent, string m) +error_dialog (wxWindow* parent, wxString m) { - wxMessageDialog* d = new wxMessageDialog (parent, std_to_wx (m), wxT ("DVD-o-matic"), wxOK); + wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxOK); d->ShowModal (); d->Destroy (); } @@ -79,8 +79,8 @@ int const ThreadedStaticText::_update_event_id = 10000; * @param initial Initial text for the wxStaticText while the computation is being run. * @param fn Function which works out what the wxStaticText content should be and returns it. */ -ThreadedStaticText::ThreadedStaticText (wxWindow* parent, string initial, function<string ()> fn) - : wxStaticText (parent, wxID_ANY, std_to_wx (initial)) +ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, function<string ()> fn) + : wxStaticText (parent, wxID_ANY, initial) { Connect (_update_event_id, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ThreadedStaticText::thread_finished), 0, this); _thread = new thread (bind (&ThreadedStaticText::run, this, fn)); diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h index 0c77735eb..dd069a9d7 100644 --- a/src/wx/wx_util.h +++ b/src/wx/wx_util.h @@ -28,8 +28,8 @@ class wxSpinCtrl; * @brief Some utility functions and classes. */ -extern void error_dialog (wxWindow *, std::string); -extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, std::string, int prop = 0); +extern void error_dialog (wxWindow *, wxString); +extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, int prop = 0); extern std::string wx_to_std (wxString); extern wxString std_to_wx (std::string); @@ -41,7 +41,7 @@ extern wxString std_to_wx (std::string); class ThreadedStaticText : public wxStaticText { public: - ThreadedStaticText (wxWindow* parent, std::string initial, boost::function<std::string ()> fn); + ThreadedStaticText (wxWindow* parent, wxString initial, boost::function<std::string ()> fn); ~ThreadedStaticText (); private: |
