diff options
55 files changed, 523 insertions, 793 deletions
diff --git a/run/dvdomatic b/run/dvdomatic index 70906a254..ff3897064 100755 --- a/run/dvdomatic +++ b/run/dvdomatic @@ -3,10 +3,10 @@ export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH if [ "$1" == "--debug" ]; then shift - gdb --args build/src/tools/dvdomatic $* + gdb --args build/src/tools/dvdomatic "$*" elif [ "$1" == "--valgrind" ]; then shift valgrind --tool="memcheck" build/src/tools/dvdomatic $* else - build/src/tools/dvdomatic $* + build/src/tools/dvdomatic "$*" fi diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc index c9fd5bc97..b9538ce2e 100644 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@ -33,9 +33,10 @@ using boost::shared_ptr; /** @param f Film to compare. * @param o Options. */ -ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req) +ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req) : Job (f, req) - , _opt (o) + , _decode_opt (od) + , _encode_opt (oe) { _film_b.reset (new Film (*_film)); _film_b->set_scaler (Config::instance()->reference_scaler ()); @@ -53,7 +54,7 @@ ABTranscodeJob::run () { try { /* _film_b is the one with reference filters */ - ABTranscoder w (_film_b, _film, _opt, this, encoder_factory (_film, _opt)); + ABTranscoder w (_film_b, _film, _decode_opt, this, encoder_factory (_film, _encode_opt)); 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 8331edf76..86a2a81b8 100644 --- a/src/lib/ab_transcode_job.h +++ b/src/lib/ab_transcode_job.h @@ -25,6 +25,8 @@ #include "job.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. @@ -36,13 +38,19 @@ class Film; class ABTranscodeJob : public Job { public: - ABTranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, boost::shared_ptr<Job> req); + ABTranscodeJob ( + 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 (); private: - boost::shared_ptr<const Options> _opt; + boost::shared_ptr<const DecodeOptions> _decode_opt; + boost::shared_ptr<const EncodeOptions> _encode_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 537cb4dd7..d85f078a5 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -43,16 +43,15 @@ using boost::shared_ptr; /** @param a Film to use for the left half of the screen. * @param b Film to use for the right half of the screen. - * @param o Options. + * @param o Decoder options. * @param j Job that we are associated with. * @param e Encoder to use. */ ABTranscoder::ABTranscoder ( - shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e) + shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e) : _film_a (a) , _film_b (b) - , _opt (o) , _job (j) , _encoder (e) { @@ -67,12 +66,12 @@ ABTranscoder::ABTranscoder ( } /* Set up the decoder to use the film's set streams */ - _da.first->set_subtitle_stream (_film_a->subtitle_stream ()); - _db.first->set_subtitle_stream (_film_a->subtitle_stream ()); - _da.second->set_audio_stream (_film_a->audio_stream ()); + _da.video->set_subtitle_stream (_film_a->subtitle_stream ()); + _db.video->set_subtitle_stream (_film_a->subtitle_stream ()); + _da.audio->set_audio_stream (_film_a->audio_stream ()); - _da.first->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2)); - _db.first->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2)); + _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2)); + _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2)); if (_matcher) { _combiner->connect_video (_matcher); @@ -82,7 +81,7 @@ ABTranscoder::ABTranscoder ( } if (_matcher && _delay_line) { - _da.second->connect_audio (_delay_line); + _da.audio->connect_audio (_delay_line); _delay_line->connect_audio (_matcher); _matcher->connect_audio (_gain); _gain->connect_audio (_encoder); @@ -95,11 +94,11 @@ ABTranscoder::go () _encoder->process_begin (); while (1) { - bool const va = _da.first->pass (); - bool const vb = _db.first->pass (); - bool const a = _da.first->pass (); + bool const va = _da.video->pass (); + bool const vb = _db.video->pass (); + bool const a = _da.audio->pass (); - _da.first->set_progress (); + _da.video->set_progress (); if (va && vb && a) { break; diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h index 9b57e4f73..7bfcb393c 100644 --- a/src/lib/ab_transcoder.h +++ b/src/lib/ab_transcoder.h @@ -25,12 +25,13 @@ #include <boost/shared_ptr.hpp> #include <stdint.h> #include "util.h" +#include "decoder_factory.h" class Job; class Encoder; class VideoDecoder; class AudioDecoder; -class Options; +class DecodeOptions; class Image; class Log; class Subtitle; @@ -50,7 +51,7 @@ public: ABTranscoder ( boost::shared_ptr<Film> a, boost::shared_ptr<Film> b, - boost::shared_ptr<const Options> o, + boost::shared_ptr<const DecodeOptions> o, Job* j, boost::shared_ptr<Encoder> e ); @@ -60,11 +61,10 @@ public: private: boost::shared_ptr<Film> _film_a; boost::shared_ptr<Film> _film_b; - boost::shared_ptr<const Options> _opt; Job* _job; boost::shared_ptr<Encoder> _encoder; - std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _da; - std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _db; + Decoders _da; + Decoders _db; boost::shared_ptr<Combiner> _combiner; boost::shared_ptr<Matcher> _matcher; boost::shared_ptr<DelayLine> _delay_line; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 70f0effd9..9d8de971c 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 Options> o, Job* j) +AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) : Decoder (f, o, j) { diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index 1570fe3b0..013a6327f 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 Options>, Job *); + AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const 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 index 3967d0d70..50d86c523 100644 --- a/src/lib/check_hashes_job.cc +++ b/src/lib/check_hashes_job.cc @@ -34,9 +34,10 @@ using std::stringstream; using std::ifstream; using boost::shared_ptr; -CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req) +CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req) : Job (f, req) - , _opt (o) + , _decode_opt (od) + , _encode_opt (oe) , _bad (0) { @@ -61,7 +62,7 @@ CheckHashesJob::run () 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 = _opt->frame_out_path (i, false); + string const j2k_file = _encode_opt->frame_out_path (i, false); string const hash_file = j2k_file + ".md5"; if (!boost::filesystem::exists (j2k_file)) { @@ -91,13 +92,13 @@ CheckHashesJob::run () shared_ptr<Job> tc; if (_film->dcp_ab()) { - tc.reset (new ABTranscodeJob (_film, _opt, shared_from_this())); + tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this())); } else { - tc.reset (new TranscodeJob (_film, _opt, shared_from_this())); + 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, _opt, tc))); + JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, _encode_opt, tc))); } set_progress (1); diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h index e0ed6a64a..c41af9d3f 100644 --- a/src/lib/check_hashes_job.h +++ b/src/lib/check_hashes_job.h @@ -19,16 +19,25 @@ #include "job.h" +class DecodeOptions; +class EncodeOptions; + class CheckHashesJob : public Job { public: - CheckHashesJob (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, boost::shared_ptr<Job> req); + 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 Options> _opt; + boost::shared_ptr<const DecodeOptions> _decode_opt; + boost::shared_ptr<const EncodeOptions> _encode_opt; int _bad; }; diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index c185de0f4..996aff33f 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -376,7 +376,7 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv) * @param frame Frame index. */ void -EncodedData::write (shared_ptr<const Options> opt, SourceFrame frame) +EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame) { string const tmp_j2k = opt->frame_out_path (frame, true); diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index 5ae53f1e8..57e7e6203 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -26,7 +26,7 @@ */ class FilmState; -class Options; +class EncodeOptions; class ServerDescription; class Scaler; class Image; @@ -50,7 +50,7 @@ public: virtual ~EncodedData () {} void send (boost::shared_ptr<Socket> socket); - void write (boost::shared_ptr<const Options>, SourceFrame); + void write (boost::shared_ptr<const EncodeOptions>, SourceFrame); /** @return data */ uint8_t* data () const { @@ -122,7 +122,6 @@ public: private: void create_openjpeg_container (); - void write_encoded (boost::shared_ptr<const Options>, uint8_t *, int); boost::shared_ptr<const Image> _input; ///< the input image boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 2bacf58e7..507708345 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -49,10 +49,16 @@ using boost::optional; * @param o Options. * @param j Job that we are running within, or 0 */ -Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j) +Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j) : _film (f) , _opt (o) , _job (j) { } + +bool +Decoder::seek (SourceFrame f) +{ + throw DecodeError ("decoder does not support seek"); +} diff --git a/src/lib/decoder.h b/src/lib/decoder.h index e757e5401..0d35ebb3a 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -35,7 +35,7 @@ #include "audio_source.h" class Job; -class Options; +class DecodeOptions; class Image; class Log; class DelayLine; @@ -54,16 +54,22 @@ class FilterGraph; class Decoder { public: - Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); + Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); virtual ~Decoder () {} virtual bool pass () = 0; + /** Seek. + * @return true on error. + */ + virtual bool seek (SourceFrame); + + boost::signals2::signal<void()> OutputChanged; protected: /** our Film */ boost::shared_ptr<Film> _film; /** our options */ - boost::shared_ptr<const Options> _opt; + boost::shared_ptr<const DecodeOptions> _opt; /** associated Job, or 0 */ Job* _job; }; diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc index b2118ef74..1d8d12cd5 100644 --- a/src/lib/decoder_factory.cc +++ b/src/lib/decoder_factory.cc @@ -26,6 +26,7 @@ #include "imagemagick_decoder.h" #include "film.h" #include "external_audio_decoder.h" +#include "decoder_factory.h" using std::string; using std::pair; @@ -33,14 +34,14 @@ using std::make_pair; using boost::shared_ptr; using boost::dynamic_pointer_cast; -pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > +Decoders decoder_factory ( - shared_ptr<Film> f, shared_ptr<const Options> o, Job* j + shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j ) { if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) { /* A single image file, or a directory of them */ - return make_pair ( + return Decoders ( shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o, j)), shared_ptr<AudioDecoder> () ); @@ -48,8 +49,8 @@ decoder_factory ( shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o, j)); if (f->use_content_audio()) { - return make_pair (fd, fd); + return Decoders (fd, fd); } - return make_pair (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j))); + return Decoders (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j))); } diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h index 1f3690611..47d977ce7 100644 --- a/src/lib/decoder_factory.h +++ b/src/lib/decoder_factory.h @@ -17,16 +17,33 @@ */ +#ifndef DVDOMATIC_DECODER_FACTORY_H +#define DVDOMATIC_DECODER_FACTORY_H + /** @file src/decoder_factory.h * @brief A method to create appropriate decoders for some content. */ class Film; -class Options; +class DecodeOptions; class Job; class VideoDecoder; class AudioDecoder; -extern std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > decoder_factory ( - boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job * +struct Decoders { + Decoders () {} + + Decoders (boost::shared_ptr<VideoDecoder> v, boost::shared_ptr<AudioDecoder> a) + : video (v) + , audio (a) + {} + + boost::shared_ptr<VideoDecoder> video; + boost::shared_ptr<AudioDecoder> audio; +}; + +extern Decoders decoder_factory ( + boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job * ); + +#endif diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 17a6726a6..1fc7d5997 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -33,7 +33,7 @@ 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 Options> o) +Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) : _film (f) , _opt (o) , _just_skipped (false) @@ -107,13 +107,13 @@ Encoder::frame_skipped () void Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) { - if (_opt->decode_video_skip != 0 && (_video_frame % _opt->decode_video_skip) != 0) { + if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) { ++_video_frame; return; } - if (_opt->video_decode_range) { - pair<SourceFrame, SourceFrame> const r = _opt->video_decode_range.get(); + 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; @@ -127,12 +127,12 @@ Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) void Encoder::process_audio (shared_ptr<AudioBuffers> data) { - if (_opt->audio_decode_range) { + 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_decode_range.get(); + 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()); diff --git a/src/lib/encoder.h b/src/lib/encoder.h index b12bd0d48..64f113d74 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -35,7 +35,7 @@ extern "C" { #include "video_sink.h" #include "audio_sink.h" -class Options; +class EncodeOptions; class Image; class Subtitle; class AudioBuffers; @@ -54,7 +54,7 @@ class Film; class Encoder : public VideoSink, public AudioSink { public: - Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o); + Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o); virtual ~Encoder () {} /** Called to indicate that a processing run is about to begin */ @@ -93,7 +93,7 @@ protected: /** Film that we are encoding */ boost::shared_ptr<const Film> _film; /** Options */ - boost::shared_ptr<const Options> _opt; + boost::shared_ptr<const EncodeOptions> _opt; /** Mutex for _time_history, _just_skipped and _last_frame */ mutable boost::mutex _history_mutex; diff --git a/src/lib/encoder_factory.cc b/src/lib/encoder_factory.cc index 2da021ad8..fe4d50ef3 100644 --- a/src/lib/encoder_factory.cc +++ b/src/lib/encoder_factory.cc @@ -29,7 +29,7 @@ using boost::shared_ptr; shared_ptr<Encoder> -encoder_factory (shared_ptr<const Film> f, shared_ptr<const Options> o) +encoder_factory (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) { if (!boost::filesystem::is_directory (f->content_path()) && f->content_type() == STILL) { return shared_ptr<Encoder> (new J2KStillEncoder (f, o)); diff --git a/src/lib/encoder_factory.h b/src/lib/encoder_factory.h index 1bc4e18df..5ac5c9559 100644 --- a/src/lib/encoder_factory.h +++ b/src/lib/encoder_factory.h @@ -22,9 +22,9 @@ */ class Encoder; -class Options; +class EncodeOptions; class Job; class Log; class Film; -extern boost::shared_ptr<Encoder> encoder_factory (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>); +extern boost::shared_ptr<Encoder> encoder_factory (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>); diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc index 93333605b..70a04b825 100644 --- a/src/lib/examine_content_job.cc +++ b/src/lib/examine_content_job.cc @@ -26,7 +26,6 @@ #include "options.h" #include "decoder_factory.h" #include "decoder.h" -#include "imagemagick_encoder.h" #include "transcoder.h" #include "log.h" #include "film.h" @@ -60,13 +59,12 @@ ExamineContentJob::name () const void ExamineContentJob::run () { - float progress_remaining = 1; + descend (1); /* Set the film's length to either a) a length judged by running through the content or b) the length from a decoder's header. */ - if (!_film->trust_content_header()) { /* Decode the content to get an accurate length */ @@ -74,97 +72,33 @@ ExamineContentJob::run () will be messed up. */ _film->unset_length (); + _film->set_crop (Crop ()); - shared_ptr<Options> o (new Options ("", "", "")); - o->out_size = Size (512, 512); - o->apply_crop = false; + shared_ptr<DecodeOptions> o (new DecodeOptions); o->decode_audio = false; - descend (0.5); - - pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoders = decoder_factory (_film, o, this); + Decoders decoders = decoder_factory (_film, o, this); set_progress_unknown (); - while (!decoders.first->pass()) { + while (!decoders.video->pass()) { /* keep going */ } - _film->set_length (decoders.first->video_frame()); + _film->set_length (decoders.video->video_frame()); _film->log()->log (String::compose ("Video length examined as %1 frames", _film->length().get())); - ascend (); - - progress_remaining -= 0.5; - } else { - /* Get a quick decoder to get the content's length from its header. - It would have been nice to just use the thumbnail transcoder's decoder, - but that's a bit fiddly, and this isn't too expensive. - */ + /* Get a quick decoder to get the content's length from its header */ - shared_ptr<Options> o (new Options ("", "", "")); - o->out_size = Size (1024, 1024); - pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (_film, o, 0); - _film->set_length (d.first->length()); + shared_ptr<DecodeOptions> o (new DecodeOptions); + Decoders d = decoder_factory (_film, o, 0); + _film->set_length (d.video->length()); _film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get())); } - /* Now make thumbnails for it */ - - descend (progress_remaining); - - try { - shared_ptr<Options> o (new Options (_film->dir ("thumbs"), ".png", "")); - o->out_size = _film->size (); - o->apply_crop = false; - o->decode_audio = false; - o->decode_video_skip = _film->length().get() / 128; - o->decode_subtitles = true; - shared_ptr<ImageMagickEncoder> e (new ImageMagickEncoder (_film, o)); - Transcoder w (_film, o, this, e); - w.go (); - - /* Now set the film's length from the transcoder's decoder, since we - went to all the trouble of going through the content. - */ - - _film->set_length (w.video_decoder()->video_frame()); - - } catch (std::exception& e) { - - ascend (); - set_progress (1); - set_error (e.what ()); - set_state (FINISHED_ERROR); - return; - - } - - string const tdir = _film->dir ("thumbs"); - vector<SourceFrame> thumbs; - - for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (tdir); i != boost::filesystem::directory_iterator(); ++i) { - - /* Aah, the sweet smell of progress */ -#if BOOST_FILESYSTEM_VERSION == 3 - string const l = boost::filesystem::path(*i).leaf().generic_string(); -#else - string const l = i->leaf (); -#endif - - size_t const d = l.find (".png"); - size_t const t = l.find (".tmp"); - if (d != string::npos && t == string::npos) { - thumbs.push_back (atoi (l.substr (0, d).c_str())); - } - } - - sort (thumbs.begin(), thumbs.end()); - _film->set_thumbs (thumbs); - ascend (); set_progress (1); set_state (FINISHED_OK); diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc index 9b121235a..25c8068b6 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 Options> o, Job* j) +ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const 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 45a2a809c..2558955eb 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 Options>, Job *); + ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); bool pass (); diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index acaf149f4..136843190 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -59,7 +59,7 @@ using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j) +FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) : Decoder (f, o, j) , VideoDecoder (f, o, j) , AudioDecoder (f, o, j) @@ -77,6 +77,8 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, J setup_video (); setup_audio (); setup_subtitle (); + + _film_connection = f->Changed.connect (bind (&FFmpegDecoder::film_changed, this, _1)); } FFmpegDecoder::~FFmpegDecoder () @@ -101,11 +103,9 @@ FFmpegDecoder::~FFmpegDecoder () void FFmpegDecoder::setup_general () { - int r; - av_register_all (); - if ((r = avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0)) != 0) { + if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) { throw OpenFileError (_film->content_path ()); } @@ -265,46 +265,10 @@ FFmpegDecoder::pass () _film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size)); } - /* Where we are in the output, in seconds */ - double const out_pts_seconds = video_frame() / frames_per_second(); - - /* Where we are in the source, in seconds */ - double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame); - - _film->log()->log ( - String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds), - Log::VERBOSE - ); - - if (!_first_video) { - _first_video = source_pts_seconds; - } - - /* Difference between where we are and where we should be */ - double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds; - double const one_frame = 1 / frames_per_second(); - - /* Insert frames if required to get out_pts_seconds up to pts_seconds */ - if (delta > one_frame) { - int const extra = rint (delta / one_frame); - for (int i = 0; i < extra; ++i) { - repeat_last_video (); - _film->log()->log ( - String::compose ( - "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)", - out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second() - ) - ); - } - } - - if (delta > -one_frame) { - /* Process this frame */ - filter_and_emit_video (_frame); + if (_opt->video_sync) { + out_with_sync (); } else { - /* Otherwise we are omitting a frame to keep things right */ - _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds)); + filter_and_emit_video (_frame); } } @@ -557,6 +521,8 @@ FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) void FFmpegDecoder::filter_and_emit_video (AVFrame* frame) { + boost::mutex::scoped_lock lm (_filter_graphs_mutex); + shared_ptr<FilterGraph> graph; list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); @@ -565,7 +531,7 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame) } if (i == _filter_graphs.end ()) { - graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format)); + graph.reset (new FilterGraph (_film, this, Size (frame->width, frame->height), (AVPixelFormat) frame->format)); _filter_graphs.push_back (graph); _film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format)); } else { @@ -574,11 +540,23 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame) list<shared_ptr<Image> > images = graph->process (frame); + SourceFrame const sf = av_q2d (_format_context->streams[_video_stream]->time_base) + * av_frame_get_best_effort_timestamp(_frame) * frames_per_second(); + for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { - emit_video (*i); + emit_video (*i, sf); } } +bool +FFmpegDecoder::seek (SourceFrame f) +{ + int64_t const t = static_cast<int64_t>(f) / (av_q2d (_format_context->streams[_video_stream]->time_base) * frames_per_second()); + int const r = av_seek_frame (_format_context, _video_stream, t, 0); + avcodec_flush_buffers (_video_codec_context); + return r < 0; +} + shared_ptr<FFmpegAudioStream> FFmpegAudioStream::create (string t, optional<int> v) { @@ -631,9 +609,73 @@ FFmpegAudioStream::to_string () const return String::compose ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name); } +void +FFmpegDecoder::out_with_sync () +{ + /* Where we are in the output, in seconds */ + double const out_pts_seconds = video_frame() / frames_per_second(); + + /* Where we are in the source, in seconds */ + double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base) + * av_frame_get_best_effort_timestamp(_frame); + + _film->log()->log ( + String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds), + Log::VERBOSE + ); + + if (!_first_video) { + _first_video = source_pts_seconds; + } + + /* Difference between where we are and where we should be */ + double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds; + double const one_frame = 1 / frames_per_second(); + + /* Insert frames if required to get out_pts_seconds up to pts_seconds */ + if (delta > one_frame) { + int const extra = rint (delta / one_frame); + for (int i = 0; i < extra; ++i) { + repeat_last_video (); + _film->log()->log ( + String::compose ( + "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)", + out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second() + ) + ); + } + } + + if (delta > -one_frame) { + /* Process this frame */ + filter_and_emit_video (_frame); + } else { + /* Otherwise we are omitting a frame to keep things right */ + _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds)); + } +} + +void +FFmpegDecoder::film_changed (Film::Property p) +{ + switch (p) { + case Film::CROP: + { + boost::mutex::scoped_lock lm (_filter_graphs_mutex); + _filter_graphs.clear (); + } + OutputChanged (); + break; + + default: + break; + } +} + /** @return Length (in video frames) according to our content's header */ SourceFrame FFmpegDecoder::length () const { return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second(); } + diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 1771551fc..35688003e 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -26,6 +26,7 @@ #include <stdint.h> #include <boost/shared_ptr.hpp> #include <boost/optional.hpp> +#include <boost/thread/mutex.hpp> extern "C" { #include <libavcodec/avcodec.h> #include <libpostproc/postprocess.h> @@ -34,6 +35,7 @@ extern "C" { #include "decoder.h" #include "video_decoder.h" #include "audio_decoder.h" +#include "film.h" struct AVFilterGraph; struct AVCodecContext; @@ -84,7 +86,7 @@ private: class FFmpegDecoder : public VideoDecoder, public AudioDecoder { public: - FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); + FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); ~FFmpegDecoder (); float frames_per_second () const; @@ -98,6 +100,8 @@ public: void set_audio_stream (boost::shared_ptr<AudioStream>); void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); + bool seek (SourceFrame); + private: bool pass (); @@ -105,6 +109,7 @@ private: AVSampleFormat audio_sample_format () const; int bytes_per_audio_sample () const; + void out_with_sync (); void filter_and_emit_video (AVFrame *); void setup_general (); @@ -115,6 +120,9 @@ private: void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size); + void film_changed (Film::Property); + boost::signals2::scoped_connection _film_connection; + std::string stream_name (AVStream* s) const; AVFormatContext* _format_context; @@ -135,4 +143,5 @@ private: boost::optional<double> _first_audio; std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; + boost::mutex _filter_graphs_mutex; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index e2a4cbeda..4cfe7de0a 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -31,7 +31,6 @@ #include <boost/date_time.hpp> #include "film.h" #include "format.h" -#include "imagemagick_encoder.h" #include "job.h" #include "filter.h" #include "transcoder.h" @@ -171,7 +170,6 @@ Film::Film (Film const & o) , _studio (o._studio) , _facility (o._facility) , _package_type (o._package_type) - , _thumbs (o._thumbs) , _size (o._size) , _length (o._length) , _content_digest (o._content_digest) @@ -262,35 +260,37 @@ Film::make_dcp (bool transcode) throw MissingSettingError ("name"); } - shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", dir ("wavs"))); - o->out_size = format()->dcp_size (); - o->padding = format()->dcp_padding (shared_from_this ()); - o->ratio = format()->ratio_as_float (shared_from_this ()); + 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 ()) { - o->video_decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get()); + oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get()); if (audio_stream()) { - o->audio_decode_range = make_pair ( - video_frames_to_audio_frames (o->video_decode_range.get().first, audio_stream()->sample_rate(), frames_per_second()), - video_frames_to_audio_frames (o->video_decode_range.get().second, audio_stream()->sample_rate(), frames_per_second()) + oe->audio_range = make_pair ( + video_frames_to_audio_frames (oe->video_range.get().first, audio_stream()->sample_rate(), frames_per_second()), + video_frames_to_audio_frames (oe->video_range.get().second, audio_stream()->sample_rate(), frames_per_second()) ); } } - o->decode_subtitles = with_subtitles (); - o->decode_video_skip = dcp_frame_rate (frames_per_second()).skip; + + oe->video_skip = dcp_frame_rate (frames_per_second()).skip; + + shared_ptr<DecodeOptions> od (new DecodeOptions); + 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(), o, shared_ptr<Job> ()))); + r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ()))); } else { - r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), o, shared_ptr<Job> ()))); + 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 CheckHashesJob (shared_from_this(), o, r))); - JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), o, r))); + 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 */ @@ -301,12 +301,6 @@ Film::examine_content () return; } - set_thumbs (vector<SourceFrame> ()); - boost::filesystem::remove_all (dir ("thumbs")); - - /* This call will recreate the directory */ - dir ("thumbs"); - _examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr<Job> ())); _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this)); JobManager::instance()->add (_examine_content_job); @@ -357,35 +351,6 @@ Film::encoded_frames () const return N; } -/** Return the filename of a subtitle image if one exists for a given thumb index. - * @param Thumbnail index. - * @return Position of the image within the source frame, and the image filename, if one exists. - * Otherwise the filename will be empty. - */ -pair<Position, string> -Film::thumb_subtitle (int n) const -{ - string sub_file = thumb_base(n) + ".sub"; - if (!boost::filesystem::exists (sub_file)) { - return pair<Position, string> (); - } - - pair<Position, string> sub; - - ifstream f (sub_file.c_str ()); - multimap<string, string> kv = read_key_value (f); - for (map<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) { - if (i->first == "x") { - sub.first.x = lexical_cast<int> (i->second); - } else if (i->first == "y") { - sub.first.y = lexical_cast<int> (i->second); - sub.second = String::compose ("%1.sub.png", thumb_base(n)); - } - } - - return sub; -} - /** Write state to our `metadata' file */ void Film::write_metadata () const @@ -448,12 +413,6 @@ Film::write_metadata () const f << "facility " << _facility << "\n"; f << "package_type " << _package_type << "\n"; - /* Cached stuff; this is information about our content; we could - look it up each time, but that's slow. - */ - for (vector<SourceFrame>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) { - f << "thumb " << *i << "\n"; - } f << "width " << _size.width << "\n"; f << "height " << _size.height << "\n"; f << "length " << _length.get_value_or(0) << "\n"; @@ -481,7 +440,6 @@ Film::read_metadata () boost::mutex::scoped_lock lm (_state_mutex); _external_audio.clear (); - _thumbs.clear (); _content_audio_streams.clear (); _subtitle_streams.clear (); @@ -585,13 +543,7 @@ Film::read_metadata () } /* Cached stuff */ - if (k == "thumb") { - int const n = atoi (v.c_str ()); - /* Only add it to the list if it still exists */ - if (boost::filesystem::exists (thumb_file_for_frame (n))) { - _thumbs.push_back (n); - } - } else if (k == "width") { + if (k == "width") { _size.width = atoi (v.c_str ()); } else if (k == "height") { _size.height = atoi (v.c_str ()); @@ -635,61 +587,6 @@ Film::read_metadata () _dirty = false; } -/** @param n A thumb index. - * @return The path to the thumb's image file. - */ -string -Film::thumb_file (int n) const -{ - return thumb_file_for_frame (thumb_frame (n)); -} - -/** @param n A frame index within the Film's source. - * @return The path to the thumb's image file for this frame; - * we assume that it exists. - */ -string -Film::thumb_file_for_frame (SourceFrame n) const -{ - return thumb_base_for_frame(n) + ".png"; -} - -/** @param n Thumb index. - * Must not be called with the _state_mutex locked. - */ -string -Film::thumb_base (int n) const -{ - return thumb_base_for_frame (thumb_frame (n)); -} - -string -Film::thumb_base_for_frame (SourceFrame n) const -{ - stringstream s; - s.width (8); - s << setfill('0') << n; - - boost::filesystem::path p; - p /= dir ("thumbs"); - p /= s.str (); - - return p.string (); -} - -/** @param n A thumb index. - * @return The frame within the Film's source that it is for. - * - * Must not be called with the _state_mutex locked. - */ -SourceFrame -Film::thumb_frame (int n) const -{ - boost::mutex::scoped_lock lm (_state_mutex); - assert (n < int (_thumbs.size ())); - return _thumbs[n]; -} - Size Film::cropped_size (Size s) const { @@ -954,23 +851,21 @@ Film::set_content (string c) */ try { - shared_ptr<Options> o (new Options ("", "", "")); - o->out_size = Size (1024, 1024); - - pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (shared_from_this(), o, 0); + shared_ptr<DecodeOptions> o (new DecodeOptions); + Decoders d = decoder_factory (shared_from_this(), o, 0); - set_size (d.first->native_size ()); - set_frames_per_second (d.first->frames_per_second ()); - set_subtitle_streams (d.first->subtitle_streams ()); - set_content_audio_streams (d.second->audio_streams ()); + set_size (d.video->native_size ()); + set_frames_per_second (d.video->frames_per_second ()); + set_subtitle_streams (d.video->subtitle_streams ()); + set_content_audio_streams (d.audio->audio_streams ()); /* Start off with the first audio and subtitle streams */ - if (!d.second->audio_streams().empty()) { - set_content_audio_stream (d.second->audio_streams().front()); + if (!d.audio->audio_streams().empty()) { + set_content_audio_stream (d.audio->audio_streams().front()); } - if (!d.first->subtitle_streams().empty()) { - set_subtitle_stream (d.first->subtitle_streams().front()); + if (!d.video->subtitle_streams().empty()) { + set_subtitle_stream (d.video->subtitle_streams().front()); } { @@ -1003,7 +898,7 @@ Film::set_trust_content_header (bool t) signal_changed (TRUST_CONTENT_HEADER); - if (!_trust_content_header) && !content().empty()) { + if (!_trust_content_header && !content().empty()) { /* We just said that we don't trust the content's header */ examine_content (); } @@ -1164,8 +1059,7 @@ Film::set_external_audio (vector<string> a) _external_audio = a; } - shared_ptr<Options> o (new Options ("", "", "")); - o->decode_audio = true; + shared_ptr<DecodeOptions> o (new DecodeOptions); shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0)); if (decoder->audio_stream()) { _external_audio_stream = decoder->audio_stream (); @@ -1326,16 +1220,6 @@ Film::set_package_type (string p) } void -Film::set_thumbs (vector<SourceFrame> t) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _thumbs = t; - } - signal_changed (THUMBS); -} - -void Film::set_size (Size s) { { diff --git a/src/lib/film.h b/src/lib/film.h index 8cd55a227..642f2d7da 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -61,7 +61,6 @@ public: std::string j2k_dir () const; std::vector<std::string> audio_files () const; - std::pair<Position, std::string> thumb_subtitle (int) const; void examine_content (); void send_dcp_to_tms (); @@ -83,10 +82,6 @@ public: std::string content_path () const; ContentType content_type () const; - std::string thumb_file (int) const; - std::string thumb_base (int) const; - SourceFrame thumb_frame (int) const; - int target_audio_sample_rate () const; void write_metadata () const; @@ -130,7 +125,6 @@ public: SUBTITLE_OFFSET, SUBTITLE_SCALE, DCI_METADATA, - THUMBS, SIZE, LENGTH, CONTENT_AUDIO_STREAMS, @@ -291,11 +285,6 @@ public: return _package_type; } - std::vector<SourceFrame> thumbs () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _thumbs; - } - Size size () const { boost::mutex::scoped_lock lm (_state_mutex); return _size; @@ -365,7 +354,6 @@ public: void set_studio (std::string); void set_facility (std::string); void set_package_type (std::string); - void set_thumbs (std::vector<SourceFrame>); void set_size (Size); void set_length (SourceFrame); void unset_length (); @@ -391,8 +379,6 @@ private: /** The date that we should use in a DCI name */ boost::gregorian::date _dci_date; - std::string thumb_file_for_frame (SourceFrame) const; - std::string thumb_base_for_frame (SourceFrame) const; void signal_changed (Property); void examine_content_finished (); @@ -466,8 +452,6 @@ private: /* Data which are cached to speed things up */ - /** Vector of frame indices for each of our `thumbnails' */ - std::vector<SourceFrame> _thumbs; /** Size, in pixels, of the source (ignoring cropping) */ Size _size; /** Actual length of the source (in video frames) from examining it */ diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 7320070fe..17107a05b 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -49,11 +49,10 @@ using boost::shared_ptr; /** Construct a FilterGraph for the settings in a film. * @param film Film. * @param decoder Decoder that we are using. - * @param crop true to apply crop, otherwise false. * @param s Size of the images to process. * @param p Pixel format of the images to process. */ -FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p) +FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) @@ -64,11 +63,7 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, bool cr filters += ","; } - if (crop) { - filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size())); - } else { - filters += crop_string (Position (0, 0), decoder->native_size()); - } + filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size())); avfilter_register_all (); diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index 3842e9f7d..a4b9ef75f 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -36,7 +36,7 @@ class FFmpegDecoder; class FilterGraph { public: - FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p); + FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, Size s, AVPixelFormat p); bool can_process (Size s, AVPixelFormat p) const; std::list<boost::shared_ptr<Image> > process (AVFrame const * frame); diff --git a/src/lib/image.cc b/src/lib/image.cc index 72828ed46..748e9ae4b 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -125,7 +125,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal Size content_size = out_size; content_size.width -= (padding * 2); - shared_ptr<Image> rgb (new AlignedImage (PIX_FMT_RGB24, content_size)); + shared_ptr<Image> rgb (new CompactImage (PIX_FMT_RGB24, content_size)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index d68c1648f..9d11e043f 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -29,7 +29,7 @@ using std::cout; using boost::shared_ptr; ImageMagickDecoder::ImageMagickDecoder ( - boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j) + boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j) : Decoder (f, o, j) , VideoDecoder (f, o, j) { @@ -92,7 +92,7 @@ ImageMagickDecoder::pass () delete magick_image; - emit_video (image); + emit_video (image, 0); ++_iter; return false; diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index de49c1b56..cfcf4b4f6 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 Options>, Job *); + ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); float frames_per_second () const { /* We don't know */ diff --git a/src/lib/imagemagick_encoder.cc b/src/lib/imagemagick_encoder.cc deleted file mode 100644 index 480dec8bc..000000000 --- a/src/lib/imagemagick_encoder.cc +++ /dev/null @@ -1,91 +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. - -*/ - -/** @file src/imagemagick_encoder.cc - * @brief An encoder that writes image files using ImageMagick (and does nothing with audio). - */ - -#include <stdexcept> -#include <vector> -#include <sstream> -#include <iomanip> -#include <iostream> -#include <fstream> -#include <boost/filesystem.hpp> -#include <Magick++/Image.h> -#include "imagemagick_encoder.h" -#include "film.h" -#include "options.h" -#include "exceptions.h" -#include "image.h" -#include "subtitle.h" - -using std::string; -using std::ofstream; -using boost::shared_ptr; - -/** @param f Film that we are encoding. - * @param o Options. - */ -ImageMagickEncoder::ImageMagickEncoder (shared_ptr<const Film> f, shared_ptr<const Options> o) - : Encoder (f, o) -{ - -} - -void -ImageMagickEncoder::do_process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) -{ - shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _film->scaler()); - shared_ptr<Image> compact (new CompactImage (scaled)); - - string tmp_file = _opt->frame_out_path (_video_frame, true); - Magick::Image thumb (compact->size().width, compact->size().height, "RGB", MagickCore::CharPixel, compact->data()[0]); - thumb.magick ("PNG"); - thumb.write (tmp_file); - boost::filesystem::rename (tmp_file, _opt->frame_out_path (_video_frame, false)); - - if (sub) { - float const x_scale = float (_opt->out_size.width) / _film->size().width; - float const y_scale = float (_opt->out_size.height) / _film->size().height; - - string tmp_metadata_file = _opt->frame_out_path (_video_frame, false, ".sub"); - ofstream metadata (tmp_metadata_file.c_str ()); - - Size new_size = sub->image()->size (); - new_size.width *= x_scale; - new_size.height *= y_scale; - shared_ptr<Image> scaled = sub->image()->scale (new_size, _film->scaler()); - shared_ptr<Image> compact (new CompactImage (scaled)); - - string tmp_sub_file = _opt->frame_out_path (_video_frame, true, ".sub.png"); - Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]); - sub_thumb.magick ("PNG"); - sub_thumb.write (tmp_sub_file); - boost::filesystem::rename (tmp_sub_file, _opt->frame_out_path (_video_frame, false, ".sub.png")); - - metadata << "x " << sub->position().x << "\n" - << "y " << sub->position().y << "\n"; - - metadata.close (); - boost::filesystem::rename (tmp_metadata_file, _opt->frame_out_path (_video_frame, false, ".sub")); - } - - frame_done (); -} diff --git a/src/lib/imagemagick_encoder.h b/src/lib/imagemagick_encoder.h deleted file mode 100644 index dfc741cb2..000000000 --- a/src/lib/imagemagick_encoder.h +++ /dev/null @@ -1,42 +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. - -*/ - -/** @file src/imagemagick_encoder.h - * @brief An encoder that writes image files using ImageMagick (and does nothing with audio). - */ - -#include <string> -#include <sndfile.h> -#include "encoder.h" - -class FilmState; -class Log; - -/** @class ImageMagickEncoder - * @brief An encoder that writes image files using ImageMagick files (and does nothing with audio). - */ -class ImageMagickEncoder : public Encoder -{ -public: - ImageMagickEncoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o); - -private: - void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); - void do_process_audio (boost::shared_ptr<AudioBuffers>) {} -}; diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc index dd6ef49b2..68088377b 100644 --- a/src/lib/j2k_still_encoder.cc +++ b/src/lib/j2k_still_encoder.cc @@ -42,7 +42,7 @@ using std::string; using std::pair; using boost::shared_ptr; -J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const Options> o) +J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) : Encoder (f, o) { diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h index 4ffe876af..6069637d0 100644 --- a/src/lib/j2k_still_encoder.h +++ b/src/lib/j2k_still_encoder.h @@ -27,6 +27,7 @@ class Image; class Log; +class EncodeOptions; /** @class J2KStillEncoder * @brief An encoder which writes repeated JPEG2000 files from a single decoded input. @@ -34,7 +35,7 @@ class Log; class J2KStillEncoder : public Encoder { public: - J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>); + J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>); private: void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc index 134d74623..e76591552 100644 --- a/src/lib/j2k_wav_encoder.cc +++ b/src/lib/j2k_wav_encoder.cc @@ -51,7 +51,7 @@ using boost::shared_ptr; using boost::thread; using boost::lexical_cast; -J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const Film> f, shared_ptr<const Options> o) +J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) : Encoder (f, o) #ifdef HAVE_SWRESAMPLE , _swr_context (0) diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h index f3340ba72..064f4221e 100644 --- a/src/lib/j2k_wav_encoder.h +++ b/src/lib/j2k_wav_encoder.h @@ -47,7 +47,7 @@ class AudioBuffers; class J2KWAVEncoder : public Encoder { public: - J2KWAVEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>); + J2KWAVEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>); ~J2KWAVEncoder (); void process_begin (); diff --git a/src/lib/job.h b/src/lib/job.h index 41cefb9be..f32cfa811 100644 --- a/src/lib/job.h +++ b/src/lib/job.h @@ -30,7 +30,6 @@ #include <boost/signals2.hpp> class Film; -class Options; /** @class Job * @brief A parent class to represent long-running tasks which are run in their own thread. diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc index 65cd272e7..4605d1724 100644 --- a/src/lib/make_dcp_job.cc +++ b/src/lib/make_dcp_job.cc @@ -42,7 +42,7 @@ using boost::shared_ptr; /** @param f Film we are making the DCP for. * @param o Options. */ -MakeDCPJob::MakeDCPJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req) +MakeDCPJob::MakeDCPJob (shared_ptr<Film> f, shared_ptr<const EncodeOptions> o, shared_ptr<Job> req) : Job (f, req) , _opt (o) { diff --git a/src/lib/make_dcp_job.h b/src/lib/make_dcp_job.h index 442bb55f5..1aa906b0a 100644 --- a/src/lib/make_dcp_job.h +++ b/src/lib/make_dcp_job.h @@ -23,13 +23,15 @@ #include "job.h" +class EncodeOptions; + /** @class MakeDCPJob * @brief A job to create DCPs */ class MakeDCPJob : public Job { public: - MakeDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, boost::shared_ptr<Job> req); + MakeDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<const EncodeOptions>, boost::shared_ptr<Job> req); std::string name () const; void run (); @@ -39,6 +41,6 @@ private: std::string j2c_path (int) const; std::string wav_path (libdcp::Channel) const; - boost::shared_ptr<const Options> _opt; + boost::shared_ptr<const EncodeOptions> _opt; }; diff --git a/src/lib/options.h b/src/lib/options.h index 29b3b71cd..4457969f3 100644 --- a/src/lib/options.h +++ b/src/lib/options.h @@ -27,22 +27,19 @@ #include <boost/optional.hpp> #include "util.h" -/** @class Options - * @brief Options for a transcoding operation. +/** @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 transcode operation. + * the same film; ie they are options for a particular operation. */ -class Options +class EncodeOptions { public: - Options (std::string f, std::string e, std::string m) + EncodeOptions (std::string f, std::string e, std::string m) : padding (0) - , apply_crop (true) - , decode_video_skip (0) - , decode_audio (true) - , decode_subtitles (false) + , video_skip (0) , _frame_out_path (f) , _frame_out_extension (e) , _multichannel_audio_out_path (m) @@ -94,21 +91,17 @@ public: } Size out_size; ///< size of output images - float ratio; ///< ratio of the wanted output image (not considering padding) int padding; ///< number of pixels of padding (in terms of the output size) each side of the image - bool apply_crop; ///< true to apply cropping /** Range of video frames to decode */ - boost::optional<std::pair<SourceFrame, SourceFrame> > video_decode_range; + boost::optional<std::pair<SourceFrame, SourceFrame> > video_range; /** Range of audio frames to decode */ - boost::optional<std::pair<int64_t, int64_t> > audio_decode_range; + 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 decode_video_skip; - bool decode_audio; ///< true to decode audio, otherwise false - bool decode_subtitles; + SourceFrame video_skip; private: /** Path of the directory to write video frames to */ @@ -118,3 +111,18 @@ private: /** Path of the directory to write audio files to */ std::string _multichannel_audio_out_path; }; + + +class DecodeOptions +{ +public: + DecodeOptions () + : decode_audio (true) + , decode_subtitles (false) + , video_sync (true) + {} + + bool decode_audio; + bool decode_subtitles; + bool video_sync; +}; diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 081e04252..54619c39f 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -41,9 +41,10 @@ using boost::shared_ptr; * @param o Options. * @param req Job that must be completed before this job is run. */ -TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req) +TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req) : Job (f, req) - , _opt (o) + , _decode_opt (od) + , _encode_opt (oe) { } @@ -62,8 +63,8 @@ TranscodeJob::run () _film->log()->log ("Transcode job starting"); _film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay())); - _encoder = encoder_factory (_film, _opt); - Transcoder w (_film, _opt, this, _encoder); + _encoder = encoder_factory (_film, _encode_opt); + Transcoder w (_film, _decode_opt, this, _encoder); w.go (); set_progress (1); set_state (FINISHED_OK); diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h index 1decea070..97f655e15 100644 --- a/src/lib/transcode_job.h +++ b/src/lib/transcode_job.h @@ -25,6 +25,8 @@ #include "job.h" class Encoder; +class DecodeOptions; +class EncodeOptions; /** @class TranscodeJob * @brief A job which transcodes from one format to another. @@ -32,7 +34,7 @@ class Encoder; class TranscodeJob : public Job { public: - TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, boost::shared_ptr<Job> req); + TranscodeJob (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 (); @@ -42,6 +44,7 @@ protected: int remaining_time () const; private: - boost::shared_ptr<const Options> _opt; + boost::shared_ptr<const DecodeOptions> _decode_opt; + boost::shared_ptr<const EncodeOptions> _encode_opt; boost::shared_ptr<Encoder> _encoder; }; diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index a7e79b05f..87a1fb3f2 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -44,11 +44,11 @@ using boost::dynamic_pointer_cast; /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. - * @param o Options. + * @param o Decode options. * @param j Job that we are running under, or 0. * @param e Encoder to use. */ -Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e) +Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e) : _job (j) , _encoder (e) , _decoders (decoder_factory (f, o, j)) @@ -63,20 +63,20 @@ Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, } /* Set up the decoder to use the film's set streams */ - _decoders.first->set_subtitle_stream (f->subtitle_stream ()); - if (_decoders.second) { - _decoders.second->set_audio_stream (f->audio_stream ()); + _decoders.video->set_subtitle_stream (f->subtitle_stream ()); + if (_decoders.audio) { + _decoders.audio->set_audio_stream (f->audio_stream ()); } if (_matcher) { - _decoders.first->connect_video (_matcher); + _decoders.video->connect_video (_matcher); _matcher->connect_video (_encoder); } else { - _decoders.first->connect_video (_encoder); + _decoders.video->connect_video (_encoder); } - if (_matcher && _delay_line && _decoders.second) { - _decoders.second->connect_audio (_delay_line); + if (_matcher && _delay_line && _decoders.audio) { + _decoders.audio->connect_audio (_delay_line); _delay_line->connect_audio (_matcher); _matcher->connect_audio (_gain); _gain->connect_audio (_encoder); @@ -95,12 +95,12 @@ Transcoder::go () while (1) { if (!done[0]) { - done[0] = _decoders.first->pass (); - _decoders.first->set_progress (); + done[0] = _decoders.video->pass (); + _decoders.video->set_progress (); } - if (!done[1] && _decoders.second && dynamic_pointer_cast<Decoder> (_decoders.second) != dynamic_pointer_cast<Decoder> (_decoders.first)) { - done[1] = _decoders.second->pass (); + if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) { + done[1] = _decoders.audio->pass (); } else { done[1] = true; } diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index 4a9667b3c..b50113742 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -24,6 +24,8 @@ * as a parameter to the constructor. */ +#include "decoder_factory.h" + class Film; class Job; class Encoder; @@ -34,7 +36,8 @@ class Gain; class VideoDecoder; class AudioDecoder; class DelayLine; -class Options; +class EncodeOptions; +class DecodeOptions; /** @class Transcoder * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. @@ -45,12 +48,17 @@ class Options; class Transcoder { public: - Transcoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j, boost::shared_ptr<Encoder> e); + Transcoder ( + boost::shared_ptr<Film> f, + boost::shared_ptr<const DecodeOptions> o, + Job* j, + boost::shared_ptr<Encoder> e + ); void go (); boost::shared_ptr<VideoDecoder> video_decoder () const { - return _decoders.first; + return _decoders.video; } protected: @@ -59,7 +67,7 @@ protected: /** The encoder that we will use */ boost::shared_ptr<Encoder> _encoder; /** The decoders that we will use */ - std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _decoders; + Decoders _decoders; boost::shared_ptr<Matcher> _matcher; boost::shared_ptr<DelayLine> _delay_line; boost::shared_ptr<Gain> _gain; diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 23a69f958..d3b441fbf 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -28,19 +28,20 @@ using boost::shared_ptr; using boost::optional; -VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j) +VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j) : Decoder (f, o, j) , _video_frame (0) + , _last_source_frame (0) { } /** Called by subclasses to tell the world that some video data is ready. * We find a subtitle then emit it for listeners. - * @param frame to decode; caller manages memory. + * @param frame to emit. */ void -VideoDecoder::emit_video (shared_ptr<Image> image) +VideoDecoder::emit_video (shared_ptr<Image> image, SourceFrame f) { shared_ptr<Subtitle> sub; if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) { @@ -48,6 +49,7 @@ VideoDecoder::emit_video (shared_ptr<Image> image) } signal_video (image, sub); + _last_source_frame = f; } void @@ -77,7 +79,7 @@ VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s) { _timed_subtitle = s; - if (_timed_subtitle && _opt->apply_crop) { + if (_timed_subtitle) { Position const p = _timed_subtitle->subtitle()->position (); _timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top)); } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 685138a58..f682941d1 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 Options>, Job *); + VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *); /** @return video frames per second, or 0 if unknown */ virtual float frames_per_second () const = 0; @@ -57,11 +57,15 @@ public: return _subtitle_streams; } + SourceFrame last_source_frame () const { + return _last_source_frame; + } + protected: virtual PixelFormat pixel_format () const = 0; - void emit_video (boost::shared_ptr<Image>); + void emit_video (boost::shared_ptr<Image>, SourceFrame); void emit_subtitle (boost::shared_ptr<TimedSubtitle>); void repeat_last_video (); @@ -74,7 +78,8 @@ private: void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); SourceFrame _video_frame; - + SourceFrame _last_source_frame; + boost::shared_ptr<TimedSubtitle> _timed_subtitle; boost::shared_ptr<Image> _last_image; diff --git a/src/lib/wscript b/src/lib/wscript index c1786bb81..5d3fbb906 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -37,7 +37,6 @@ def build(bld): gain.cc image.cc imagemagick_decoder.cc - imagemagick_encoder.cc j2k_still_encoder.cc j2k_wav_encoder.cc job.cc diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc index 993c41563..d5d5bfc2f 100644 --- a/src/tools/dvdomatic.cc +++ b/src/tools/dvdomatic.cc @@ -226,7 +226,6 @@ public: /* XXX: calling these here is a bit of a hack */ film_editor->setup_visibility (); - film_viewer->setup_visibility (); film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1)); if (film) { diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc index d6804c981..41ec8075d 100644 --- a/src/tools/servomatictest.cc +++ b/src/tools/servomatictest.cc @@ -135,7 +135,6 @@ main (int argc, char* argv[]) shared_ptr<Options> opt (new Options ("fred", "jim", "sheila")); opt->out_size = Size (1024, 1024); - opt->apply_crop = false; opt->decode_audio = false; shared_ptr<Decoder> decoder = decoder_factory (film.state_copy(), opt, 0, &log_); diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 1e592e268..d33f89ce6 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -588,8 +588,6 @@ FilmEditor::film_changed (Film::Property p) checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ())); _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); break; - case Film::THUMBS: - break; case Film::DCP_AB: checked_set (_dcp_ab, _film->dcp_ab ()); break; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index a82132358..8312fa5e5 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -18,307 +18,229 @@ */ /** @file src/film_viewer.cc - * @brief A wx widget to view `thumbnails' of a Film. + * @brief A wx widget to view a preview of a Film. */ #include <iostream> #include <iomanip> +#include <wx/tglbtn.h> #include "lib/film.h" #include "lib/format.h" #include "lib/util.h" #include "lib/job_manager.h" #include "lib/options.h" #include "lib/subtitle.h" +#include "lib/image.h" +#include "lib/scaler.h" #include "film_viewer.h" #include "wx_util.h" +#include "video_decoder.h" using std::string; using std::pair; using std::max; +using std::cout; using boost::shared_ptr; -class ThumbPanel : public wxPanel +FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) + : wxPanel (p) + , _panel (new wxPanel (this)) + , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096)) + , _play_button (new wxToggleButton (this, wxID_ANY, wxT ("Play"))) + , _out_width (0) + , _out_height (0) + , _panel_width (0) + , _panel_height (0) { -public: - ThumbPanel (wxPanel* parent, shared_ptr<Film> film) - : wxPanel (parent) - , _film (film) - , _index (0) - , _frame_rebuild_needed (false) - , _composition_needed (false) - {} - - /** Handle a paint event */ - void paint_event (wxPaintEvent& ev) - { - if (!_film || _film->thumbs().size() == 0) { - wxPaintDC dc (this); - return; - } - - if (_frame_rebuild_needed) { - _image.reset (new wxImage (std_to_wx (_film->thumb_file (_index)))); - - _subtitle.reset (); - pair<Position, string> s = _film->thumb_subtitle (_index); - if (!s.second.empty ()) { - _subtitle.reset (new SubtitleView (s.first, std_to_wx (s.second))); - } - - _frame_rebuild_needed = false; - compose (); - } + wxBoxSizer* v_sizer = new wxBoxSizer (wxVERTICAL); + SetSizer (v_sizer); - if (_composition_needed) { - compose (); - } + v_sizer->Add (_panel, 1, wxEXPAND); - wxPaintDC dc (this); - if (_bitmap) { - dc.DrawBitmap (*_bitmap, 0, 0, false); - } + wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL); + h_sizer->Add (_play_button, 0, wxEXPAND); + h_sizer->Add (_slider, 1, wxEXPAND); - if (_film->with_subtitles() && _subtitle) { - dc.DrawBitmap (*_subtitle->bitmap, _subtitle->transformed_area.x, _subtitle->transformed_area.y, true); - } - } + v_sizer->Add (h_sizer, 0, wxEXPAND); - /** Handle a size event */ - void size_event (wxSizeEvent &) - { - if (!_image) { - return; - } + _panel->Bind (wxEVT_PAINT, &FilmViewer::paint_panel, this); + _panel->Bind (wxEVT_SIZE, &FilmViewer::panel_sized, this); + _slider->Bind (wxEVT_SCROLL_THUMBTRACK, &FilmViewer::slider_moved, this); + _slider->Bind (wxEVT_SCROLL_PAGEUP, &FilmViewer::slider_moved, this); + _slider->Bind (wxEVT_SCROLL_PAGEDOWN, &FilmViewer::slider_moved, this); + _play_button->Bind (wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &FilmViewer::play_clicked, this); + _timer.Bind (wxEVT_TIMER, &FilmViewer::timer, this); - recompose (); - } + set_film (_film); +} - /** @param n Thumbnail index */ - void set (int n) +void +FilmViewer::film_changed (Film::Property p) +{ + switch (p) { + case Film::FORMAT: + calculate_sizes (); + update_from_raw (); + break; + case Film::CONTENT: { - _index = n; - _frame_rebuild_needed = true; - Refresh (); + shared_ptr<DecodeOptions> o (new DecodeOptions); + o->decode_audio = false; + o->video_sync = false; + _decoders = decoder_factory (_film, o, 0); + _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2)); + _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this)); + break; } - - void set_film (shared_ptr<Film> f) - { - _film = f; - if (!_film) { - clear (); - _frame_rebuild_needed = true; - Refresh (); - } else { - _frame_rebuild_needed = true; - Refresh (); - } + default: + break; } +} - /** Clear our thumbnail image */ - void clear () - { - _bitmap.reset (); - _image.reset (); - _subtitle.reset (); +void +FilmViewer::set_film (shared_ptr<Film> f) +{ + if (_film == f) { + return; } + + _film = f; - void recompose () - { - _composition_needed = true; - Refresh (); + if (!_film) { + return; } - DECLARE_EVENT_TABLE (); - -private: + _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1)); - void compose () - { - _composition_needed = false; - - if (!_film || !_image) { - return; - } - - /* Size of the view */ - int vw, vh; - GetSize (&vw, &vh); - - Crop const fc = _film->crop (); - - /* Cropped rectangle */ - Rect cropped_area ( - fc.left, - fc.top, - _image->GetWidth() - (fc.left + fc.right), - _image->GetHeight() - (fc.top + fc.bottom) - ); - - /* Target ratio */ - float const target = _film->format() ? _film->format()->ratio_as_float (_film) : 1.78; - - _transformed_image = _image->GetSubImage (wxRect (cropped_area.x, cropped_area.y, cropped_area.width, cropped_area.height)); - - float x_scale = 1; - float y_scale = 1; - - if ((float (vw) / vh) > target) { - /* view is longer (horizontally) than the ratio; fit height */ - _transformed_image.Rescale (vh * target, vh, wxIMAGE_QUALITY_HIGH); - x_scale = vh * target / cropped_area.width; - y_scale = float (vh) / cropped_area.height; - } else { - /* view is shorter (horizontally) than the ratio; fit width */ - _transformed_image.Rescale (vw, vw / target, wxIMAGE_QUALITY_HIGH); - x_scale = float (vw) / cropped_area.width; - y_scale = (vw / target) / cropped_area.height; - } + film_changed (Film::CONTENT); + film_changed (Film::CROP); + film_changed (Film::FORMAT); +} - _bitmap.reset (new wxBitmap (_transformed_image)); +void +FilmViewer::decoder_changed () +{ + seek_and_update (_decoders.video->last_source_frame ()); +} - if (_subtitle) { +void +FilmViewer::timer (wxTimerEvent& ev) +{ + _panel->Refresh (); + _panel->Update (); - _subtitle->transformed_area = subtitle_transformed_area ( - x_scale, y_scale, _subtitle->base_area, _film->subtitle_offset(), _film->subtitle_scale() - ); + shared_ptr<Image> last = _display; + while (last == _display) { + _decoders.video->pass (); + } - _subtitle->transformed_image = _subtitle->base_image; - _subtitle->transformed_image.Rescale (_subtitle->transformed_area.width, _subtitle->transformed_area.height, wxIMAGE_QUALITY_HIGH); - _subtitle->transformed_area.x -= rint (_film->crop().left * x_scale); - _subtitle->transformed_area.y -= rint (_film->crop().top * y_scale); - _subtitle->bitmap.reset (new wxBitmap (_subtitle->transformed_image)); + if (_film->length()) { + int const new_slider_position = 4096 * _decoders.video->last_source_frame() / _film->length().get(); + if (new_slider_position != _slider->GetValue()) { + _slider->SetValue (new_slider_position); } } +} - shared_ptr<Film> _film; - shared_ptr<wxImage> _image; - wxImage _transformed_image; - /** currently-displayed thumbnail index */ - int _index; - shared_ptr<wxBitmap> _bitmap; - bool _frame_rebuild_needed; - bool _composition_needed; - struct SubtitleView - { - SubtitleView (Position p, wxString const & i) - : base_image (i) - { - base_area.x = p.x; - base_area.y = p.y; - base_area.width = base_image.GetWidth (); - base_area.height = base_image.GetHeight (); - } +void +FilmViewer::paint_panel (wxPaintEvent& ev) +{ + wxPaintDC dc (_panel); + if (!_display) { + return; + } - Rect base_area; - Rect transformed_area; - wxImage base_image; - wxImage transformed_image; - shared_ptr<wxBitmap> bitmap; - }; + wxImage i (_out_width, _out_height, _display->data()[0], true); + wxBitmap b (i); + dc.DrawBitmap (b, 0, 0); +} - shared_ptr<SubtitleView> _subtitle; -}; -BEGIN_EVENT_TABLE (ThumbPanel, wxPanel) -EVT_PAINT (ThumbPanel::paint_event) -EVT_SIZE (ThumbPanel::size_event) -END_EVENT_TABLE () +void +FilmViewer::slider_moved (wxCommandEvent& ev) +{ + if (_film->length()) { + seek_and_update (_slider->GetValue() * _film->length().get() / 4096); + } +} -FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) - : wxPanel (p) +void +FilmViewer::seek_and_update (SourceFrame f) { - _sizer = new wxBoxSizer (wxVERTICAL); - SetSizer (_sizer); + if (_decoders.video->seek (f)) { + return; + } - _thumb_panel = new ThumbPanel (this, f); - _sizer->Add (_thumb_panel, 1, wxEXPAND); - - int const m = max ((size_t) 1, f ? f->thumbs().size() - 1 : 0); - _slider = new wxSlider (this, wxID_ANY, 0, 0, m); - _sizer->Add (_slider, 0, wxEXPAND | wxLEFT | wxRIGHT); - set_thumbnail (0); - - _slider->Connect (wxID_ANY, wxEVT_COMMAND_SLIDER_UPDATED, wxCommandEventHandler (FilmViewer::slider_changed), 0, this); + shared_ptr<Image> last = _display; + while (last == _display) { + _decoders.video->pass (); + } + _panel->Refresh (); + _panel->Update (); +} - set_film (_film); +void +FilmViewer::panel_sized (wxSizeEvent& ev) +{ + _panel_width = ev.GetSize().GetWidth(); + _panel_height = ev.GetSize().GetHeight(); + calculate_sizes (); + update_from_raw (); } void -FilmViewer::set_thumbnail (int n) +FilmViewer::update_from_raw () { - if (_film == 0 || int (_film->thumbs().size()) <= n) { + if (!_raw) { return; } - _thumb_panel->set (n); + if (_out_width && _out_height) { + _display = _raw->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, Scaler::from_id ("bicubic")); + } + + _panel->Refresh (); + _panel->Update (); } void -FilmViewer::slider_changed (wxCommandEvent &) +FilmViewer::calculate_sizes () { - set_thumbnail (_slider->GetValue ()); + float const panel_ratio = static_cast<float> (_panel_width) / _panel_height; + float const film_ratio = _film->format() ? _film->format()->ratio_as_float(_film) : 1.78; + if (panel_ratio < film_ratio) { + /* panel is less widscreen than the film; clamp width */ + _out_width = _panel_width; + _out_height = _out_width / film_ratio; + } else { + /* panel is more widescreen than the film; clamp heignt */ + _out_height = _panel_height; + _out_width = _out_height * film_ratio; + } } void -FilmViewer::film_changed (Film::Property p) +FilmViewer::play_clicked (wxCommandEvent &) { - ensure_ui_thread (); - - switch (p) { - case Film::THUMBS: - if (_film && _film->thumbs().size() > 1) { - _slider->SetRange (0, _film->thumbs().size() - 1); - } else { - _thumb_panel->clear (); - _slider->SetRange (0, 1); - } - - _slider->SetValue (0); - set_thumbnail (0); - break; - case Film::CONTENT: - setup_visibility (); - break; - case Film::CROP: - case Film::FORMAT: - case Film::WITH_SUBTITLES: - case Film::SUBTITLE_OFFSET: - case Film::SUBTITLE_SCALE: - _thumb_panel->recompose (); - break; - default: - break; - } + check_play_state (); } void -FilmViewer::set_film (shared_ptr<Film> f) +FilmViewer::check_play_state () { - if (_film == f) { - return; + if (_play_button->GetValue()) { + _timer.Start (1000 / _film->frames_per_second()); + } else { + _timer.Stop (); } - - _film = f; - _thumb_panel->set_film (_film); - - if (!_film) { - return; - } - - _film->Changed.connect (bind (&FilmViewer::film_changed, this, _1)); - film_changed (Film::CROP); - film_changed (Film::THUMBS); - setup_visibility (); } void -FilmViewer::setup_visibility () +FilmViewer::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) { - if (!_film) { - return; + _raw = image; + if (_out_width && _out_height) { + _display = _raw->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, Scaler::from_id ("bicubic")); } - - ContentType const c = _film->content_type (); - _slider->Show (c == VIDEO); } diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 95bdf099d..8194d0206 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -23,11 +23,15 @@ #include <wx/wx.h> #include "lib/film.h" +#include "lib/decoder_factory.h" -class ThumbPanel; +class wxToggleButton; +class FFmpegPlayer; +class Image; +class Subtitle; /** @class FilmViewer - * @brief A wx widget to view `thumbnails' of a Film. + * @brief A wx widget to view a preview of a Film. */ class FilmViewer : public wxPanel { @@ -35,15 +39,34 @@ public: FilmViewer (boost::shared_ptr<Film>, wxWindow *); void set_film (boost::shared_ptr<Film>); - void setup_visibility (); private: - void slider_changed (wxCommandEvent &); - void set_thumbnail (int); void film_changed (Film::Property); + void paint_panel (wxPaintEvent &); + void panel_sized (wxSizeEvent &); + void slider_moved (wxCommandEvent &); + void play_clicked (wxCommandEvent &); + void timer (wxTimerEvent &); + void process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); + void calculate_sizes (); + void check_play_state (); + void update_from_raw (); + void decoder_changed (); + void seek_and_update (SourceFrame); boost::shared_ptr<Film> _film; - wxBoxSizer* _sizer; - ThumbPanel* _thumb_panel; + + wxPanel* _panel; wxSlider* _slider; + wxToggleButton* _play_button; + wxTimer _timer; + + Decoders _decoders; + boost::shared_ptr<Image> _raw; + boost::shared_ptr<Image> _display; + + int _out_width; + int _out_height; + int _panel_width; + int _panel_height; }; diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index 186e9c86b..b9a462801 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -155,7 +155,10 @@ checked_set (wxComboBox* widget, int value) void checked_set (wxComboBox* widget, string value) { - wxClientData* o = widget->GetClientObject (widget->GetSelection ()); + wxClientData* o = 0; + if (widget->GetSelection() != -1) { + o = widget->GetClientObject (widget->GetSelection ()); + } if (!o || string_client_data(o) != value) { for (unsigned int i = 0; i < widget->GetCount(); ++i) { diff --git a/test/test.cc b/test/test.cc index e2f9f41ee..2bbf4f08a 100644 --- a/test/test.cc +++ b/test/test.cc @@ -307,10 +307,6 @@ BOOST_AUTO_TEST_CASE (paths_test) { shared_ptr<Film> f = new_test_film ("paths_test"); f->set_directory ("build/test/a/b/c/d/e"); - vector<SourceFrame> thumbs; - thumbs.push_back (42); - f->set_thumbs (thumbs); - BOOST_CHECK_EQUAL (f->thumb_file (0), "build/test/a/b/c/d/e/thumbs/00000042.png"); f->_content = "/foo/bar/baz"; BOOST_CHECK_EQUAL (f->content_path(), "/foo/bar/baz"); |
