diff options
63 files changed, 1175 insertions, 920 deletions
@@ -1,3 +1,12 @@ +2012-12-18 Carl Hetherington <cth@carlh.net> + + * Alter film viewer so that it is much quicker, responds instantly + to changes in video filtering settings, and can (roughly) play the + source material back. + + * Make the examination of content for length optional, so that + if a source file has an accurate header you can trust it. + 2012-12-13 Carl Hetherington <cth@carlh.net> * Version 0.64 released. @@ -42,6 +51,15 @@ 2012-12-10 Carl Hetherington <cth@carlh.net> + * Add a check-box (which defaults to on) which tells DVD-o-matic + not to scan new content files to work out their length, but instead + to trust the length from the header. This length only matters for + working out what thumbnails to generate, so it isn't critical. + Trusting the header will speed up the "Examine Content" job by + a factor of about 2, which is handy for large films. + +2012-12-10 Carl Hetherington <cth@carlh.net> + * Version 0.59 released. 2012-12-09 Carl Hetherington <cth@carlh.net> 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..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..7d4085045 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) { - + _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1)); +} + +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..b8278ff80 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,29 @@ 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; + +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 911714d7b..0e4446a86 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); } } @@ -557,11 +518,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(); @@ -570,7 +534,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 { @@ -579,11 +543,33 @@ 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 vt = static_cast<int64_t>(f) / (av_q2d (_format_context->streams[_video_stream]->time_base) * frames_per_second()); + + /* This AVSEEK_FLAG_BACKWARD 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. + */ + int const r = av_seek_frame (_format_context, _video_stream, vt, (f == last_source_frame() ? 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) { @@ -636,3 +622,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..2011ef72f 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,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 (); @@ -104,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 (); @@ -114,6 +120,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 +142,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..5713e68f9 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,24 @@ ImageMagickDecoder::pixel_format () const return PIX_FMT_RGB24; } +bool +ImageMagickDecoder::seek (SourceFrame f) +{ + _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..cf417d373 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,8 @@ public: return false; } + bool seek (SourceFrame); + protected: bool pass (); PixelFormat pixel_format () const; @@ -74,6 +81,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..39f8faa68 100644 --- a/src/lib/subtitle.cc +++ b/src/lib/subtitle.cc @@ -54,7 +54,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]; 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..4c05d5fcd 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -28,33 +28,35 @@ 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())) { + if (_timed_subtitle && _timed_subtitle->displayed_at (f / _film->frames_per_second())) { sub = _timed_subtitle->subtitle (); } signal_video (image, sub); + _last_source_frame = f; } 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 +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)); } @@ -92,7 +94,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..f682941d1 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; @@ -55,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 (); @@ -72,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 2528f49db..da48c2645 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -114,6 +114,11 @@ FilmEditor::make_film_panel () _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*")); _film_sizer->Add (_content, 1, wxEXPAND); + _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header")); + video_control (_trust_content_header); + _film_sizer->Add (_trust_content_header, 1); + _film_sizer->AddSpacer (0); + add_label_to_sizer (_film_sizer, _film_panel, "Content Type"); _dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); _film_sizer->Add (_dcp_content_type); @@ -134,12 +139,12 @@ FilmEditor::make_film_panel () { video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames")); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - add_label_to_sizer (s, _film_panel, "Start"); + video_control (add_label_to_sizer (s, _film_panel, "Start")); _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_dcp_trim_start); - add_label_to_sizer (s, _film_panel, "End"); + s->Add (video_control (_dcp_trim_start)); + video_control (add_label_to_sizer (s, _film_panel, "End")); _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_dcp_trim_end); + s->Add (video_control (_dcp_trim_end)); _film_sizer->Add (s); } @@ -174,6 +179,7 @@ FilmEditor::connect_to_widgets () _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this); _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this); _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this); + _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this); _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this); _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this); _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this); @@ -263,7 +269,7 @@ FilmEditor::make_video_panel () _top_crop->SetRange (0, 1024); _right_crop->SetRange (0, 1024); _bottom_crop->SetRange (0, 1024); - _still_duration->SetRange (0, 60 * 60); + _still_duration->SetRange (1, 60 * 60); _dcp_trim_start->SetRange (0, 100); _dcp_trim_end->SetRange (0, 100); } @@ -325,7 +331,7 @@ FilmEditor::make_audio_panel () }; for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]); + video_control (add_label_to_sizer (_audio_sizer, _audio_panel, channels[i])); _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav")); _audio_sizer->Add (video_control (_external_audio[i]), 1, wxEXPAND); } @@ -348,7 +354,7 @@ FilmEditor::make_subtitle_panel () _subtitle_sizer->Add (_with_subtitles, 1); _subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); - _subtitle_sizer->Add (_subtitle_stream); + _subtitle_sizer->Add (video_control (_subtitle_stream)); video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset")); _subtitle_offset = new wxSpinCtrl (_subtitle_panel); @@ -427,6 +433,16 @@ FilmEditor::content_changed (wxCommandEvent &) } } +void +FilmEditor::trust_content_header_changed (wxCommandEvent &) +{ + if (!_film) { + return; + } + + _film->set_trust_content_header (_trust_content_header->GetValue ()); +} + /** Called when the DCP A/B switch has been toggled */ void FilmEditor::dcp_ab_toggled (wxCommandEvent &) @@ -495,6 +511,9 @@ FilmEditor::film_changed (Film::Property p) setup_subtitle_control_sensitivity (); setup_streams (); break; + case Film::TRUST_CONTENT_HEADER: + checked_set (_trust_content_header, _film->trust_content_header ()); + break; case Film::SUBTITLE_STREAMS: setup_subtitle_control_sensitivity (); setup_streams (); @@ -569,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; @@ -694,6 +711,7 @@ FilmEditor::set_film (shared_ptr<Film> f) film_changed (Film::NAME); film_changed (Film::USE_DCI_NAME); film_changed (Film::CONTENT); + film_changed (Film::TRUST_CONTENT_HEADER); film_changed (Film::DCP_CONTENT_TYPE); film_changed (Film::FORMAT); film_changed (Film::CROP); @@ -731,6 +749,7 @@ FilmEditor::set_things_sensitive (bool s) _edit_dci_button->Enable (s); _format->Enable (s); _content->Enable (s); + _trust_content_header->Enable (s); _left_crop->Enable (s); _right_crop->Enable (s); _top_crop->Enable (s); diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index 428b994b8..7e75b4bf0 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -61,6 +61,7 @@ private: void top_crop_changed (wxCommandEvent &); void bottom_crop_changed (wxCommandEvent &); void content_changed (wxCommandEvent &); + void trust_content_header_changed (wxCommandEvent &); void format_changed (wxCommandEvent &); void dcp_trim_start_changed (wxCommandEvent &); void dcp_trim_end_changed (wxCommandEvent &); @@ -118,6 +119,7 @@ private: wxComboBox* _format; /** The Film's content file */ wxFilePickerCtrl* _content; + wxCheckBox* _trust_content_header; /** The Film's left crop */ wxSpinCtrl* _left_crop; /** The Film's right crop */ diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index a82132358..891d1671b 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -18,307 +18,343 @@ */ /** @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 "lib/exceptions.h" +#include "lib/examine_content_job.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 std::list; 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)))); + _panel->SetDoubleBuffered (true); + _panel->SetBackgroundStyle (wxBG_STYLE_PAINT); + + wxBoxSizer* v_sizer = new wxBoxSizer (wxVERTICAL); + SetSizer (v_sizer); - _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))); - } + v_sizer->Add (_panel, 1, wxEXPAND); - _frame_rebuild_needed = false; - compose (); - } + wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL); + h_sizer->Add (_play_button, 0, wxEXPAND); + h_sizer->Add (_slider, 1, wxEXPAND); - if (_composition_needed) { - compose (); - } + v_sizer->Add (h_sizer, 0, wxEXPAND); - wxPaintDC dc (this); - if (_bitmap) { - dc.DrawBitmap (*_bitmap, 0, 0, false); - } + _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); - if (_film->with_subtitles() && _subtitle) { - dc.DrawBitmap (*_subtitle->bitmap, _subtitle->transformed_area.x, _subtitle->transformed_area.y, true); - } - } - - /** Handle a size event */ - void size_event (wxSizeEvent &) - { - if (!_image) { - return; - } + set_film (_film); - recompose (); - } + JobManager::instance()->ActiveJobsChanged.connect ( + bind (&FilmViewer::active_jobs_changed, this, _1) + ); +} - /** @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->decode_subtitles = true; + 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)); + _decoders.video->set_subtitle_stream (_film->subtitle_stream()); + calculate_sizes (); + get_frame (); + _panel->Refresh (); + _slider->Show (_film->content_type() == VIDEO); + _play_button->Show (_film->content_type() == VIDEO); + break; } - - void set_film (shared_ptr<Film> f) - { - _film = f; - if (!_film) { - clear (); - _frame_rebuild_needed = true; - Refresh (); - } else { - _frame_rebuild_needed = true; - Refresh (); - } + case Film::WITH_SUBTITLES: + case Film::SUBTITLE_OFFSET: + case Film::SUBTITLE_SCALE: + case Film::SCALER: + update_from_raw (); + break; + case Film::SUBTITLE_STREAM: + _decoders.video->set_subtitle_stream (_film->subtitle_stream ()); + break; + 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 (); + _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1)); -private: - - 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) - ); + film_changed (Film::CONTENT); + film_changed (Film::CROP); + film_changed (Film::FORMAT); + film_changed (Film::WITH_SUBTITLES); + film_changed (Film::SUBTITLE_OFFSET); + film_changed (Film::SUBTITLE_SCALE); + film_changed (Film::SUBTITLE_STREAM); +} - /* Target ratio */ - float const target = _film->format() ? _film->format()->ratio_as_float (_film) : 1.78; +void +FilmViewer::decoder_changed () +{ + seek_and_update (_decoders.video->last_source_frame ()); +} - _transformed_image = _image->GetSubImage (wxRect (cropped_area.x, cropped_area.y, cropped_area.width, cropped_area.height)); +void +FilmViewer::timer (wxTimerEvent& ev) +{ + if (!_film) { + return; + } + + _panel->Refresh (); + _panel->Update (); - float x_scale = 1; - float y_scale = 1; + get_frame (); - 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; + 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); } + } +} - _bitmap.reset (new wxBitmap (_transformed_image)); - - if (_subtitle) { - _subtitle->transformed_area = subtitle_transformed_area ( - x_scale, y_scale, _subtitle->base_area, _film->subtitle_offset(), _film->subtitle_scale() - ); +void +FilmViewer::paint_panel (wxPaintEvent& ev) +{ + wxPaintDC dc (_panel); - _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 (!_display_frame || !_film) { + dc.Clear (); + return; } - 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; + wxImage frame (_out_width, _out_height, _display_frame->data()[0], true); + wxBitmap frame_bitmap (frame); + dc.DrawBitmap (frame_bitmap, 0, 0); - 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 (); - } - - Rect base_area; - Rect transformed_area; - wxImage base_image; - wxImage transformed_image; - shared_ptr<wxBitmap> bitmap; - }; - - shared_ptr<SubtitleView> _subtitle; -}; + if (_film->with_subtitles() && _display_sub) { + wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true); + wxBitmap sub_bitmap (sub); + dc.DrawBitmap (sub_bitmap, _display_sub_position.x, _display_sub_position.y); + } +} -BEGIN_EVENT_TABLE (ThumbPanel, wxPanel) -EVT_PAINT (ThumbPanel::paint_event) -EVT_SIZE (ThumbPanel::size_event) -END_EVENT_TABLE () -FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) - : wxPanel (p) +void +FilmViewer::slider_moved (wxCommandEvent& ev) { - _sizer = new wxBoxSizer (wxVERTICAL); - SetSizer (_sizer); + if (!_film) { + 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); - - set_film (_film); + if (_film->length()) { + seek_and_update (_slider->GetValue() * _film->length().get() / 4096); + } } void -FilmViewer::set_thumbnail (int n) +FilmViewer::seek_and_update (SourceFrame f) { - if (_film == 0 || int (_film->thumbs().size()) <= n) { + if (_decoders.video->seek (f)) { + cout << "could not s&u to " << f << "\n"; return; } - _thumb_panel->set (n); + get_frame (); + _panel->Refresh (); + _panel->Update (); } void -FilmViewer::slider_changed (wxCommandEvent &) +FilmViewer::panel_sized (wxSizeEvent& ev) { - set_thumbnail (_slider->GetValue ()); + _panel_width = ev.GetSize().GetWidth(); + _panel_height = ev.GetSize().GetHeight(); + calculate_sizes (); + update_from_raw (); } void -FilmViewer::film_changed (Film::Property p) +FilmViewer::update_from_raw () { - 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; + if (!_raw_frame) { + return; } + + raw_to_display (); + + _panel->Refresh (); + _panel->Update (); } void -FilmViewer::set_film (shared_ptr<Film> f) +FilmViewer::raw_to_display () { - if (_film == f) { + if (!_raw_frame || !_out_width || !_out_height || !_film) { return; } - - _film = f; - _thumb_panel->set_film (_film); + /* Get a compacted image as we have to feed it to wxWidgets */ + _display_frame = _raw_frame->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, _film->scaler(), false); + + if (_raw_sub) { + Rect tx = subtitle_transformed_area ( + float (_out_width) / _film->size().width, + float (_out_height) / _film->size().height, + _raw_sub->area(), _film->subtitle_offset(), _film->subtitle_scale() + ); + + _display_sub.reset (new RGBPlusAlphaImage (_raw_sub->image()->scale (tx.size(), _film->scaler(), false))); + _display_sub_position = tx.position(); + } else { + _display_sub.reset (); + } +} + +void +FilmViewer::calculate_sizes () +{ if (!_film) { return; } + + 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; + } +} - _film->Changed.connect (bind (&FilmViewer::film_changed, this, _1)); - film_changed (Film::CROP); - film_changed (Film::THUMBS); - setup_visibility (); +void +FilmViewer::play_clicked (wxCommandEvent &) +{ + check_play_state (); } void -FilmViewer::setup_visibility () +FilmViewer::check_play_state () { if (!_film) { return; } + + if (_play_button->GetValue()) { + _timer.Start (1000 / _film->frames_per_second()); + } else { + _timer.Stop (); + } +} - ContentType const c = _film->content_type (); - _slider->Show (c == VIDEO); +void +FilmViewer::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +{ + _raw_frame = image; + _raw_sub = sub; + + raw_to_display (); +} + +void +FilmViewer::get_frame () +{ + /* Clear our raw frame in case we don't get a new one */ + _raw_frame.reset (); + + try { + shared_ptr<Image> last = _display_frame; + while (last == _display_frame) { + if (_decoders.video->pass ()) { + /* We didn't get a frame before the decoder gave up, + so clear our display frame. + */ + _display_frame.reset (); + break; + } + } + } catch (DecodeError& e) { + error_dialog (this, String::compose ("Could not decode video for view (%1)", e.what())); + } } + +void +FilmViewer::active_jobs_changed (bool a) +{ + if (a) { + list<shared_ptr<Job> > jobs = JobManager::instance()->get (); + list<shared_ptr<Job> >::iterator i = jobs.begin (); + while (i != jobs.end() && boost::dynamic_pointer_cast<ExamineContentJob> (*i) == 0) { + ++i; + } + + if (i == jobs.end() || (*i)->finished()) { + /* no examine content job running, so we're ok to use the viewer */ + a = false; + } + } + + _slider->Enable (!a); + _play_button->Enable (!a); +} + diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 95bdf099d..5a791a8e2 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -23,11 +23,16 @@ #include <wx/wx.h> #include "lib/film.h" +#include "lib/decoder_factory.h" -class ThumbPanel; +class wxToggleButton; +class FFmpegPlayer; +class Image; +class RGBPlusAlphaImage; +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 +40,40 @@ 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); + void raw_to_display (); + void get_frame (); + void active_jobs_changed (bool); 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_frame; + boost::shared_ptr<Subtitle> _raw_sub; + boost::shared_ptr<Image> _display_frame; + boost::shared_ptr<RGBPlusAlphaImage> _display_sub; + Position _display_sub_position; + + int _out_width; + int _out_height; + int _panel_width; + int _panel_height; }; diff --git a/test/metadata.ref b/test/metadata.ref index 3f129c6e2..ab5e01eb0 100644 --- a/test/metadata.ref +++ b/test/metadata.ref @@ -2,6 +2,7 @@ version 1 name fred use_dci_name 1 content +trust_content_header 1 dcp_content_type Short format 185 left_crop 1 diff --git a/test/test.cc b/test/test.cc index e2f9f41ee..d2c589951 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"); @@ -331,7 +327,7 @@ do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* descriptio BOOST_AUTO_TEST_CASE (client_server_test) { - shared_ptr<Image> image (new CompactImage (PIX_FMT_RGB24, Size (1998, 1080))); + shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, Size (1998, 1080), false)); uint8_t* p = image->data()[0]; for (int y = 0; y < 1080; ++y) { @@ -342,7 +338,7 @@ BOOST_AUTO_TEST_CASE (client_server_test) } } - shared_ptr<Image> sub_image (new CompactImage (PIX_FMT_RGBA, Size (100, 200))); + shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, Size (100, 200), false)); p = sub_image->data()[0]; for (int y = 0; y < 200; ++y) { for (int x = 0; x < 100; ++x) { @@ -535,3 +531,101 @@ BOOST_AUTO_TEST_CASE (job_manager_test) BOOST_CHECK_EQUAL (b->running(), false); } +BOOST_AUTO_TEST_CASE (compact_image_test) +{ + SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, Size (50, 50), false); + BOOST_CHECK_EQUAL (s->components(), 1); + BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3); + BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3); + BOOST_CHECK (s->data()[0]); + BOOST_CHECK (!s->data()[1]); + BOOST_CHECK (!s->data()[2]); + BOOST_CHECK (!s->data()[3]); + + /* copy constructor */ + SimpleImage* t = new SimpleImage (*s); + BOOST_CHECK_EQUAL (t->components(), 1); + BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3); + BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3); + BOOST_CHECK (t->data()[0]); + BOOST_CHECK (!t->data()[1]); + BOOST_CHECK (!t->data()[2]); + BOOST_CHECK (!t->data()[3]); + BOOST_CHECK (t->data() != s->data()); + BOOST_CHECK (t->data()[0] != s->data()[0]); + BOOST_CHECK (t->line_size() != s->line_size()); + BOOST_CHECK (t->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (t->stride() != s->stride()); + BOOST_CHECK (t->stride()[0] == s->stride()[0]); + + /* assignment operator */ + SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, Size (150, 150), true); + *u = *s; + BOOST_CHECK_EQUAL (u->components(), 1); + BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3); + BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3); + BOOST_CHECK (u->data()[0]); + BOOST_CHECK (!u->data()[1]); + BOOST_CHECK (!u->data()[2]); + BOOST_CHECK (!u->data()[3]); + BOOST_CHECK (u->data() != s->data()); + BOOST_CHECK (u->data()[0] != s->data()[0]); + BOOST_CHECK (u->line_size() != s->line_size()); + BOOST_CHECK (u->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (u->stride() != s->stride()); + BOOST_CHECK (u->stride()[0] == s->stride()[0]); + + delete s; + delete t; + delete u; +} + +BOOST_AUTO_TEST_CASE (aligned_image_test) +{ + SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, Size (50, 50), true); + BOOST_CHECK_EQUAL (s->components(), 1); + /* 160 is 150 aligned to the nearest 32 bytes */ + BOOST_CHECK_EQUAL (s->stride()[0], 160); + BOOST_CHECK_EQUAL (s->line_size()[0], 150); + BOOST_CHECK (s->data()[0]); + BOOST_CHECK (!s->data()[1]); + BOOST_CHECK (!s->data()[2]); + BOOST_CHECK (!s->data()[3]); + + /* copy constructor */ + SimpleImage* t = new SimpleImage (*s); + BOOST_CHECK_EQUAL (t->components(), 1); + BOOST_CHECK_EQUAL (t->stride()[0], 160); + BOOST_CHECK_EQUAL (t->line_size()[0], 150); + BOOST_CHECK (t->data()[0]); + BOOST_CHECK (!t->data()[1]); + BOOST_CHECK (!t->data()[2]); + BOOST_CHECK (!t->data()[3]); + BOOST_CHECK (t->data() != s->data()); + BOOST_CHECK (t->data()[0] != s->data()[0]); + BOOST_CHECK (t->line_size() != s->line_size()); + BOOST_CHECK (t->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (t->stride() != s->stride()); + BOOST_CHECK (t->stride()[0] == s->stride()[0]); + + /* assignment operator */ + SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, Size (150, 150), false); + *u = *s; + BOOST_CHECK_EQUAL (u->components(), 1); + BOOST_CHECK_EQUAL (u->stride()[0], 160); + BOOST_CHECK_EQUAL (u->line_size()[0], 150); + BOOST_CHECK (u->data()[0]); + BOOST_CHECK (!u->data()[1]); + BOOST_CHECK (!u->data()[2]); + BOOST_CHECK (!u->data()[3]); + BOOST_CHECK (u->data() != s->data()); + BOOST_CHECK (u->data()[0] != s->data()[0]); + BOOST_CHECK (u->line_size() != s->line_size()); + BOOST_CHECK (u->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (u->stride() != s->stride()); + BOOST_CHECK (u->stride()[0] == s->stride()[0]); + + delete s; + delete t; + delete u; +} @@ -22,7 +22,7 @@ def configure(conf): conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing', '-Wall', '-Wno-attributes']) if conf.options.target_windows: - conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H']) + conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE']) wxrc = os.popen('wx-config --rescomp').read().split()[1:] print wxrc conf.env.append_value('WINRCFLAGS', wxrc) |
