diff options
Diffstat (limited to 'src/lib')
53 files changed, 782 insertions, 684 deletions
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..701584c74 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,8 +62,8 @@ 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 hash_file = j2k_file + ".md5"; + 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)); @@ -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..8b70b0aa4 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -154,10 +154,10 @@ shared_ptr<EncodedData> DCPVideoFrame::encode_locally () { if (!_post_process.empty ()) { - _input = _input->post_process (_post_process); + _input = _input->post_process (_post_process, true); } - shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler); + shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true); if (_subtitle) { Rect tx = subtitle_transformed_area ( @@ -166,7 +166,7 @@ DCPVideoFrame::encode_locally () _subtitle->area(), _subtitle_offset, _subtitle_scale ); - shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler); + shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler, true); prepared->alpha_blend (im, tx.position()); } @@ -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); @@ -395,7 +395,7 @@ EncodedData::write (shared_ptr<const Options> opt, SourceFrame frame) boost::filesystem::rename (tmp_j2k, real_j2k); /* Write a file containing the hash */ - string const hash = real_j2k + ".md5"; + string const hash = opt->hash_out_path (frame, false); ofstream h (hash.c_str()); h << md5_digest (_data, _size) << "\n"; h.close (); 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..61e63460b 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -49,10 +49,29 @@ 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) { - + _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1)); +} + +/** Seek. + * @param p Position as a source timestamp in seconds. + * @return true on error. + */ +bool +Decoder::seek (double p) +{ + throw DecodeError ("decoder does not support seek"); +} + +/** Seek so that the next frame we will produce is the same as the last one. + * @return true on error. + */ +bool +Decoder::seek_to_last () +{ + throw DecodeError ("decoder does not support seek"); } diff --git a/src/lib/decoder.h b/src/lib/decoder.h index e757e5401..3908afa2f 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -33,15 +33,15 @@ #include "stream.h" #include "video_source.h" #include "audio_source.h" +#include "film.h" class Job; -class Options; +class DecodeOptions; class Image; class Log; class DelayLine; class TimedSubtitle; class Subtitle; -class Film; class FilterGraph; /** @class Decoder. @@ -54,18 +54,27 @@ 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; + virtual bool seek (double); + virtual bool seek_to_last (); + + 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; + +private: + virtual void film_changed (Film::Property) {} + + boost::signals2::scoped_connection _film_connection; }; #endif 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 8db74801f..a783cde33 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,84 +59,50 @@ ExamineContentJob::name () const void ExamineContentJob::run () { - /* Decode the content to get an accurate length */ - - /* We don't want to use any existing length here, as progress - will be messed up. - */ - _film->unset_length (); - - shared_ptr<Options> o (new Options ("", "", "")); - o->out_size = Size (512, 512); - o->apply_crop = false; - o->decode_audio = false; - descend (0.5); - - pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoders = decoder_factory (_film, o, this); - - set_progress_unknown (); - while (!decoders.first->pass()) { - /* keep going */ - } - - _film->set_length (decoders.first->video_frame()); - - _film->log()->log (String::compose ("Video length is %1 frames", _film->length())); - + _film->set_content_digest (md5_digest (_film->content_path ())); ascend (); - /* Now make thumbnails for it */ - descend (0.5); - try { - o.reset (new Options (_film->dir ("thumbs"), ".png", "")); - o->out_size = _film->size (); - o->apply_crop = false; + /* 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 */ + + /* We don't want to use any existing length here, as progress + will be messed up. + */ + _film->unset_length (); + _film->set_crop (Crop ()); + + shared_ptr<DecodeOptions> o (new DecodeOptions); o->decode_audio = false; - if (_film->length() > 0) { - o->decode_video_skip = _film->length().get() / 128; - } else { - o->decode_video_skip = 0; + + Decoders decoders = decoder_factory (_film, o, this); + + set_progress_unknown (); + while (!decoders.video->pass()) { + /* keep going */ } - o->decode_subtitles = true; - shared_ptr<ImageMagickEncoder> e (new ImageMagickEncoder (_film, o)); - Transcoder w (_film, o, this, e); - w.go (); - } catch (std::exception& e) { - - ascend (); - set_progress (1); - set_error (e.what ()); - set_state (FINISHED_ERROR); - return; + _film->set_length (decoders.video->video_frame()); - } - - 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) { + _film->log()->log (String::compose ("Video length examined as %1 frames", _film->length().get())); + + } else { - /* 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 + /* Get a quick decoder to get the content's length from its header */ - 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())); - } + 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())); } - 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 b96e2b67c..60bb3271e 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,10 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, J setup_video (); setup_audio (); setup_subtitle (); + + if (!o->video_sync) { + _first_video = 0; + } } FFmpegDecoder::~FFmpegDecoder () @@ -162,14 +166,7 @@ FFmpegDecoder::setup_video () throw DecodeError ("could not find video decoder"); } - /* I think this prevents problems with green hash on decodes and - "changing frame properties on the fly is not supported by all filters" - messages with some content. Although I'm not sure; needs checking. - */ - AVDictionary* opts = 0; - av_dict_set (&opts, "threads", "1", 0); - - if (avcodec_open2 (_video_codec_context, _video_codec, &opts) < 0) { + if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) { throw DecodeError ("could not open video decoder"); } } @@ -259,7 +256,7 @@ FFmpegDecoder::pass () avcodec_get_frame_defaults (_frame); shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); - + if (_packet.stream_index == _video_stream) { int frame_finished; @@ -270,46 +267,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); } } @@ -373,7 +334,7 @@ FFmpegDecoder::pass () if (sub.num_rects > 0) { shared_ptr<TimedSubtitle> ts; try { - emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub, _first_video.get()))); + emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub))); } catch (...) { /* some problem with the subtitle; we probably didn't understand it */ } @@ -586,11 +547,14 @@ FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) { VideoDecoder::set_subtitle_stream (s); setup_subtitle (); + OutputChanged (); } 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(); @@ -599,7 +563,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 { @@ -608,11 +572,44 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame) list<shared_ptr<Image> > images = graph->process (frame); + double const st = av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base); + for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { - emit_video (*i); + emit_video (*i, st); } } +bool +FFmpegDecoder::seek (double p) +{ + return do_seek (p, false); +} + +bool +FFmpegDecoder::seek_to_last () +{ + /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time + (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than + staying in the same place. + */ + return do_seek (last_source_time(), true); +} + +bool +FFmpegDecoder::do_seek (double p, bool backwards) +{ + int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base); + + int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0); + + avcodec_flush_buffers (_video_codec_context); + if (_subtitle_codec_context) { + avcodec_flush_buffers (_subtitle_codec_context); + } + + return r < 0; +} + shared_ptr<FFmpegAudioStream> FFmpegAudioStream::create (string t, optional<int> v) { @@ -665,3 +662,74 @@ 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: + case Film::FILTERS: + { + 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 87eebe1ec..89534a38c 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; @@ -56,7 +58,7 @@ public: , _name (n) , _id (i) {} - + std::string to_string () const; std::string name () const { @@ -84,11 +86,12 @@ 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; Size native_size () const; + SourceFrame length () const; int time_base_numerator () const; int time_base_denominator () const; int sample_aspect_ratio_numerator () const; @@ -97,13 +100,18 @@ public: void set_audio_stream (boost::shared_ptr<AudioStream>); void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); + bool seek (double); + bool seek_to_last (); + private: bool pass (); + bool do_seek (double p, bool); PixelFormat pixel_format () const; AVSampleFormat audio_sample_format () const; int bytes_per_audio_sample () const; + void out_with_sync (); void filter_and_emit_video (AVFrame *); void setup_general (); @@ -114,6 +122,8 @@ private: void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size); + void film_changed (Film::Property); + std::string stream_name (AVStream* s) const; AVFormatContext* _format_context; @@ -134,4 +144,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 3f9210080..58d1e5010 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" @@ -86,6 +85,7 @@ int const Film::state_version = 1; Film::Film (string d, bool must_exist) : _use_dci_name (true) + , _trust_content_header (true) , _dcp_content_type (0) , _format (0) , _scaler (Scaler::from_id ("bicubic")) @@ -144,6 +144,7 @@ Film::Film (Film const & o) , _name (o._name) , _use_dci_name (o._use_dci_name) , _content (o._content) + , _trust_content_header (o._trust_content_header) , _dcp_content_type (o._dcp_content_type) , _format (o._format) , _crop (o._crop) @@ -169,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) @@ -260,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 */ @@ -299,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); @@ -355,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 @@ -404,6 +371,7 @@ Film::write_metadata () const f << "name " << _name << "\n"; f << "use_dci_name " << _use_dci_name << "\n"; f << "content " << _content << "\n"; + f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n"; if (_dcp_content_type) { f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n"; } @@ -445,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"; @@ -478,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 (); @@ -513,6 +474,8 @@ Film::read_metadata () _use_dci_name = (v == "1"); } else if (k == "content") { _content = v; + } else if (k == "trust_content_header") { + _trust_content_header = (v == "1"); } else if (k == "dcp_content_type") { _dcp_content_type = DCPContentType::from_pretty_name (v); } else if (k == "format") { @@ -580,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 ()); @@ -630,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 { @@ -776,6 +678,10 @@ Film::target_audio_sample_rate () const boost::optional<SourceFrame> Film::dcp_length () const { + if (content_type() == STILL) { + return _still_duration * frames_per_second(); + } + if (!length()) { return boost::optional<SourceFrame> (); } @@ -949,23 +855,23 @@ Film::set_content (string c) */ try { - shared_ptr<Options> o (new Options ("", "", "")); - o->out_size = Size (1024, 1024); + shared_ptr<DecodeOptions> o (new DecodeOptions); + Decoders d = decoder_factory (shared_from_this(), o, 0); - pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > 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 ()); + if (d.audio) { + 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 && !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()); } { @@ -975,8 +881,6 @@ Film::set_content (string c) signal_changed (CONTENT); - set_content_digest (md5_digest (content_path ())); - examine_content (); } catch (...) { @@ -986,6 +890,32 @@ Film::set_content (string c) throw; } + + /* Default format */ + switch (content_type()) { + case STILL: + set_format (Format::from_id ("var-185")); + break; + case VIDEO: + set_format (Format::from_id ("185")); + break; + } +} + +void +Film::set_trust_content_header (bool t) +{ + { + boost::mutex::scoped_lock lm (_state_mutex); + _trust_content_header = t; + } + + signal_changed (TRUST_CONTENT_HEADER); + + if (!_trust_content_header && !content().empty()) { + /* We just said that we don't trust the content's header */ + examine_content (); + } } void @@ -1143,8 +1073,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 (); @@ -1305,16 +1234,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 2e81575e4..536855b1f 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; @@ -110,6 +105,7 @@ public: NAME, USE_DCI_NAME, CONTENT, + TRUST_CONTENT_HEADER, DCP_CONTENT_TYPE, FORMAT, CROP, @@ -129,7 +125,6 @@ public: SUBTITLE_OFFSET, SUBTITLE_SCALE, DCI_METADATA, - THUMBS, SIZE, LENGTH, CONTENT_AUDIO_STREAMS, @@ -160,6 +155,11 @@ public: return _content; } + bool trust_content_header () const { + boost::mutex::scoped_lock lm (_state_mutex); + return _trust_content_header; + } + DCPContentType const * dcp_content_type () const { boost::mutex::scoped_lock lm (_state_mutex); return _dcp_content_type; @@ -285,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; @@ -317,6 +312,10 @@ public: float frames_per_second () const { boost::mutex::scoped_lock lm (_state_mutex); + if (content_type() == STILL) { + return 24; + } + return _frames_per_second; } @@ -329,6 +328,7 @@ public: void set_name (std::string); void set_use_dci_name (bool); void set_content (std::string); + void set_trust_content_header (bool); void set_dcp_content_type (DCPContentType const *); void set_format (Format const *); void set_crop (Crop); @@ -358,7 +358,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 (); @@ -384,8 +383,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 (); @@ -404,6 +401,7 @@ private: * or an absolute path. */ std::string _content; + bool _trust_content_header; /** The type of content that this Film represents (feature, trailer etc.) */ DCPContentType const * _dcp_content_type; /** The format to present this Film in (flat, scope, etc.) */ @@ -458,8 +456,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..e136a8469 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -43,6 +43,12 @@ extern "C" { using namespace std; using namespace boost; +void +Image::swap (Image& other) +{ + std::swap (_pixel_format, other._pixel_format); +} + /** @param n Component index. * @return Number of lines in the image for the given component. */ @@ -89,11 +95,11 @@ Image::components () const } shared_ptr<Image> -Image::scale (Size out_size, Scaler const * scaler) const +Image::scale (Size out_size, Scaler const * scaler, bool aligned) const { assert (scaler); - shared_ptr<Image> scaled (new AlignedImage (pixel_format(), out_size)); + shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), @@ -118,14 +124,14 @@ Image::scale (Size out_size, Scaler const * scaler) const * @param scaler Scaler to use. */ shared_ptr<Image> -Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler) const +Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const { assert (scaler); 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 SimpleImage (PIX_FMT_RGB24, content_size, aligned)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), @@ -146,7 +152,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal scheme of things. */ if (padding > 0) { - shared_ptr<Image> padded_rgb (new AlignedImage (PIX_FMT_RGB24, out_size)); + shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, aligned)); padded_rgb->make_black (); /* XXX: we are cheating a bit here; we know the frame is RGB so we can @@ -173,9 +179,9 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal * @return Post-processed image. */ shared_ptr<Image> -Image::post_process (string pp) const +Image::post_process (string pp, bool aligned) const { - shared_ptr<Image> out (new AlignedImage (pixel_format(), size ())); + shared_ptr<Image> out (new SimpleImage (pixel_format(), size (), aligned)); int pp_format = 0; switch (pixel_format()) { @@ -206,6 +212,33 @@ Image::post_process (string pp) const return out; } +shared_ptr<Image> +Image::crop (Crop crop, bool aligned) const +{ + Size cropped_size = size (); + cropped_size.width -= crop.left + crop.right; + cropped_size.height -= crop.top + crop.bottom; + + shared_ptr<Image> out (new SimpleImage (pixel_format(), cropped_size, aligned)); + + for (int c = 0; c < components(); ++c) { + int const crop_left_in_bytes = bytes_per_pixel(c) * crop.left; + int const cropped_width_in_bytes = bytes_per_pixel(c) * cropped_size.width; + + /* Start of the source line, cropped from the top but not the left */ + uint8_t* in_p = data()[c] + crop.top * stride()[c]; + uint8_t* out_p = out->data()[c]; + + 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]; + } + } + + return out; +} + void Image::make_black () { @@ -228,7 +261,7 @@ Image::make_black () } void -Image::alpha_blend (shared_ptr<Image> other, Position position) +Image::alpha_blend (shared_ptr<const Image> other, Position position) { /* Only implemented for RGBA onto RGB24 so far */ assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA); @@ -287,15 +320,64 @@ Image::write_to_socket (shared_ptr<Socket> socket) const } } + +float +Image::bytes_per_pixel (int c) const +{ + if (c == 3) { + return 0; + } + + switch (_pixel_format) { + case PIX_FMT_RGB24: + if (c == 0) { + return 3; + } else { + return 0; + } + case PIX_FMT_RGBA: + if (c == 0) { + return 4; + } else { + return 0; + } + case PIX_FMT_YUV420P: + case PIX_FMT_YUV422P: + if (c == 0) { + return 1; + } else { + return 0.5; + } + case PIX_FMT_YUV422P10LE: + if (c == 1) { + return 2; + } else { + return 1; + } + default: + assert (false); + } + + return 0; +} + + /** Construct a SimpleImage of a given size and format, allocating memory * as required. * * @param p Pixel format. * @param s Size in pixels. */ -SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int, int const *)> stride_computer) +SimpleImage::SimpleImage (AVPixelFormat p, Size s, bool aligned) : Image (p) , _size (s) + , _aligned (aligned) +{ + allocate (); +} + +void +SimpleImage::allocate () { _data = (uint8_t **) av_malloc (4 * sizeof (uint8_t *)); _data[0] = _data[1] = _data[2] = _data[3] = 0; @@ -306,78 +388,68 @@ SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int, int const _stride = (int *) av_malloc (4 * sizeof (int)); _stride[0] = _stride[1] = _stride[2] = _stride[3] = 0; - switch (p) { - case PIX_FMT_RGB24: - _line_size[0] = s.width * 3; - break; - case PIX_FMT_RGBA: - _line_size[0] = s.width * 4; - break; - case PIX_FMT_YUV420P: - case PIX_FMT_YUV422P: - _line_size[0] = s.width; - _line_size[1] = s.width / 2; - _line_size[2] = s.width / 2; - break; - case PIX_FMT_YUV422P10LE: - _line_size[0] = s.width * 2; - _line_size[1] = s.width; - _line_size[2] = s.width; - break; - default: - assert (false); - } - for (int i = 0; i < components(); ++i) { - _stride[i] = stride_computer (i, _line_size); + _line_size[i] = _size.width * bytes_per_pixel(i); + _stride[i] = stride_round_up (i, _line_size, _aligned ? 32 : 1); _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i)); } } -/** Destroy a SimpleImage */ -SimpleImage::~SimpleImage () +SimpleImage::SimpleImage (SimpleImage const & other) + : Image (other) { + _size = other._size; + _aligned = other._aligned; + + allocate (); + for (int i = 0; i < components(); ++i) { - av_free (_data[i]); + memcpy (_data[i], other._data[i], _line_size[i] * lines(i)); } - - av_free (_data); - av_free (_line_size); - av_free (_stride); } -uint8_t ** -SimpleImage::data () const +SimpleImage& +SimpleImage::operator= (SimpleImage const & other) { - return _data; -} + if (this == &other) { + return *this; + } -int * -SimpleImage::line_size () const -{ - return _line_size; + SimpleImage tmp (other); + swap (tmp); + return *this; } -int * -SimpleImage::stride () const +void +SimpleImage::swap (SimpleImage & other) { - return _stride; -} + Image::swap (other); + + std::swap (_size, other._size); -Size -SimpleImage::size () const -{ - return _size; + for (int i = 0; i < 4; ++i) { + std::swap (_data[i], other._data[i]); + std::swap (_line_size[i], other._line_size[i]); + std::swap (_stride[i], other._stride[i]); + } + + std::swap (_aligned, other._aligned); } -AlignedImage::AlignedImage (AVPixelFormat f, Size s) - : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 32)) +/** Destroy a SimpleImage */ +SimpleImage::~SimpleImage () { + for (int i = 0; i < components(); ++i) { + av_free (_data[i]); + } + av_free (_data); + av_free (_line_size); + av_free (_stride); } -AlignedImage::AlignedImage (shared_ptr<Image> im) - : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 32)) +SimpleImage::SimpleImage (shared_ptr<const Image> im, bool aligned) + : Image (im->pixel_format()) { assert (components() == im->components()); @@ -396,30 +468,28 @@ AlignedImage::AlignedImage (shared_ptr<Image> im) } } -CompactImage::CompactImage (AVPixelFormat f, Size s) - : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 1)) +uint8_t ** +SimpleImage::data () const { - + return _data; } -CompactImage::CompactImage (shared_ptr<Image> im) - : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 1)) +int * +SimpleImage::line_size () const { - assert (components() == im->components()); - - for (int c = 0; c < components(); ++c) { + return _line_size; +} - assert (line_size()[c] == im->line_size()[c]); +int * +SimpleImage::stride () const +{ + return _stride; +} - uint8_t* t = data()[c]; - uint8_t* o = im->data()[c]; - - for (int y = 0; y < lines(c); ++y) { - memcpy (t, o, line_size()[c]); - t += stride()[c]; - o += im->stride()[c]; - } - } +Size +SimpleImage::size () const +{ + return _size; } FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b) @@ -459,3 +529,31 @@ FilterBufferImage::size () const return Size (_buffer->video->w, _buffer->video->h); } +RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im) + : SimpleImage (im->pixel_format(), im->size(), false) +{ + assert (im->pixel_format() == PIX_FMT_RGBA); + + _alpha = (uint8_t *) av_malloc (im->size().width * im->size().height); + + uint8_t* in = im->data()[0]; + uint8_t* out = data()[0]; + uint8_t* out_alpha = _alpha; + for (int y = 0; y < im->size().height; ++y) { + uint8_t* in_r = in; + for (int x = 0; x < im->size().width; ++x) { + *out++ = *in_r++; + *out++ = *in_r++; + *out++ = *in_r++; + *out_alpha++ = *in_r++; + } + + in += im->stride()[0]; + } +} + +RGBPlusAlphaImage::~RGBPlusAlphaImage () +{ + av_free (_alpha); +} + diff --git a/src/lib/image.h b/src/lib/image.h index 7c118f338..13b92d72f 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -69,10 +69,12 @@ public: int components () const; int lines (int) const; - boost::shared_ptr<Image> scale_and_convert_to_rgb (Size, int, Scaler const *) const; - boost::shared_ptr<Image> scale (Size, Scaler const *) const; - boost::shared_ptr<Image> post_process (std::string) const; - void alpha_blend (boost::shared_ptr<Image> image, Position pos); + + boost::shared_ptr<Image> scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const; + boost::shared_ptr<Image> scale (Size, Scaler const *, bool aligned) const; + boost::shared_ptr<Image> post_process (std::string, bool aligned) const; + void alpha_blend (boost::shared_ptr<const Image> image, Position pos); + boost::shared_ptr<Image> crop (Crop c, bool aligned) const; void make_black (); @@ -83,7 +85,11 @@ public: return _pixel_format; } -private: +protected: + virtual void swap (Image &); + float bytes_per_pixel (int) const; + +private: AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image }; @@ -102,6 +108,10 @@ public: Size size () const; private: + /* Not allowed */ + FilterBufferImage (FilterBufferImage const &); + FilterBufferImage& operator= (FilterBufferImage const &); + AVFilterBufferRef* _buffer; }; @@ -111,40 +121,41 @@ private: class SimpleImage : public Image { public: - SimpleImage (AVPixelFormat, Size, boost::function<int (int, int const *)> rounder); + SimpleImage (AVPixelFormat, Size, bool); + SimpleImage (SimpleImage const &); + SimpleImage& operator= (SimpleImage const &); + SimpleImage (boost::shared_ptr<const Image>, bool aligned); ~SimpleImage (); uint8_t ** data () const; int * line_size () const; int * stride () const; Size size () const; + +protected: + void allocate (); + void swap (SimpleImage &); private: - Size _size; ///< size in pixels uint8_t** _data; ///< array of pointers to components int* _line_size; ///< array of sizes of the data in each line, in pixels (without any alignment padding bytes) int* _stride; ///< array of strides for each line (including any alignment padding bytes) + bool _aligned; }; -/** @class AlignedImage - * @brief An image whose pixel data is padded so that rows always start on 32-byte boundaries. - */ -class AlignedImage : public SimpleImage +class RGBPlusAlphaImage : public SimpleImage { public: - AlignedImage (AVPixelFormat, Size); - AlignedImage (boost::shared_ptr<Image>); -}; + RGBPlusAlphaImage (boost::shared_ptr<const Image>); + ~RGBPlusAlphaImage (); -/** @class CompactImage - * @brief An image whose pixel data is not padded, so rows may start at any pixel alignment. - */ -class CompactImage : public SimpleImage -{ -public: - CompactImage (AVPixelFormat, Size); - CompactImage (boost::shared_ptr<Image>); + uint8_t* alpha () const { + return _alpha; + } + +private: + uint8_t* _alpha; }; #endif diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index d68c1648f..131eaa500 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) { @@ -73,13 +73,13 @@ ImageMagickDecoder::pass () return true; } - using namespace MagickCore; - Magick::Image* magick_image = new Magick::Image (_film->content_path ()); Size size = native_size (); - shared_ptr<CompactImage> image (new CompactImage (PIX_FMT_RGB24, size)); + shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false)); + using namespace MagickCore; + uint8_t* p = image->data()[0]; for (int y = 0; y < size.height; ++y) { for (int x = 0; x < size.width; ++x) { @@ -91,8 +91,10 @@ ImageMagickDecoder::pass () } delete magick_image; + + image = image->crop (_film->crop(), false); - emit_video (image); + emit_video (image, 0); ++_iter; return false; @@ -105,3 +107,38 @@ ImageMagickDecoder::pixel_format () const return PIX_FMT_RGB24; } +bool +ImageMagickDecoder::seek_to_last () +{ + if (_iter == _files.end()) { + _iter = _files.begin(); + } else { + --_iter; + } + + return false; +} + +bool +ImageMagickDecoder::seek (double t) +{ + int const f = t * frames_per_second(); + + _iter = _files.begin (); + for (int i = 0; i < f; ++i) { + if (_iter == _files.end()) { + return true; + } + ++_iter; + } + + return false; +} + +void +ImageMagickDecoder::film_changed (Film::Property p) +{ + if (p == Film::CROP) { + OutputChanged (); + } +} diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index f636191f2..6f426f308 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 */ @@ -35,6 +35,11 @@ public: Size native_size () const; + SourceFrame length () const { + /* We don't know */ + return 0; + } + int audio_channels () const { return 0; } @@ -51,6 +56,9 @@ public: return false; } + bool seek (double); + bool seek_to_last (); + protected: bool pass (); PixelFormat pixel_format () const; @@ -74,6 +82,8 @@ protected: } private: + void film_changed (Film::Property); + std::list<std::string> _files; std::list<std::string>::iterator _iter; }; 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..968257691 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) { @@ -64,19 +64,32 @@ J2KStillEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> s } string const real = _opt->frame_out_path (0, false); - for (int i = 1; i < (_film->still_duration() * 24); ++i) { + string const real_hash = _opt->hash_out_path (0, false); + for (int i = 1; i < (_film->still_duration() * _film->frames_per_second()); ++i) { + if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) { - string const link = _opt->frame_out_path (i, false); + link (real, _opt->frame_out_path (i, false)); + } + + if (!boost::filesystem::exists (_opt->hash_out_path (i, false))) { + link (real_hash, _opt->hash_out_path (i, false)); + } + + frame_done (); + } +} + +void +J2KStillEncoder::link (string a, string b) const +{ #ifdef DVDOMATIC_POSIX - int const r = symlink (real.c_str(), link.c_str()); - if (r) { - throw EncodeError ("could not create symlink"); - } + int const r = symlink (a.c_str(), b.c_str()); + if (r) { + throw EncodeError ("could not create symlink"); + } #endif + #ifdef DVDOMATIC_WINDOWS - boost::filesystem::copy_file (real, link); + boost::filesystem::copy_file (a, b); #endif - } - frame_done (); - } } diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h index 4ffe876af..7c302474c 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,9 +35,11 @@ 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>); void do_process_audio (boost::shared_ptr<AudioBuffers>) {} + + void link (std::string, std::string) const; }; 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/matcher.cc b/src/lib/matcher.cc index 7b4434539..2dd36c11e 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 CompactImage (_pixel_format.get(), _size.get())); + shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), false)); black->make_black (); for (int i = 0; i < black_video_frames; ++i) { Video (black, shared_ptr<Subtitle>()); diff --git a/src/lib/options.h b/src/lib/options.h index 29b3b71cd..10cdfa8cd 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) @@ -57,15 +54,11 @@ public: * @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, std::string e = "") const { - if (e.empty ()) { - e = _frame_out_extension; - } - + 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 << e; + s << std::setfill('0') << f << _frame_out_extension; if (t) { s << ".tmp"; @@ -74,6 +67,10 @@ public: 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; @@ -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/server.cc b/src/lib/server.cc index 38f9834ff..bea75cff8 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -113,13 +113,13 @@ Server::process (shared_ptr<Socket> socket) PixelFormat pixel_format = (PixelFormat) pixel_format_int; Scaler const * scaler = Scaler::from_id (scaler_id); - shared_ptr<Image> image (new AlignedImage (pixel_format, in_size)); + shared_ptr<Image> image (new SimpleImage (pixel_format, in_size, true)); image->read_from_socket (socket); shared_ptr<Subtitle> sub; if (subtitle_size.width && subtitle_size.height) { - shared_ptr<Image> subtitle_image (new AlignedImage (PIX_FMT_RGBA, subtitle_size)); + shared_ptr<Image> subtitle_image (new SimpleImage (PIX_FMT_RGBA, subtitle_size, true)); subtitle_image->read_from_socket (socket); sub.reset (new Subtitle (subtitle_position, subtitle_image)); } diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc index 8e1bfac22..c52d3ac66 100644 --- a/src/lib/subtitle.cc +++ b/src/lib/subtitle.cc @@ -31,14 +31,15 @@ using namespace boost; /** Construct a TimedSubtitle. This is a subtitle image, position, * and a range of time over which it should be shown. * @param sub AVSubtitle to read. - * @param c Fractional seconds that should be subtracted from the AVSubtitle's PTS. */ -TimedSubtitle::TimedSubtitle (AVSubtitle const & sub, double c) +TimedSubtitle::TimedSubtitle (AVSubtitle const & sub) { assert (sub.rects > 0); - /* subtitle PTS in seconds */ - double const packet_time = ((sub.pts / AV_TIME_BASE) + float (sub.pts % AV_TIME_BASE) / 1e6) - c; + /* Subtitle PTS in seconds (within the source, not taking into account any of the + source that we may have chopped off for the DCP) + */ + double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE; /* hence start time for this sub */ _from = packet_time + (double (sub.start_display_time) / 1e3); @@ -54,7 +55,7 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub, double c) throw DecodeError ("non-bitmap subtitles not yet supported"); } - shared_ptr<Image> image (new AlignedImage (PIX_FMT_RGBA, Size (rect->w, rect->h))); + shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGBA, Size (rect->w, rect->h), true)); /* Start of the first line in the subtitle */ uint8_t* sub_p = rect->pict.data[0]; @@ -76,7 +77,7 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub, double c) _subtitle.reset (new Subtitle (Position (rect->x, rect->y), image)); } -/** @param t Time in seconds from the start of the film */ +/** @param t Time in seconds from the start of the source */ bool TimedSubtitle::displayed_at (double t) const { diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h index 590e0dd31..38ba4e70e 100644 --- a/src/lib/subtitle.h +++ b/src/lib/subtitle.h @@ -63,7 +63,7 @@ subtitle_transformed_area ( class TimedSubtitle { public: - TimedSubtitle (AVSubtitle const &, double c); + TimedSubtitle (AVSubtitle const &); bool displayed_at (double t) const; diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 081e04252..477c73c75 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); @@ -116,7 +117,11 @@ TranscodeJob::remaining_time () const return 0; } - /* We assume that dcp_length() is valid */ + if (!_film->dcp_length()) { + return 0; + } + + /* 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(); return left / fps; } 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 537b9b664..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,18 +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 ()); - _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->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); @@ -93,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] && 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 e3ca2bb32..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,17 +48,26 @@ 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.video; + } + protected: /** A Job that is running this Transcoder, or 0 */ Job* _job; /** 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/util.cc b/src/lib/util.cc index b69581eba..66eaea39e 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -576,7 +576,8 @@ Rect::intersection (Rect const & other) const } /** Round a number up to the nearest multiple of another number. - * @param a Number to round. + * @param c Index. + * @param s Array of numbers to round, indexed by c. * @param t Multiple to round to. * @return Rounded number. */ diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 23a69f958..e723610b3 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -28,33 +28,36 @@ 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_time (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 image frame to emit. + * @param t Time of the frame within the source, in seconds. */ void -VideoDecoder::emit_video (shared_ptr<Image> image) +VideoDecoder::emit_video (shared_ptr<Image> image, double t) { shared_ptr<Subtitle> sub; - if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) { + if (_timed_subtitle && _timed_subtitle->displayed_at (t)) { sub = _timed_subtitle->subtitle (); } signal_video (image, sub); + _last_source_time = t; } void VideoDecoder::repeat_last_video () { if (!_last_image) { - _last_image.reset (new CompactImage (pixel_format(), native_size())); + _last_image.reset (new SimpleImage (pixel_format(), native_size(), false)); _last_image->make_black (); } @@ -77,7 +80,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)); } @@ -92,7 +95,7 @@ VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) void VideoDecoder::set_progress () const { - if (_job && _film->dcp_length()) { + if (_job && _film->length()) { _job->set_progress (float (_video_frame) / _film->length().get()); } } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index ea1899840..97bbb0e48 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -27,12 +27,14 @@ 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; /** @return native size in pixels */ virtual Size native_size () const = 0; + /** @return length (in source video frames), according to our content's header */ + virtual SourceFrame length () const = 0; virtual int time_base_numerator () const = 0; virtual int time_base_denominator () const = 0; @@ -43,7 +45,7 @@ public: void set_progress () const; - SourceFrame video_frame () const { + int video_frame () const { return _video_frame; } @@ -55,11 +57,15 @@ public: return _subtitle_streams; } + double last_source_time () const { + return _last_source_time; + } + protected: virtual PixelFormat pixel_format () const = 0; - void emit_video (boost::shared_ptr<Image>); + void emit_video (boost::shared_ptr<Image>, double); void emit_subtitle (boost::shared_ptr<TimedSubtitle>); void repeat_last_video (); @@ -71,8 +77,9 @@ protected: private: void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); - SourceFrame _video_frame; - + int _video_frame; + double _last_source_time; + 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 |
