diff options
Diffstat (limited to 'src')
167 files changed, 7800 insertions, 6684 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc deleted file mode 100644 index a204677db..000000000 --- a/src/lib/ab_transcode_job.cc +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <stdexcept> -#include "ab_transcode_job.h" -#include "film.h" -#include "format.h" -#include "filter.h" -#include "ab_transcoder.h" -#include "config.h" -#include "encoder.h" -#include "log.h" - -#include "i18n.h" - -using std::string; -using boost::shared_ptr; - -/** @param f Film to compare. - * @param o Decode options. - */ -ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o) - : Job (f) - , _decode_opt (o) -{ - _film_b.reset (new Film (*_film)); - _film_b->set_scaler (Config::instance()->reference_scaler ()); - _film_b->set_filters (Config::instance()->reference_filters ()); -} - -string -ABTranscodeJob::name () const -{ - return String::compose (_("A/B transcode %1"), _film->name()); -} - -void -ABTranscodeJob::run () -{ - try { - /* _film_b is the one with reference filters */ - ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film))); - w.go (); - set_progress (1); - set_state (FINISHED_OK); - - _film->log()->log ("A/B transcode job completed successfully"); - - } catch (std::exception& e) { - - set_progress (1); - set_state (FINISHED_ERROR); - _film->log()->log (String::compose ("A/B transcode job failed (%1)", e.what())); - throw; - } -} diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h deleted file mode 100644 index 8e3cbe2d8..000000000 --- a/src/lib/ab_transcode_job.h +++ /dev/null @@ -1,53 +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/ab_transcode_job.h - * @brief Job to run a transcoder which produces output for A/B comparison of various settings. - */ - -#include <boost/shared_ptr.hpp> -#include "job.h" -#include "options.h" - -class Film; - -/** @class ABTranscodeJob - * @brief Job to run a transcoder which produces output for A/B comparison of various settings. - * - * The right half of the frame will be processed using the Film supplied; - * the left half will be processed using the same state but with the reference - * filters and scaler. - */ -class ABTranscodeJob : public Job -{ -public: - ABTranscodeJob ( - boost::shared_ptr<Film> f, - DecodeOptions o - ); - - std::string name () const; - void run (); - -private: - DecodeOptions _decode_opt; - - /** Copy of our Film using the reference filters and scaler */ - boost::shared_ptr<Film> _film_b; -}; diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc deleted file mode 100644 index c42f0d241..000000000 --- a/src/lib/ab_transcoder.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <iostream> -#include <boost/shared_ptr.hpp> -#include "ab_transcoder.h" -#include "film.h" -#include "video_decoder.h" -#include "audio_decoder.h" -#include "encoder.h" -#include "job.h" -#include "options.h" -#include "image.h" -#include "decoder_factory.h" -#include "matcher.h" -#include "delay_line.h" -#include "gain.h" -#include "combiner.h" -#include "trimmer.h" - -/** @file src/ab_transcoder.cc - * @brief A transcoder which uses one Film for the left half of the screen, and a different one - * for the right half (to facilitate A/B comparisons of settings) - */ - -using std::string; -using boost::shared_ptr; -using boost::dynamic_pointer_cast; - -/** @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 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, DecodeOptions o, Job* j, shared_ptr<Encoder> e) - : _film_a (a) - , _film_b (b) - , _job (j) - , _encoder (e) - , _combiner (new Combiner (a->log())) -{ - _da = decoder_factory (_film_a, o); - _db = decoder_factory (_film_b, o); - - shared_ptr<AudioStream> st = _film_a->audio_stream(); - if (st && st->sample_rate()) { - _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate())); - } - _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() / 1000.0f)); - _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain())); - - int const sr = st ? st->sample_rate() : 0; - int const trim_start = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_start() : 0; - int const trim_end = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_end() : 0; - _trimmer.reset (new Trimmer ( - _film_a->log(), trim_start, trim_end, _film_a->length().get(), - sr, _film_a->source_frame_rate(), _film_a->dcp_frame_rate() - )); - - /* Set up the decoder to use the film's set streams */ - _da.video->set_subtitle_stream (_film_a->subtitle_stream ()); - _db.video->set_subtitle_stream (_film_a->subtitle_stream ()); - if (_film_a->audio_stream ()) { - _da.audio->set_audio_stream (_film_a->audio_stream ()); - } - - _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4)); - _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4)); - - _combiner->connect_video (_delay_line); - if (_matcher) { - _delay_line->connect_video (_matcher); - _matcher->connect_video (_trimmer); - } else { - _delay_line->connect_video (_trimmer); - } - _trimmer->connect_video (_encoder); - - _da.audio->connect_audio (_delay_line); - if (_matcher) { - _delay_line->connect_audio (_matcher); - _matcher->connect_audio (_gain); - } else { - _delay_line->connect_audio (_gain); - } - _gain->connect_audio (_trimmer); - _trimmer->connect_audio (_encoder); -} - -void -ABTranscoder::go () -{ - _encoder->process_begin (); - - bool done[3] = { false, false, false }; - - while (1) { - done[0] = _da.video->pass (); - done[1] = _db.video->pass (); - - if (!done[2] && _da.audio && dynamic_pointer_cast<Decoder> (_da.audio) != dynamic_pointer_cast<Decoder> (_da.video)) { - done[2] = _da.audio->pass (); - } else { - done[2] = true; - } - - if (_job) { - _da.video->set_progress (_job); - } - - if (done[0] && done[1] && done[2]) { - break; - } - } - - _delay_line->process_end (); - if (_matcher) { - _matcher->process_end (); - } - _gain->process_end (); - _encoder->process_end (); -} - diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h deleted file mode 100644 index 4f1b14e48..000000000 --- a/src/lib/ab_transcoder.h +++ /dev/null @@ -1,74 +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/ab_transcoder.h - * @brief A transcoder which uses one Film for the left half of the screen, and a different one - * for the right half (to facilitate A/B comparisons of settings) - */ - -#include <boost/shared_ptr.hpp> -#include <stdint.h> -#include "util.h" -#include "decoder_factory.h" - -class Job; -class Encoder; -class VideoDecoder; -class AudioDecoder; -class Image; -class Log; -class Subtitle; -class Film; -class Matcher; -class DelayLine; -class Gain; -class Combiner; -class Trimmer; - -/** @class ABTranscoder - * @brief A transcoder which uses one Film for the left half of the screen, and a different one - * for the right half (to facilitate A/B comparisons of settings) - */ -class ABTranscoder -{ -public: - ABTranscoder ( - boost::shared_ptr<Film> a, - boost::shared_ptr<Film> b, - DecodeOptions o, - Job* j, - boost::shared_ptr<Encoder> e - ); - - void go (); - -private: - boost::shared_ptr<Film> _film_a; - boost::shared_ptr<Film> _film_b; - Job* _job; - boost::shared_ptr<Encoder> _encoder; - Decoders _da; - Decoders _db; - boost::shared_ptr<Combiner> _combiner; - boost::shared_ptr<Matcher> _matcher; - boost::shared_ptr<DelayLine> _delay_line; - boost::shared_ptr<Gain> _gain; - boost::shared_ptr<Trimmer> _trimmer; - boost::shared_ptr<Image> _image; -}; diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 88cd65fee..2848c1ed7 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -21,9 +21,7 @@ #include "analyse_audio_job.h" #include "compose.hpp" #include "film.h" -#include "options.h" -#include "decoder_factory.h" -#include "audio_decoder.h" +#include "player.h" #include "i18n.h" @@ -35,8 +33,9 @@ using boost::shared_ptr; int const AnalyseAudioJob::_num_points = 1024; -AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f) +AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioContent> c) : Job (f) + , _content (c) , _done (0) , _samples_per_point (1) { @@ -46,45 +45,47 @@ AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f) string AnalyseAudioJob::name () const { - return String::compose (_("Analyse audio of %1"), _film->name()); + shared_ptr<AudioContent> content = _content.lock (); + if (!content) { + return ""; + } + + return String::compose (_("Analyse audio of %1"), content->file().filename()); } void AnalyseAudioJob::run () { - if (!_film->audio_stream () || !_film->length()) { - set_progress (1); - set_state (FINISHED_ERROR); + shared_ptr<AudioContent> content = _content.lock (); + if (!content) { return; } - - DecodeOptions options; - options.decode_video = false; - Decoders decoders = decoder_factory (_film, options); - assert (decoders.audio); + shared_ptr<Playlist> playlist (new Playlist); + playlist->add (content); + shared_ptr<Player> player (new Player (_film, playlist)); + player->disable_video (); - decoders.audio->set_audio_stream (_film->audio_stream ()); - decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1)); + player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2)); - int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->source_frame_rate()); - _samples_per_point = max (int64_t (1), total_audio_frames / _num_points); + _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points); - _current.resize (_film->audio_stream()->channels ()); - _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels())); - - while (!decoders.audio->pass()) { - set_progress (float (_done) / total_audio_frames); + _current.resize (_film->dcp_audio_channels ()); + _analysis.reset (new AudioAnalysis (_film->dcp_audio_channels ())); + + _done = 0; + while (!player->pass ()) { + set_progress (double (_film->audio_frames_to_time (_done)) / _film->length ()); } - _analysis->write (_film->audio_analysis_path ()); + _analysis->write (content->audio_analysis_path ()); set_progress (1); set_state (FINISHED_OK); } void -AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b) +AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time) { for (int i = 0; i < b->frames(); ++i) { for (int j = 0; j < b->channels(); ++j) { @@ -100,7 +101,7 @@ AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b) if ((_done % _samples_per_point) == 0) { _current[j][AudioPoint::RMS] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point); _analysis->add_point (j, _current[j]); - + _current[j] = AudioPoint (); } } diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 5435e0a7c..3d4881983 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -19,21 +19,24 @@ #include "job.h" #include "audio_analysis.h" +#include "types.h" class AudioBuffers; +class AudioContent; class AnalyseAudioJob : public Job { public: - AnalyseAudioJob (boost::shared_ptr<Film> f); + AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>); std::string name () const; void run (); private: - void audio (boost::shared_ptr<const AudioBuffers>); + void audio (boost::shared_ptr<const AudioBuffers>, Time); - int64_t _done; + boost::weak_ptr<AudioContent> _content; + OutputAudioFrame _done; int64_t _samples_per_point; std::vector<AudioPoint> _current; diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc index 9d708bbfd..e12516620 100644 --- a/src/lib/audio_analysis.cc +++ b/src/lib/audio_analysis.cc @@ -62,9 +62,9 @@ AudioAnalysis::AudioAnalysis (int channels) _data.resize (channels); } -AudioAnalysis::AudioAnalysis (string filename) +AudioAnalysis::AudioAnalysis (boost::filesystem::path filename) { - ifstream f (filename.c_str ()); + ifstream f (filename.string().c_str ()); int channels; f >> channels; @@ -107,10 +107,10 @@ AudioAnalysis::points (int c) const } void -AudioAnalysis::write (string filename) +AudioAnalysis::write (boost::filesystem::path filename) { - string tmp = filename + ".tmp"; - + string tmp = filename.string() + ".tmp"; + ofstream f (tmp.c_str ()); f << _data.size() << "\n"; for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) { diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h index 6e0e2b78a..d57eba90a 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -17,12 +17,13 @@ */ -#ifndef DVDOMATIC_AUDIO_ANALYSIS_H -#define DVDOMATIC_AUDIO_ANALYSIS_H +#ifndef DCPOMATIC_AUDIO_ANALYSIS_H +#define DCPOMATIC_AUDIO_ANALYSIS_H #include <iostream> #include <vector> #include <list> +#include <boost/filesystem.hpp> class AudioPoint { @@ -50,7 +51,7 @@ class AudioAnalysis { public: AudioAnalysis (int c); - AudioAnalysis (std::string); + AudioAnalysis (boost::filesystem::path); void add_point (int c, AudioPoint const & p); @@ -58,7 +59,7 @@ public: int points (int c) const; int channels () const; - void write (std::string); + void write (boost::filesystem::path); private: std::vector<std::vector<AudioPoint> > _data; diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc new file mode 100644 index 000000000..403babaf7 --- /dev/null +++ b/src/lib/audio_buffers.cc @@ -0,0 +1,243 @@ +/* + Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <cassert> +#include <cstring> +#include <stdexcept> +#include "audio_buffers.h" + +using std::bad_alloc; +using boost::shared_ptr; + +/** Construct an AudioBuffers. Audio data is undefined after this constructor. + * @param channels Number of channels. + * @param frames Number of frames to reserve space for. + */ +AudioBuffers::AudioBuffers (int channels, int frames) + : _channels (channels) + , _frames (frames) + , _allocated_frames (frames) +{ + _data = static_cast<float**> (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (malloc (frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + } +} + +/** Copy constructor. + * @param other Other AudioBuffers; data is copied. + */ +AudioBuffers::AudioBuffers (AudioBuffers const & other) + : _channels (other._channels) + , _frames (other._frames) + , _allocated_frames (other._frames) +{ + _data = static_cast<float**> (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (malloc (_frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + memcpy (_data[i], other._data[i], _frames * sizeof (float)); + } +} + +/* XXX: it's a shame that this is a copy-and-paste of the above; + probably fixable with c++0x. +*/ +AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> other) + : _channels (other->_channels) + , _frames (other->_frames) + , _allocated_frames (other->_frames) +{ + _data = static_cast<float**> (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (malloc (_frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + memcpy (_data[i], other->_data[i], _frames * sizeof (float)); + } +} + +/** AudioBuffers destructor */ +AudioBuffers::~AudioBuffers () +{ + for (int i = 0; i < _channels; ++i) { + free (_data[i]); + } + + free (_data); +} + +/** @param c Channel index. + * @return Buffer for this channel. + */ +float* +AudioBuffers::data (int c) const +{ + assert (c >= 0 && c < _channels); + return _data[c]; +} + +/** Set the number of frames that these AudioBuffers will report themselves + * as having. + * @param f Frames; must be less than or equal to the number of allocated frames. + */ +void +AudioBuffers::set_frames (int f) +{ + assert (f <= _allocated_frames); + _frames = f; +} + +/** Make all samples on all channels silent */ +void +AudioBuffers::make_silent () +{ + for (int i = 0; i < _channels; ++i) { + make_silent (i); + } +} + +/** Make all samples on a given channel silent. + * @param c Channel. + */ +void +AudioBuffers::make_silent (int c) +{ + assert (c >= 0 && c < _channels); + + for (int i = 0; i < _frames; ++i) { + _data[c][i] = 0; + } +} + +/** Copy data from another AudioBuffers to this one. All channels are copied. + * @param from AudioBuffers to copy from; must have the same number of channels as this. + * @param frames_to_copy Number of frames to copy. + * @param read_offset Offset to read from in `from'. + * @param write_offset Offset to write to in `to'. + */ +void +AudioBuffers::copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset) +{ + assert (from->channels() == channels()); + + assert (from); + assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames); + assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames); + + for (int i = 0; i < _channels; ++i) { + memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float)); + } +} + +/** Move audio data around. + * @param from Offset to move from. + * @param to Offset to move to. + * @param frames Number of frames to move. + */ + +void +AudioBuffers::move (int from, int to, int frames) +{ + if (frames == 0) { + return; + } + + assert (from >= 0); + assert (from < _frames); + assert (to >= 0); + assert (to < _frames); + assert (frames > 0); + assert (frames <= _frames); + assert ((from + frames) <= _frames); + assert ((to + frames) <= _frames); + + for (int i = 0; i < _channels; ++i) { + memmove (_data[i] + to, _data[i] + from, frames * sizeof(float)); + } +} + +/** Add data from from `from', `from_channel' to our channel `to_channel' */ +void +AudioBuffers::accumulate_channel (AudioBuffers const * from, int from_channel, int to_channel) +{ + int const N = frames (); + assert (from->frames() == N); + + float* s = from->data (from_channel); + float* d = _data[to_channel]; + + for (int i = 0; i < N; ++i) { + *d++ += *s++; + } +} + +/** Ensure we have space for at least a certain number of frames. If we extend + * the buffers, fill the new space with silence. + */ +void +AudioBuffers::ensure_size (int frames) +{ + if (_allocated_frames >= frames) { + return; + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (realloc (_data[i], frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + for (int j = _allocated_frames; j < frames; ++j) { + _data[i][j] = 0; + } + } + + _allocated_frames = frames; +} + +void +AudioBuffers::accumulate_frames (AudioBuffers const * from, int read_offset, int write_offset, int frames) +{ + assert (_channels == from->channels ()); + + for (int i = 0; i < _channels; ++i) { + for (int j = 0; j < frames; ++j) { + _data[i][j + write_offset] += from->data()[i][j + read_offset]; + } + } +} + diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h new file mode 100644 index 000000000..47b8145a1 --- /dev/null +++ b/src/lib/audio_buffers.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/shared_ptr.hpp> + +/** @class AudioBuffers + * @brief A class to hold multi-channel audio data in float format. + */ +class AudioBuffers +{ +public: + AudioBuffers (int channels, int frames); + AudioBuffers (AudioBuffers const &); + AudioBuffers (boost::shared_ptr<const AudioBuffers>); + ~AudioBuffers (); + + void ensure_size (int); + + float** data () const { + return _data; + } + + float* data (int) const; + + int channels () const { + return _channels; + } + + int frames () const { + return _frames; + } + + void set_frames (int f); + + void make_silent (); + void make_silent (int c); + + void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset); + void move (int from, int to, int frames); + void accumulate_channel (AudioBuffers const *, int, int); + void accumulate_frames (AudioBuffers const *, int read_offset, int write_offset, int frames); + +private: + /** Number of channels */ + int _channels; + /** Number of frames (where a frame is one sample across all channels) */ + int _frames; + /** Number of frames that _data can hold */ + int _allocated_frames; + /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */ + float** _data; +}; diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc new file mode 100644 index 000000000..e93f348f4 --- /dev/null +++ b/src/lib/audio_content.cc @@ -0,0 +1,122 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libcxml/cxml.h> +#include "audio_content.h" +#include "analyse_audio_job.h" +#include "job_manager.h" +#include "film.h" + +using std::string; +using boost::shared_ptr; +using boost::lexical_cast; +using boost::dynamic_pointer_cast; + +int const AudioContentProperty::AUDIO_CHANNELS = 200; +int const AudioContentProperty::AUDIO_LENGTH = 201; +int const AudioContentProperty::AUDIO_FRAME_RATE = 202; +int const AudioContentProperty::AUDIO_GAIN = 203; +int const AudioContentProperty::AUDIO_DELAY = 204; +int const AudioContentProperty::AUDIO_MAPPING = 205; + +AudioContent::AudioContent (shared_ptr<const Film> f, Time s) + : Content (f, s) + , _audio_gain (0) + , _audio_delay (0) +{ + +} + +AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , _audio_gain (0) + , _audio_delay (0) +{ + +} + +AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + : Content (f, node) +{ + _audio_gain = node->number_child<float> ("AudioGain"); + _audio_delay = node->number_child<int> ("AudioDelay"); +} + +AudioContent::AudioContent (AudioContent const & o) + : Content (o) + , _audio_gain (o._audio_gain) + , _audio_delay (o._audio_delay) +{ + +} + +void +AudioContent::as_xml (xmlpp::Node* node) const +{ + boost::mutex::scoped_lock lm (_mutex); + node->add_child("AudioGain")->add_child_text (lexical_cast<string> (_audio_gain)); + node->add_child("AudioDelay")->add_child_text (lexical_cast<string> (_audio_delay)); +} + + +void +AudioContent::set_audio_gain (float g) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_gain = g; + } + + signal_changed (AudioContentProperty::AUDIO_GAIN); +} + +void +AudioContent::set_audio_delay (int d) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_delay = d; + } + + signal_changed (AudioContentProperty::AUDIO_DELAY); +} + +void +AudioContent::analyse_audio (boost::function<void()> finished) +{ + shared_ptr<const Film> film = _film.lock (); + if (!film) { + return; + } + + shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, dynamic_pointer_cast<AudioContent> (shared_from_this()))); + job->Finished.connect (finished); + JobManager::instance()->add (job); +} + +boost::filesystem::path +AudioContent::audio_analysis_path () const +{ + shared_ptr<const Film> film = _film.lock (); + if (!film) { + return boost::filesystem::path (); + } + + return film->audio_analysis_path (dynamic_pointer_cast<const AudioContent> (shared_from_this ())); +} diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h new file mode 100644 index 000000000..9bf53e0ab --- /dev/null +++ b/src/lib/audio_content.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_AUDIO_CONTENT_H +#define DCPOMATIC_AUDIO_CONTENT_H + +#include "content.h" +#include "audio_mapping.h" + +namespace cxml { + class Node; +} + +class AudioContentProperty +{ +public: + static int const AUDIO_CHANNELS; + static int const AUDIO_LENGTH; + static int const AUDIO_FRAME_RATE; + static int const AUDIO_GAIN; + static int const AUDIO_DELAY; + static int const AUDIO_MAPPING; +}; + +class AudioContent : public virtual Content +{ +public: + typedef int64_t Frame; + + AudioContent (boost::shared_ptr<const Film>, Time); + AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path); + AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + AudioContent (AudioContent const &); + + void as_xml (xmlpp::Node *) const; + + virtual int audio_channels () const = 0; + virtual AudioContent::Frame audio_length () const = 0; + virtual int content_audio_frame_rate () const = 0; + virtual int output_audio_frame_rate () const = 0; + virtual AudioMapping audio_mapping () const = 0; + virtual void set_audio_mapping (AudioMapping) = 0; + + void analyse_audio (boost::function<void()>); + boost::filesystem::path audio_analysis_path () const; + + void set_audio_gain (float); + void set_audio_delay (int); + + float audio_gain () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_gain; + } + + int audio_delay () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_delay; + } + +private: + /** Gain to apply to audio in dB */ + float _audio_gain; + /** Delay to apply to audio (positive moves audio later) in milliseconds */ + int _audio_delay; +}; + +#endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index a54c14843..dc49a1846 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -18,19 +18,58 @@ */ #include "audio_decoder.h" -#include "stream.h" +#include "audio_buffers.h" +#include "exceptions.h" +#include "log.h" +#include "i18n.h" + +using std::stringstream; +using std::list; +using std::pair; +using std::cout; using boost::optional; using boost::shared_ptr; -AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) +AudioDecoder::AudioDecoder (shared_ptr<const Film> f) + : Decoder (f) + , _audio_position (0) +{ +} + +#if 0 +void +AudioDecoder::process_end () { + if (_swr_context) { + + shared_ptr<const Film> film = _film.lock (); + assert (film); + + shared_ptr<AudioBuffers> out (new AudioBuffers (film->audio_mapping().dcp_channels(), 256)); + + while (1) { + int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0); + + if (frames < 0) { + throw EncodeError (_("could not run sample-rate converter")); + } + + if (frames == 0) { + break; + } + + out->set_frames (frames); + _writer->write (out); + } + } } +#endif void -AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s) +AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame) { - _audio_stream = s; + Audio (data, frame); + _audio_position = frame + data->frames (); } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index cfe94b528..ddfb296c9 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -21,38 +21,29 @@ * @brief Parent class for audio decoders. */ -#ifndef DVDOMATIC_AUDIO_DECODER_H -#define DVDOMATIC_AUDIO_DECODER_H +#ifndef DCPOMATIC_AUDIO_DECODER_H +#define DCPOMATIC_AUDIO_DECODER_H -#include "audio_source.h" -#include "stream.h" #include "decoder.h" +#include "content.h" + +class AudioBuffers; /** @class AudioDecoder. * @brief Parent class for audio decoders. */ -class AudioDecoder : public TimedAudioSource, public virtual Decoder +class AudioDecoder : public virtual Decoder { public: - AudioDecoder (boost::shared_ptr<Film>, DecodeOptions); - - virtual void set_audio_stream (boost::shared_ptr<AudioStream>); + AudioDecoder (boost::shared_ptr<const Film>); - /** @return Audio stream that we are using */ - boost::shared_ptr<AudioStream> audio_stream () const { - return _audio_stream; - } - - /** @return All available audio streams */ - std::vector<boost::shared_ptr<AudioStream> > audio_streams () const { - return _audio_streams; - } + /** Emitted when some audio data is ready */ + boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio; protected: - /** Audio stream that we are using */ - boost::shared_ptr<AudioStream> _audio_stream; - /** All available audio streams */ - std::vector<boost::shared_ptr<AudioStream> > _audio_streams; + + void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); + AudioContent::Frame _audio_position; }; #endif diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc new file mode 100644 index 000000000..4630f17c1 --- /dev/null +++ b/src/lib/audio_mapping.cc @@ -0,0 +1,116 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/lexical_cast.hpp> +#include <libxml++/libxml++.h> +#include <libcxml/cxml.h> +#include "audio_mapping.h" + +using std::list; +using std::cout; +using std::make_pair; +using std::pair; +using std::string; +using boost::shared_ptr; +using boost::lexical_cast; +using boost::dynamic_pointer_cast; + +AudioMapping::AudioMapping () +{ + +} + +/** Create a default AudioMapping for a given channel count. + * @param c Number of channels. + */ +AudioMapping::AudioMapping (int c) +{ + if (c == 1) { + /* Mono -> Centre */ + add (0, libdcp::CENTRE); + } else { + /* 1:1 mapping */ + for (int i = 0; i < c; ++i) { + add (i, static_cast<libdcp::Channel> (i)); + } + } +} + +AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node) +{ + list<shared_ptr<cxml::Node> > const c = node->node_children ("Map"); + for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) { + add ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP"))); + } +} + +void +AudioMapping::add (int c, libdcp::Channel d) +{ + _content_to_dcp.push_back (make_pair (c, d)); +} + +list<int> +AudioMapping::dcp_to_content (libdcp::Channel d) const +{ + list<int> c; + for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + if (i->second == d) { + c.push_back (i->first); + } + } + + return c; +} + +list<int> +AudioMapping::content_channels () const +{ + list<int> c; + for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + if (find (c.begin(), c.end(), i->first) == c.end ()) { + c.push_back (i->first); + } + } + + return c; +} + +list<libdcp::Channel> +AudioMapping::content_to_dcp (int c) const +{ + list<libdcp::Channel> d; + for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + if (i->first == c) { + d.push_back (i->second); + } + } + + return d; +} + +void +AudioMapping::as_xml (xmlpp::Node* node) const +{ + for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + xmlpp::Node* t = node->add_child ("Map"); + t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first)); + t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second)); + } +} diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h new file mode 100644 index 000000000..a2de8306b --- /dev/null +++ b/src/lib/audio_mapping.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_AUDIO_MAPPING_H +#define DCPOMATIC_AUDIO_MAPPING_H + +#include <list> +#include <libdcp/types.h> +#include <boost/shared_ptr.hpp> + +namespace xmlpp { + class Node; +} + +namespace cxml { + class Node; +} + +class AudioMapping +{ +public: + AudioMapping (); + AudioMapping (int); + AudioMapping (boost::shared_ptr<const cxml::Node>); + + void as_xml (xmlpp::Node *) const; + + void add (int, libdcp::Channel); + + std::list<int> dcp_to_content (libdcp::Channel) const; + std::list<std::pair<int, libdcp::Channel> > content_to_dcp () const { + return _content_to_dcp; + } + + std::list<int> content_channels () const; + std::list<libdcp::Channel> content_to_dcp (int) const; + +private: + std::list<std::pair<int, libdcp::Channel> > _content_to_dcp; +}; + +#endif diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc deleted file mode 100644 index d77e89367..000000000 --- a/src/lib/audio_source.cc +++ /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. - -*/ - -#include "audio_source.h" -#include "audio_sink.h" - -using boost::shared_ptr; -using boost::bind; - -void -AudioSource::connect_audio (shared_ptr<AudioSink> s) -{ - Audio.connect (bind (&AudioSink::process_audio, s, _1)); -} - -void -TimedAudioSource::connect_audio (shared_ptr<TimedAudioSink> s) -{ - Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2)); -} - -void -TimedAudioSource::connect_audio (shared_ptr<AudioSink> s) -{ - Audio.connect (bind (&AudioSink::process_audio, s, _1)); -} diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h deleted file mode 100644 index c13f1636b..000000000 --- a/src/lib/audio_source.h +++ /dev/null @@ -1,55 +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/audio_source.h - * @brief Parent class for classes which emit audio data. - */ - -#ifndef DVDOMATIC_AUDIO_SOURCE_H -#define DVDOMATIC_AUDIO_SOURCE_H - -#include <boost/signals2.hpp> - -class AudioBuffers; -class AudioSink; -class TimedAudioSink; - -/** A class that emits audio data */ -class AudioSource -{ -public: - /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>)> Audio; - - void connect_audio (boost::shared_ptr<AudioSink>); -}; - - -/** A class that emits audio data with timestamps */ -class TimedAudioSource -{ -public: - /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, double)> Audio; - - void connect_audio (boost::shared_ptr<AudioSink>); - void connect_audio (boost::shared_ptr<TimedAudioSink>); -}; - -#endif diff --git a/src/lib/combiner.h b/src/lib/combiner.h deleted file mode 100644 index 7ed316e26..000000000 --- a/src/lib/combiner.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/lib/combiner.h - * @brief Class for combining two video streams. - */ - -#include "processor.h" - -/** @class Combiner - * @brief A class which can combine two video streams into one, with - * one image used for the left half of the screen and the other for - * the right. - */ -class Combiner : public TimedVideoProcessor -{ -public: - Combiner (boost::shared_ptr<Log> log); - - void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double); - void process_video_b (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double); - -private: - /** The image that we are currently working on */ - boost::shared_ptr<Image> _image; -}; diff --git a/src/lib/config.cc b/src/lib/config.cc index d2d7fa2fd..e0fbcc703 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -22,11 +22,12 @@ #include <fstream> #include <glib.h> #include <boost/filesystem.hpp> +#include <libcxml/cxml.h> #include "config.h" #include "server.h" #include "scaler.h" #include "filter.h" -#include "format.h" +#include "ratio.h" #include "dcp_content_type.h" #include "sound_processor.h" @@ -36,8 +37,11 @@ using std::vector; using std::ifstream; using std::string; using std::ofstream; +using std::list; using std::max; using boost::shared_ptr; +using boost::lexical_cast; +using boost::optional; Config* Config::_instance = 0; @@ -45,10 +49,10 @@ Config* Config::_instance = 0; Config::Config () : _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency())) , _server_port (6192) - , _reference_scaler (Scaler::from_id (N_("bicubic"))) , _tms_path (N_(".")) , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750"))) - , _default_format (0) + , _default_still_length (10) + , _default_container (Ratio::from_id ("185")) , _default_dcp_content_type (0) { _allowed_dcp_frame_rates.push_back (24); @@ -57,8 +61,61 @@ Config::Config () _allowed_dcp_frame_rates.push_back (48); _allowed_dcp_frame_rates.push_back (50); _allowed_dcp_frame_rates.push_back (60); +} + +void +Config::read () +{ + if (!boost::filesystem::exists (file (false))) { + read_old_metadata (); + return; + } + + cxml::File f (file (false), "Config"); + optional<string> c; + + _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads"); + _default_directory = f.string_child ("DefaultDirectory"); + _server_port = f.number_child<int> ("ServerPort"); - ifstream f (file().c_str ()); + list<shared_ptr<cxml::Node> > servers = f.node_children ("Server"); + for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) { + _servers.push_back (new ServerDescription (*i)); + } + + _tms_ip = f.string_child ("TMSIP"); + _tms_path = f.string_child ("TMSPath"); + _tms_user = f.string_child ("TMSUser"); + _tms_password = f.string_child ("TMSPassword"); + + c = f.optional_string_child ("SoundProcessor"); + if (c) { + _sound_processor = SoundProcessor::from_id (c.get ()); + } + + _language = f.optional_string_child ("Language"); + + c = f.optional_string_child ("DefaultContainer"); + if (c) { + _default_container = Ratio::from_id (c.get ()); + } + + c = f.optional_string_child ("DefaultDCPContentType"); + if (c) { + _default_dcp_content_type = DCPContentType::from_dci_name (c.get ()); + } + + _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or (""); + _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or (""); + + _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); + _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10); +} + +void +Config::read_old_metadata () +{ + ifstream f (file(true).c_str ()); string line; while (getline (f, line)) { if (line.empty ()) { @@ -83,10 +140,6 @@ Config::Config () _default_directory = v; } else if (k == N_("server_port")) { _server_port = atoi (v.c_str ()); - } else if (k == N_("reference_scaler")) { - _reference_scaler = Scaler::from_id (v); - } else if (k == N_("reference_filter")) { - _reference_filters.push_back (Filter::from_id (v)); } else if (k == N_("server")) { _servers.push_back (ServerDescription::create_from_metadata (v)); } else if (k == N_("tms_ip")) { @@ -101,8 +154,8 @@ Config::Config () _sound_processor = SoundProcessor::from_id (v); } else if (k == "language") { _language = v; - } else if (k == "default_format") { - _default_format = Format::from_metadata (v); + } else if (k == "default_container") { + _default_container = Ratio::from_id (v); } else if (k == "default_dcp_content_type") { _default_dcp_content_type = DCPContentType::from_dci_name (v); } else if (k == "dcp_metadata_issuer") { @@ -113,19 +166,23 @@ Config::Config () _dcp_metadata.issue_date = v; } - _default_dci_metadata.read (k, v); + _default_dci_metadata.read_old_metadata (k, v); } } /** @return Filename to write configuration to */ string -Config::file () const +Config::file (bool old) const { boost::filesystem::path p; p /= g_get_user_config_dir (); boost::system::error_code ec; boost::filesystem::create_directory (p, ec); - p /= N_(".dvdomatic"); + if (old) { + p /= ".dvdomatic"; + } else { + p /= ".dcpomatic.xml"; + } return p.string (); } @@ -135,6 +192,13 @@ Config::instance () { if (_instance == 0) { _instance = new Config; + try { + _instance->read (); + } catch (...) { + /* configuration load failed; never mind, just + stick with the default. + */ + } } return _instance; @@ -144,44 +208,41 @@ Config::instance () void Config::write () const { - ofstream f (file().c_str ()); - f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n" - << "default_directory " << _default_directory << "\n" - << "server_port " << _server_port << "\n"; - - if (_reference_scaler) { - f << "reference_scaler " << _reference_scaler->id () << "\n"; - } + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("Config"); - for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) { - f << "reference_filter " << (*i)->id () << "\n"; - } + root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast<string> (_num_local_encoding_threads)); + root->add_child("DefaultDirectory")->add_child_text (_default_directory); + root->add_child("ServerPort")->add_child_text (lexical_cast<string> (_server_port)); for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) { - f << "server " << (*i)->as_metadata () << "\n"; + (*i)->as_xml (root->add_child ("Server")); } - f << "tms_ip " << _tms_ip << "\n"; - f << "tms_path " << _tms_path << "\n"; - f << "tms_user " << _tms_user << "\n"; - f << "tms_password " << _tms_password << "\n"; + root->add_child("TMSIP")->add_child_text (_tms_ip); + root->add_child("TMSPath")->add_child_text (_tms_path); + root->add_child("TMSUser")->add_child_text (_tms_user); + root->add_child("TMSPassword")->add_child_text (_tms_password); if (_sound_processor) { - f << "sound_processor " << _sound_processor->id () << "\n"; + root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ()); } if (_language) { - f << "language " << _language.get() << "\n"; + root->add_child("Language")->add_child_text (_language.get()); } - if (_default_format) { - f << "default_format " << _default_format->as_metadata() << "\n"; + if (_default_container) { + root->add_child("DefaultContainer")->add_child_text (_default_container->id ()); } if (_default_dcp_content_type) { - f << "default_dcp_content_type " << _default_dcp_content_type->dci_name() << "\n"; + root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ()); } - f << "dcp_metadata_issuer " << _dcp_metadata.issuer << "\n"; - f << "dcp_metadata_creator " << _dcp_metadata.creator << "\n"; - f << "dcp_metadata_issue_date " << _dcp_metadata.issue_date << "\n"; + root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer); + root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator); + + _default_dci_metadata.as_xml (root->add_child ("DCIMetadata")); + + root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length)); - _default_dci_metadata.write (f); + doc.write_to_file_formatted (file (false)); } string diff --git a/src/lib/config.h b/src/lib/config.h index a59cdcae0..f3e8f1441 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -21,8 +21,8 @@ * @brief Class holding configuration. */ -#ifndef DVDOMATIC_CONFIG_H -#define DVDOMATIC_CONFIG_H +#ifndef DCPOMATIC_CONFIG_H +#define DCPOMATIC_CONFIG_H #include <vector> #include <boost/shared_ptr.hpp> @@ -34,8 +34,8 @@ class ServerDescription; class Scaler; class Filter; class SoundProcessor; -class Format; class DCPContentType; +class Ratio; /** @class Config * @brief A singleton class holding configuration. @@ -65,14 +65,6 @@ public: return _servers; } - Scaler const * reference_scaler () const { - return _reference_scaler; - } - - std::vector<Filter const *> reference_filters () const { - return _reference_filters; - } - /** @return The IP address of a TMS that we can copy DCPs to */ std::string tms_ip () const { return _tms_ip; @@ -110,8 +102,12 @@ public: return _language; } - Format const * default_format () const { - return _default_format; + int default_still_length () const { + return _default_still_length; + } + + Ratio const * default_container () const { + return _default_container; } DCPContentType const * default_dcp_content_type () const { @@ -185,8 +181,12 @@ public: _language = boost::none; } - void set_default_format (Format const * f) { - _default_format = f; + void set_default_still_length (int s) { + _default_still_length = s; + } + + void set_default_container (Ratio const * c) { + _default_container = c; } void set_default_dcp_content_type (DCPContentType const * t) { @@ -204,7 +204,9 @@ public: private: Config (); - std::string file () const; + std::string file (bool) const; + void read (); + void read_old_metadata (); /** number of threads to use for J2K encoding on the local machine */ int _num_local_encoding_threads; @@ -233,7 +235,8 @@ private: /** Default DCI metadata for newly-created Films */ DCIMetadata _default_dci_metadata; boost::optional<std::string> _language; - Format const * _default_format; + int _default_still_length; + Ratio const * _default_container; DCPContentType const * _default_dcp_content_type; libdcp::XMLMetadata _dcp_metadata; diff --git a/src/lib/content.cc b/src/lib/content.cc new file mode 100644 index 000000000..6a33e9f7e --- /dev/null +++ b/src/lib/content.cc @@ -0,0 +1,98 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/thread/mutex.hpp> +#include <libxml++/libxml++.h> +#include <libcxml/cxml.h> +#include "content.h" +#include "util.h" + +using std::string; +using boost::shared_ptr; +using boost::lexical_cast; + +int const ContentProperty::START = 400; +int const ContentProperty::LENGTH = 401; + +Content::Content (shared_ptr<const Film> f, Time s) + : _film (f) + , _start (s) +{ + +} + +Content::Content (shared_ptr<const Film> f, boost::filesystem::path p) + : _film (f) + , _file (p) + , _start (0) +{ + +} + +Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + : _film (f) +{ + _file = node->string_child ("File"); + _digest = node->string_child ("Digest"); + _start = node->number_child<Time> ("Start"); +} + +Content::Content (Content const & o) + : boost::enable_shared_from_this<Content> (o) + , _film (o._film) + , _file (o._file) + , _digest (o._digest) + , _start (o._start) +{ + +} + +void +Content::as_xml (xmlpp::Node* node) const +{ + boost::mutex::scoped_lock lm (_mutex); + node->add_child("File")->add_child_text (_file.string()); + node->add_child("Digest")->add_child_text (_digest); + node->add_child("Start")->add_child_text (lexical_cast<string> (_start)); +} + +void +Content::examine (shared_ptr<Job>) +{ + string const d = md5_digest (_file); + boost::mutex::scoped_lock lm (_mutex); + _digest = d; +} + +void +Content::signal_changed (int p) +{ + Changed (shared_from_this (), p); +} + +void +Content::set_start (Time s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _start = s; + } + + signal_changed (ContentProperty::START); +} diff --git a/src/lib/content.h b/src/lib/content.h new file mode 100644 index 000000000..e33f517ab --- /dev/null +++ b/src/lib/content.h @@ -0,0 +1,94 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_CONTENT_H +#define DCPOMATIC_CONTENT_H + +#include <boost/filesystem.hpp> +#include <boost/signals2.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <libxml++/libxml++.h> +#include "types.h" + +namespace cxml { + class Node; +} + +class Job; +class Film; + +class ContentProperty +{ +public: + static int const START; + static int const LENGTH; +}; + +class Content : public boost::enable_shared_from_this<Content> +{ +public: + Content (boost::shared_ptr<const Film>, Time); + Content (boost::shared_ptr<const Film>, boost::filesystem::path); + Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + Content (Content const &); + + virtual void examine (boost::shared_ptr<Job>); + virtual std::string summary () const = 0; + virtual std::string information () const = 0; + virtual void as_xml (xmlpp::Node *) const; + virtual boost::shared_ptr<Content> clone () const = 0; + virtual Time length () const = 0; + + boost::filesystem::path file () const { + boost::mutex::scoped_lock lm (_mutex); + return _file; + } + + std::string digest () const { + boost::mutex::scoped_lock lm (_mutex); + return _digest; + } + + void set_start (Time); + + Time start () const { + boost::mutex::scoped_lock lm (_mutex); + return _start; + } + + Time end () const { + return start() + length(); + } + + boost::signals2::signal<void (boost::weak_ptr<Content>, int)> Changed; + +protected: + void signal_changed (int); + + boost::weak_ptr<const Film> _film; + mutable boost::mutex _mutex; + +private: + boost::filesystem::path _file; + std::string _digest; + Time _start; +}; + +#endif diff --git a/src/lib/cross.cc b/src/lib/cross.cc index 124697fb4..ee0ef89b2 100644 --- a/src/lib/cross.cc +++ b/src/lib/cross.cc @@ -22,16 +22,16 @@ #include "cross.h" #include "compose.hpp" #include "log.h" -#ifdef DVDOMATIC_LINUX +#ifdef DCPOMATIC_LINUX #include <unistd.h> #include <mntent.h> #endif -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS #include <windows.h> #undef DATADIR #include <shlwapi.h> #endif -#ifdef DVDOMATIC_OSX +#ifdef DCPOMATIC_OSX #include <sys/sysctl.h> #endif @@ -43,12 +43,12 @@ using std::make_pair; using boost::shared_ptr; void -dvdomatic_sleep (int s) +dcpomatic_sleep (int s) { -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX sleep (s); #endif -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS Sleep (s * 1000); #endif } @@ -60,7 +60,7 @@ cpu_info () pair<string, int> info; info.second = 0; -#ifdef DVDOMATIC_LINUX +#ifdef DCPOMATIC_LINUX ifstream f ("/proc/cpuinfo"); while (f.good ()) { string l; @@ -76,7 +76,7 @@ cpu_info () } #endif -#ifdef DVDOMATIC_OSX +#ifdef DCPOMATIC_OSX size_t N = sizeof (info.second); sysctlbyname ("hw.ncpu", &info.second, &N, 0, 0); char buffer[64]; @@ -92,7 +92,7 @@ cpu_info () void run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log) { -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS SECURITY_ATTRIBUTES security; security.nLength = sizeof (security); security.bInheritHandle = TRUE; @@ -167,7 +167,7 @@ mount_info () { list<pair<string, string> > m; -#ifdef DVDOMATIC_LINUX +#ifdef DCPOMATIC_LINUX FILE* f = setmntent ("/etc/mtab", "r"); if (!f) { return m; diff --git a/src/lib/cross.h b/src/lib/cross.h index d9cc2d12f..a00fee679 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -17,16 +17,15 @@ */ -#include <string> #include <boost/filesystem.hpp> -class Log; - -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS #define WEXITSTATUS(w) (w) #endif -extern void dvdomatic_sleep (int); +class Log; + +void dcpomatic_sleep (int); extern std::pair<std::string, int> cpu_info (); extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path, boost::shared_ptr<Log>); extern std::list<std::pair<std::string, std::string> > mount_info (); diff --git a/src/lib/dci_metadata.cc b/src/lib/dci_metadata.cc index 758886db4..f25b3ddb0 100644 --- a/src/lib/dci_metadata.cc +++ b/src/lib/dci_metadata.cc @@ -18,26 +18,39 @@ */ #include <iostream> +#include <libcxml/cxml.h> #include "dci_metadata.h" #include "i18n.h" -using namespace std; +using std::string; +using boost::shared_ptr; + +DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node) +{ + audio_language = node->string_child ("AudioLanguage"); + subtitle_language = node->string_child ("SubtitleLanguage"); + territory = node->string_child ("Territory"); + rating = node->string_child ("Rating"); + studio = node->string_child ("Studio"); + facility = node->string_child ("Facility"); + package_type = node->string_child ("PackageType"); +} void -DCIMetadata::write (ostream& f) const +DCIMetadata::as_xml (xmlpp::Node* root) const { - f << N_("audio_language ") << audio_language << N_("\n"); - f << N_("subtitle_language ") << subtitle_language << N_("\n"); - f << N_("territory ") << territory << N_("\n"); - f << N_("rating ") << rating << N_("\n"); - f << N_("studio ") << studio << N_("\n"); - f << N_("facility ") << facility << N_("\n"); - f << N_("package_type ") << package_type << N_("\n"); + root->add_child("AudioLanguage")->add_child_text (audio_language); + root->add_child("SubtitleLanguage")->add_child_text (subtitle_language); + root->add_child("Territory")->add_child_text (territory); + root->add_child("Rating")->add_child_text (rating); + root->add_child("Studio")->add_child_text (studio); + root->add_child("Facility")->add_child_text (facility); + root->add_child("PackageType")->add_child_text (package_type); } void -DCIMetadata::read (string k, string v) +DCIMetadata::read_old_metadata (string k, string v) { if (k == N_("audio_language")) { audio_language = v; diff --git a/src/lib/dci_metadata.h b/src/lib/dci_metadata.h index eecdc7655..b87609ed0 100644 --- a/src/lib/dci_metadata.h +++ b/src/lib/dci_metadata.h @@ -17,16 +17,24 @@ */ -#ifndef DVDOMATIC_DCI_METADATA_H -#define DVDOMATIC_DCI_METADATA_H +#ifndef DCPOMATIC_DCI_METADATA_H +#define DCPOMATIC_DCI_METADATA_H #include <string> +#include <libxml++/libxml++.h> + +namespace cxml { + class Node; +} class DCIMetadata { public: - void read (std::string, std::string); - void write (std::ostream &) const; + DCIMetadata () {} + DCIMetadata (boost::shared_ptr<const cxml::Node>); + + void as_xml (xmlpp::Node *) const; + void read_old_metadata (std::string, std::string); std::string audio_language; std::string subtitle_language; diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h index 960bb0129..14204bd72 100644 --- a/src/lib/dcp_content_type.h +++ b/src/lib/dcp_content_type.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_DCP_CONTENT_TYPE_H -#define DVDOMATIC_DCP_CONTENT_TYPE_H +#ifndef DCPOMATIC_DCP_CONTENT_TYPE_H +#define DCPOMATIC_DCP_CONTENT_TYPE_H /** @file src/content_type.h * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.) diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index 77b81a658..2f597522c 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -47,14 +47,12 @@ #include "dcp_video_frame.h" #include "lut.h" #include "config.h" -#include "options.h" #include "exceptions.h" #include "server.h" #include "util.h" #include "scaler.h" #include "image.h" #include "log.h" -#include "subtitle.h" #include "i18n.h" @@ -67,35 +65,21 @@ using libdcp::Size; /** Construct a DCP video frame. * @param input Input image. - * @param out Required size of output, in pixels (including any padding). - * @param s Scaler to use. - * @param p Number of pixels of padding either side of the image. * @param f Index of the frame within the DCP. - * @param fps Frames per second of the Film's source. - * @param pp FFmpeg post-processing string to use. * @param clut Colour look-up table to use (see Config::colour_lut_index ()) * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ()) * @param l Log to write to. */ DCPVideoFrame::DCPVideoFrame ( - shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub, - Size out, int p, int subtitle_offset, float subtitle_scale, - Scaler const * s, int f, int dcp_fps, string pp, int clut, int bw, shared_ptr<Log> l + shared_ptr<const Image> image, int f, int dcp_fps, int clut, int bw, shared_ptr<Log> l ) - : _input (yuv) - , _subtitle (sub) - , _out_size (out) - , _padding (p) - , _subtitle_offset (subtitle_offset) - , _subtitle_scale (subtitle_scale) - , _scaler (s) + : _image (image) , _frame (f) , _frames_per_second (dcp_fps) - , _post_process (pp) , _colour_lut (clut) , _j2k_bandwidth (bw) , _log (l) - , _image (0) + , _opj_image (0) , _parameters (0) , _cinfo (0) , _cio (0) @@ -110,8 +94,8 @@ DCPVideoFrame::create_openjpeg_container () for (int i = 0; i < 3; ++i) { _cmptparm[i].dx = 1; _cmptparm[i].dy = 1; - _cmptparm[i].w = _out_size.width; - _cmptparm[i].h = _out_size.height; + _cmptparm[i].w = _image->size().width; + _cmptparm[i].h = _image->size().height; _cmptparm[i].x0 = 0; _cmptparm[i].y0 = 0; _cmptparm[i].prec = 12; @@ -119,21 +103,21 @@ DCPVideoFrame::create_openjpeg_container () _cmptparm[i].sgnd = 0; } - _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB); - if (_image == 0) { + _opj_image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB); + if (_opj_image == 0) { throw EncodeError (N_("could not create libopenjpeg image")); } - _image->x0 = 0; - _image->y0 = 0; - _image->x1 = _out_size.width; - _image->y1 = _out_size.height; + _opj_image->x0 = 0; + _opj_image->y0 = 0; + _opj_image->x1 = _image->size().width; + _opj_image->y1 = _image->size().height; } DCPVideoFrame::~DCPVideoFrame () { - if (_image) { - opj_image_destroy (_image); + if (_opj_image) { + opj_image_destroy (_opj_image); } if (_cio) { @@ -157,23 +141,6 @@ DCPVideoFrame::~DCPVideoFrame () shared_ptr<EncodedData> DCPVideoFrame::encode_locally () { - if (!_post_process.empty ()) { - _input = _input->post_process (_post_process, true); - } - - shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true); - - if (_subtitle) { - dvdomatic::Rect tx = subtitle_transformed_area ( - float (_out_size.width) / _input->size().width, - float (_out_size.height) / _input->size().height, - _subtitle->area(), _subtitle_offset, _subtitle_scale - ); - - shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler, true); - prepared->alpha_blend (im, tx.position()); - } - create_openjpeg_container (); struct { @@ -187,9 +154,9 @@ DCPVideoFrame::encode_locally () /* Copy our RGB into the openjpeg container, converting to XYZ in the process */ int jn = 0; - for (int y = 0; y < _out_size.height; ++y) { - uint8_t* p = prepared->data()[0] + y * prepared->stride()[0]; - for (int x = 0; x < _out_size.width; ++x) { + for (int y = 0; y < _image->size().height; ++y) { + uint8_t* p = _image->data()[0] + y * _image->stride()[0]; + for (int x = 0; x < _image->size().width; ++x) { /* In gamma LUT (converting 8-bit input to 12-bit) */ s.r = lut_in[_colour_lut][*p++ << 4]; @@ -215,9 +182,9 @@ DCPVideoFrame::encode_locally () d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); /* Out gamma LUT */ - _image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x]; - _image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y]; - _image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z]; + _opj_image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x]; + _opj_image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y]; + _opj_image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z]; ++jn; } @@ -267,7 +234,7 @@ DCPVideoFrame::encode_locally () _parameters->tcp_numlayers++; _parameters->cp_disto_alloc = 1; _parameters->cp_rsiz = CINEMA2K; - _parameters->cp_comment = strdup (N_("DVD-o-matic")); + _parameters->cp_comment = strdup (N_("DCP-o-matic")); _parameters->cp_cinema = CINEMA2K_24; /* 3 components, so use MCT */ @@ -275,7 +242,7 @@ DCPVideoFrame::encode_locally () /* set max image */ _parameters->max_comp_size = max_comp_size; - _parameters->tcp_rates[0] = ((float) (3 * _image->comps[0].w * _image->comps[0].h * _image->comps[0].prec)) / (max_cs_len * 8); + _parameters->tcp_rates[0] = ((float) (3 * _opj_image->comps[0].w * _opj_image->comps[0].h * _opj_image->comps[0].prec)) / (max_cs_len * 8); /* get a J2K compressor handle */ _cinfo = opj_create_compress (CODEC_J2K); @@ -287,14 +254,14 @@ DCPVideoFrame::encode_locally () _cinfo->event_mgr = 0; /* Setup the encoder parameters using the current image and user parameters */ - opj_setup_encoder (_cinfo, _parameters, _image); + opj_setup_encoder (_cinfo, _parameters, _opj_image); _cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0); if (_cio == 0) { throw EncodeError (N_("could not open JPEG2000 stream")); } - int const r = opj_encode (_cinfo, _cio, _image, 0); + int const r = opj_encode (_cinfo, _cio, _opj_image, 0); if (r == 0) { throw EncodeError (N_("JPEG2000 encoding failed")); } @@ -322,46 +289,24 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv) stringstream s; s << N_("encode please\n") - << N_("input_width ") << _input->size().width << N_("\n") - << N_("input_height ") << _input->size().height << N_("\n") - << N_("input_pixel_format ") << _input->pixel_format() << N_("\n") - << N_("output_width ") << _out_size.width << N_("\n") - << N_("output_height ") << _out_size.height << N_("\n") - << N_("padding ") << _padding << N_("\n") - << N_("subtitle_offset ") << _subtitle_offset << N_("\n") - << N_("subtitle_scale ") << _subtitle_scale << N_("\n") - << N_("scaler ") << _scaler->id () << N_("\n") + << N_("width ") << _image->size().width << N_("\n") + << N_("height ") << _image->size().height << N_("\n") << N_("frame ") << _frame << N_("\n") - << N_("frames_per_second ") << _frames_per_second << N_("\n"); - - if (!_post_process.empty()) { - s << N_("post_process ") << _post_process << N_("\n"); - } - - s << N_("colour_lut ") << _colour_lut << N_("\n") + << N_("frames_per_second ") << _frames_per_second << N_("\n") + << N_("colour_lut ") << _colour_lut << N_("\n") << N_("j2k_bandwidth ") << _j2k_bandwidth << N_("\n"); - if (_subtitle) { - s << N_("subtitle_x ") << _subtitle->position().x << N_("\n") - << N_("subtitle_y ") << _subtitle->position().y << N_("\n") - << N_("subtitle_width ") << _subtitle->image()->size().width << N_("\n") - << N_("subtitle_height ") << _subtitle->image()->size().height << N_("\n"); - } - _log->log (String::compose ( N_("Sending to remote; pixel format %1, components %2, lines (%3,%4,%5), line sizes (%6,%7,%8)"), - _input->pixel_format(), _input->components(), - _input->lines(0), _input->lines(1), _input->lines(2), - _input->line_size()[0], _input->line_size()[1], _input->line_size()[2] + _image->pixel_format(), _image->components(), + _image->lines(0), _image->lines(1), _image->lines(2), + _image->line_size()[0], _image->line_size()[1], _image->line_size()[2] )); socket->write (s.str().length() + 1); socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1); - _input->write_to_socket (socket); - if (_subtitle) { - _subtitle->image()->write_to_socket (socket); - } + _image->write_to_socket (socket); shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ())); socket->read (e->data(), e->size()); diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index 4ceb07d26..f234b445a 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -105,12 +105,8 @@ public: class DCPVideoFrame { public: - DCPVideoFrame ( - boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size, - int, int, float, Scaler const *, int, int, std::string, int, int, boost::shared_ptr<Log> - ); - - virtual ~DCPVideoFrame (); + DCPVideoFrame (boost::shared_ptr<const Image>, int, int, int, int, boost::shared_ptr<Log>); + ~DCPVideoFrame (); boost::shared_ptr<EncodedData> encode_locally (); boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *); @@ -122,23 +118,16 @@ public: private: void create_openjpeg_container (); - boost::shared_ptr<const Image> _input; ///< the input image - boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image - libdcp::Size _out_size; ///< the required size of the output, in pixels - int _padding; - int _subtitle_offset; - float _subtitle_scale; - Scaler const * _scaler; ///< scaler to use + boost::shared_ptr<const Image> _image; int _frame; ///< frame index within the DCP's intrinsic duration int _frames_per_second; ///< Frames per second that we will use for the DCP - std::string _post_process; ///< FFmpeg post-processing string to use int _colour_lut; ///< Colour look-up table to use int _j2k_bandwidth; ///< J2K bandwidth to use boost::shared_ptr<Log> _log; ///< log opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t - opj_image* _image; ///< libopenjpeg's image container + opj_image* _opj_image; ///< libopenjpeg's image container opj_cparameters_t* _parameters; ///< libopenjpeg's parameters opj_cinfo_t* _cinfo; ///< libopenjpeg's opj_cinfo_t opj_cio_t* _cio; ///< libopenjpeg's opj_cio_t diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 52b22fa06..637e0ddb2 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -21,55 +21,18 @@ * @brief Parent class for decoders of content. */ -#include <iostream> -#include <stdint.h> -#include <boost/lexical_cast.hpp> #include "film.h" -#include "format.h" -#include "options.h" -#include "exceptions.h" -#include "image.h" -#include "util.h" -#include "log.h" #include "decoder.h" -#include "delay_line.h" -#include "subtitle.h" -#include "filter_graph.h" #include "i18n.h" -using std::string; -using std::stringstream; -using std::min; -using std::pair; -using std::list; using boost::shared_ptr; -using boost::optional; /** @param f Film. * @param o Decode options. */ -Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o) +Decoder::Decoder (shared_ptr<const Film> f) : _film (f) - , _opt (o) { _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1)); } - -/** Seek to a position as a source timestamp in seconds. - * @return true on error. - */ -bool -Decoder::seek (double) -{ - throw DecodeError (N_("decoder does not support seek")); -} - -/** Seek so that the next frame we will produce is the same as the last one. - * @return true on error. - */ -bool -Decoder::seek_to_last () -{ - throw DecodeError (N_("decoder does not support seek")); -} diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 2bc462c33..cfca6867f 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2013 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 @@ -21,20 +21,15 @@ * @brief Parent class for decoders of content. */ -#ifndef DVDOMATIC_DECODER_H -#define DVDOMATIC_DECODER_H +#ifndef DCPOMATIC_DECODER_H +#define DCPOMATIC_DECODER_H #include <vector> #include <string> #include <stdint.h> #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> -#include "util.h" -#include "stream.h" -#include "video_source.h" -#include "audio_source.h" #include "film.h" -#include "options.h" class Image; class Log; @@ -45,34 +40,30 @@ class FilterGraph; /** @class Decoder. * @brief Parent class for decoders of content. - * - * These classes can be instructed run through their content (by - * calling ::go), and they emit signals when video or audio data is - * ready for something else to process. */ class Decoder { public: - Decoder (boost::shared_ptr<Film>, DecodeOptions); + Decoder (boost::shared_ptr<const Film>); virtual ~Decoder () {} - virtual bool pass () = 0; - virtual bool seek (double); - virtual bool seek_to_last (); - virtual void seek_back () {} - virtual void seek_forward () {} + /** Perform one decode pass of the content, which may or may not + * cause the object to emit some data. + */ + virtual void pass () = 0; - boost::signals2::signal<void()> OutputChanged; + virtual bool done () const = 0; protected: - /** our Film */ - boost::shared_ptr<Film> _film; - /** our decode options */ - DecodeOptions _opt; + + /** The Film that we are decoding in */ + boost::weak_ptr<const Film> _film; private: + /** This will be called when our Film emits Changed */ virtual void film_changed (Film::Property) {} - + + /** Connection to our Film */ boost::signals2::scoped_connection _film_connection; }; diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc deleted file mode 100644 index f7f9f4074..000000000 --- a/src/lib/decoder_factory.cc +++ /dev/null @@ -1,60 +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/decoder_factory.cc - * @brief A method to create an appropriate decoder for some content. - */ - -#include <boost/filesystem.hpp> -#include "ffmpeg_decoder.h" -#include "imagemagick_decoder.h" -#include "film.h" -#include "sndfile_decoder.h" -#include "decoder_factory.h" - -using std::string; -using std::pair; -using std::make_pair; -using boost::shared_ptr; -using boost::dynamic_pointer_cast; - -Decoders -decoder_factory ( - shared_ptr<Film> f, DecodeOptions o - ) -{ - if (f->content().empty()) { - return Decoders (); - } - - if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) { - /* A single image file, or a directory of them */ - return Decoders ( - shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o)), - shared_ptr<AudioDecoder> (new SndfileDecoder (f, o)) - ); - } - - shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o)); - if (f->use_content_audio()) { - return Decoders (fd, fd); - } - - return Decoders (fd, shared_ptr<AudioDecoder> (new SndfileDecoder (f, o))); -} diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h deleted file mode 100644 index 8076b01c7..000000000 --- a/src/lib/decoder_factory.h +++ /dev/null @@ -1,49 +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. - -*/ - -#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. - */ - -#include "options.h" - -class Film; -class VideoDecoder; -class AudioDecoder; - -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>, DecodeOptions - ); - -#endif diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc deleted file mode 100644 index f6af6f9b1..000000000 --- a/src/lib/delay_line.cc +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <stdint.h> -#include <cstring> -#include <algorithm> -#include <iostream> -#include "delay_line.h" -#include "util.h" - -using std::min; -using boost::shared_ptr; - -/* @param seconds Delay in seconds, +ve to move audio later. - */ -DelayLine::DelayLine (shared_ptr<Log> log, double seconds) - : TimedAudioVideoProcessor (log) - , _seconds (seconds) -{ - -} - -void -DelayLine::process_audio (shared_ptr<const AudioBuffers> data, double t) -{ - if (_seconds > 0) { - t += _seconds; - } - - Audio (data, t); -} - -void -DelayLine::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t) -{ - if (_seconds < 0) { - t -= _seconds; - } - - Video (image, same, sub, t); -} diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 0ac32d3bf..d3181acd9 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -22,20 +22,13 @@ */ #include <iostream> -#include <boost/filesystem.hpp> -#include <boost/lexical_cast.hpp> -#include <libdcp/picture_asset.h> #include "encoder.h" #include "util.h" -#include "options.h" #include "film.h" #include "log.h" -#include "exceptions.h" -#include "filter.h" #include "config.h" #include "dcp_video_frame.h" #include "server.h" -#include "format.h" #include "cross.h" #include "writer.h" @@ -49,18 +42,16 @@ using std::list; using std::cout; using std::min; using std::make_pair; -using namespace boost; +using boost::shared_ptr; +using boost::optional; int const Encoder::_history_size = 25; /** @param f Film that we are encoding */ -Encoder::Encoder (shared_ptr<Film> f) +Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<Job> j) : _film (f) - , _video_frames_in (0) + , _job (j) , _video_frames_out (0) -#ifdef HAVE_SWRESAMPLE - , _swr_context (0) -#endif , _have_a_real_frame (false) , _terminate (false) { @@ -78,35 +69,6 @@ Encoder::~Encoder () void Encoder::process_begin () { - if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) { -#ifdef HAVE_SWRESAMPLE - - stringstream s; - s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_stream()->sample_rate(), _film->target_audio_sample_rate()); - _film->log()->log (s.str ()); - - /* We will be using planar float data when we call the resampler */ - _swr_context = swr_alloc_set_opts ( - 0, - _film->audio_stream()->channel_layout(), - AV_SAMPLE_FMT_FLTP, - _film->target_audio_sample_rate(), - _film->audio_stream()->channel_layout(), - AV_SAMPLE_FMT_FLTP, - _film->audio_stream()->sample_rate(), - 0, 0 - ); - - swr_init (_swr_context); -#else - throw EncodeError (_("Cannot resample audio as libswresample is not present")); -#endif - } else { -#ifdef HAVE_SWRESAMPLE - _swr_context = 0; -#endif - } - for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) { _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0))); } @@ -119,51 +81,13 @@ Encoder::process_begin () } } - _writer.reset (new Writer (_film)); + _writer.reset (new Writer (_film, _job)); } void Encoder::process_end () { -#if HAVE_SWRESAMPLE - if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) { - - shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256)); - - while (1) { - int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0); - - if (frames < 0) { - throw EncodeError (_("could not run sample-rate converter")); - } - - if (frames == 0) { - break; - } - - out->set_frames (frames); - write_audio (out); - } - - swr_free (&_swr_context); - } -#endif - - if (_film->audio_channels() == 0 && _film->minimum_audio_channels() > 0) { - /* Put audio in where there is none at all */ - int64_t af = video_frames_to_audio_frames (_video_frames_out, 48000, _film->dcp_frame_rate ()); - while (af) { - int64_t const this_time = min (af, static_cast<int64_t> (24000)); - shared_ptr<AudioBuffers> out (new AudioBuffers (_film->minimum_audio_channels(), this_time)); - out->make_silent (); - out->set_frames (this_time); - write_audio (out); - - af -= this_time; - } - } - boost::mutex::scoped_lock lock (_mutex); _film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ())); @@ -208,7 +132,7 @@ Encoder::process_end () * or 0 if not known. */ float -Encoder::current_frames_per_second () const +Encoder::current_encoding_rate () const { boost::mutex::scoped_lock lock (_history_mutex); if (int (_time_history.size()) < _history_size) { @@ -246,15 +170,8 @@ Encoder::frame_done () } void -Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub) +Encoder::process_video (shared_ptr<const Image> image, bool same) { - FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate()); - - if (frc.skip && (_video_frames_in % 2)) { - ++_video_frames_in; - return; - } - boost::mutex::scoped_lock lock (_mutex); /* Wait until the queue has gone down a bit */ @@ -282,15 +199,12 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ frame_done (); } else { /* Queue this new frame for encoding */ - pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); TIMING ("adding to queue of %1", _queue.size ()); - _queue.push_back (boost::shared_ptr<DCPVideoFrame> ( + /* XXX: padding */ + _queue.push_back (shared_ptr<DCPVideoFrame> ( new DCPVideoFrame ( - image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film), - _film->subtitle_offset(), _film->subtitle_scale(), - _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second, - _film->colour_lut(), _film->j2k_bandwidth(), - _film->log() + image, _video_frames_out, _film->dcp_video_frame_rate(), + _film->colour_lut(), _film->j2k_bandwidth(), _film->log() ) )); @@ -298,49 +212,13 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ _have_a_real_frame = true; } - ++_video_frames_in; ++_video_frames_out; - - if (frc.repeat) { - _writer->repeat (_video_frames_out); - ++_video_frames_out; - frame_done (); - } } void Encoder::process_audio (shared_ptr<const AudioBuffers> data) { - if (!data->frames ()) { - return; - } - -#if HAVE_SWRESAMPLE - /* Maybe sample-rate convert */ - if (_swr_context) { - - /* Compute the resampled frames count and add 32 for luck */ - int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32; - - shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames)); - - /* Resample audio */ - int const resampled_frames = swr_convert ( - _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames() - ); - - if (resampled_frames < 0) { - throw EncodeError (_("could not run sample-rate converter")); - } - - resampled->set_frames (resampled_frames); - - /* And point our variables at the resampled audio */ - data = resampled; - } -#endif - - write_audio (data); + _writer->write (data); } void @@ -383,7 +261,7 @@ Encoder::encoder_thread (ServerDescription* server) } TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); - boost::shared_ptr<DCPVideoFrame> vf = _queue.front (); + shared_ptr<DCPVideoFrame> vf = _queue.front (); _film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 from queue"), boost::this_thread::get_id(), vf->frame()), Log::VERBOSE); _queue.pop_front (); @@ -437,34 +315,10 @@ Encoder::encoder_thread (ServerDescription* server) } if (remote_backoff > 0) { - dvdomatic_sleep (remote_backoff); + dcpomatic_sleep (remote_backoff); } lock.lock (); _condition.notify_all (); } } - -void -Encoder::write_audio (shared_ptr<const AudioBuffers> data) -{ - AudioMapping m (_film); - if (m.dcp_channels() != _film->audio_channels()) { - - /* Remap and pad with silence */ - - shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ())); - for (int i = 0; i < m.dcp_channels(); ++i) { - optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i)); - if (!s) { - b->make_silent (i); - } else { - memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float)); - } - } - - data = b; - } - - _writer->write (data); -} diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 70e81a7e0..b5a641f50 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_ENCODER_H -#define DVDOMATIC_ENCODER_H +#ifndef DCPOMATIC_ENCODER_H +#define DCPOMATIC_ENCODER_H /** @file src/encoder.h * @brief Encoder to J2K and WAV for DCP. @@ -33,68 +33,60 @@ #include <stdint.h> extern "C" { #include <libavutil/samplefmt.h> -} -#ifdef HAVE_SWRESAMPLE -extern "C" { #include <libswresample/swresample.h> } -#endif #include "util.h" -#include "video_sink.h" -#include "audio_sink.h" class Image; -class Subtitle; class AudioBuffers; class Film; class ServerDescription; class DCPVideoFrame; class EncodedData; class Writer; +class Job; /** @class Encoder * @brief Encoder to J2K and WAV for DCP. * - * Video is supplied to process_video as YUV frames, and audio + * Video is supplied to process_video as RGB frames, and audio * is supplied as uncompressed PCM in blocks of various sizes. */ -class Encoder : public VideoSink, public AudioSink +class Encoder { public: - Encoder (boost::shared_ptr<Film> f); + Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<Job>); virtual ~Encoder (); /** Called to indicate that a processing run is about to begin */ - virtual void process_begin (); + void process_begin (); /** Call with a frame of video. * @param i Video frame image. * @param same true if i is the same as the last time we were called. - * @param s A subtitle that should be on this frame, or 0. */ - void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s); + void process_video (boost::shared_ptr<const Image> i, bool same); /** Call with some audio data */ void process_audio (boost::shared_ptr<const AudioBuffers>); /** Called when a processing run has finished */ - virtual void process_end (); + void process_end (); - float current_frames_per_second () const; + float current_encoding_rate () const; int video_frames_out () const; private: void frame_done (); - void write_audio (boost::shared_ptr<const AudioBuffers> data); - void encoder_thread (ServerDescription *); void terminate_threads (); /** Film that we are encoding */ - boost::shared_ptr<Film> _film; + boost::shared_ptr<const Film> _film; + boost::shared_ptr<Job> _job; /** Mutex for _time_history and _last_frame */ mutable boost::mutex _history_mutex; @@ -105,15 +97,9 @@ private: /** Number of frames that we should keep history for */ static int const _history_size; - /** Number of video frames received so far */ - SourceFrame _video_frames_in; /** Number of video frames written for the DCP so far */ int _video_frames_out; -#if HAVE_SWRESAMPLE - SwrContext* _swr_context; -#endif - bool _have_a_real_frame; bool _terminate; std::list<boost::shared_ptr<DCPVideoFrame> > _queue; diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc index 4b30c9431..3cab9716d 100644 --- a/src/lib/examine_content_job.cc +++ b/src/lib/examine_content_job.cc @@ -17,29 +17,20 @@ */ -/** @file src/examine_content_job.cc - * @brief A class to run through content at high speed to find its length. - */ - #include <boost/filesystem.hpp> #include "examine_content_job.h" -#include "options.h" -#include "decoder_factory.h" -#include "decoder.h" -#include "transcoder.h" #include "log.h" +#include "content.h" #include "film.h" -#include "video_decoder.h" #include "i18n.h" using std::string; -using std::vector; -using std::pair; using boost::shared_ptr; -ExamineContentJob::ExamineContentJob (shared_ptr<Film> f) +ExamineContentJob::ExamineContentJob (shared_ptr<const Film> f, shared_ptr<Content> c) : Job (f) + , _content (c) { } @@ -51,60 +42,13 @@ ExamineContentJob::~ExamineContentJob () string ExamineContentJob::name () const { - if (_film->name().empty ()) { - return _("Examine content"); - } - - return String::compose (_("Examine content of %1"), _film->name()); + return _("Examine content"); } void ExamineContentJob::run () { - descend (0.5); - _film->set_content_digest (md5_digest (_film->content_path ())); - ascend (); - - descend (0.5); - - /* 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 ()); - - DecodeOptions o; - o.decode_audio = false; - - Decoders decoders = decoder_factory (_film, o); - - set_progress_unknown (); - while (!decoders.video->pass()) { - /* keep going */ - } - - _film->set_length (decoders.video->video_frame()); - - _film->log()->log (String::compose (N_("Video length examined as %1 frames"), _film->length().get())); - - } else { - - /* Get a quick decoder to get the content's length from its header */ - - Decoders d = decoder_factory (_film, DecodeOptions()); - _film->set_length (d.video->length()); - - _film->log()->log (String::compose (N_("Video length obtained from header as %1 frames"), _film->length().get())); - } - - ascend (); + _content->examine (shared_from_this ()); set_progress (1); set_state (FINISHED_OK); } diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h index 8ee4f0d60..b6903b86b 100644 --- a/src/lib/examine_content_job.h +++ b/src/lib/examine_content_job.h @@ -17,22 +17,22 @@ */ -/** @file src/examine_content_job.h - * @brief A class to obtain the length and MD5 digest of a content file. - */ - +#include <boost/shared_ptr.hpp> #include "job.h" -/** @class ExamineContentJob - * @brief A class to obtain the length and MD5 digest of a content file. - */ +class Content; +class Log; + class ExamineContentJob : public Job { public: - ExamineContentJob (boost::shared_ptr<Film>); + ExamineContentJob (boost::shared_ptr<const Film>, boost::shared_ptr<Content>); ~ExamineContentJob (); std::string name () const; void run (); + +private: + boost::shared_ptr<Content> _content; }; diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index e45a62353..6bad7c924 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_EXCEPTIONS_H -#define DVDOMATIC_EXCEPTIONS_H +#ifndef DCPOMATIC_EXCEPTIONS_H +#define DCPOMATIC_EXCEPTIONS_H /** @file src/exceptions.h * @brief Our exceptions. @@ -112,6 +112,7 @@ class OpenFileError : public FileError { public: /** @param f File that we were trying to open */ + /* XXX: should be boost::filesystem::path */ OpenFileError (std::string f); }; diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc new file mode 100644 index 000000000..a39de391a --- /dev/null +++ b/src/lib/ffmpeg.cc @@ -0,0 +1,147 @@ +/* + Copyright (C) 2013 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. + +*/ + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libswscale/swscale.h> +#include <libpostproc/postprocess.h> +} +#include "ffmpeg.h" +#include "ffmpeg_content.h" +#include "exceptions.h" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using boost::shared_ptr; + +boost::mutex FFmpeg::_mutex; + +FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c) + : _ffmpeg_content (c) + , _format_context (0) + , _frame (0) + , _video_stream (-1) +{ + setup_general (); + setup_video (); + setup_audio (); +} + +FFmpeg::~FFmpeg () +{ + boost::mutex::scoped_lock lm (_mutex); + + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { + AVCodecContext* context = _format_context->streams[i]->codec; + if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) { + avcodec_close (context); + } + } + + av_free (_frame); + + avformat_close_input (&_format_context); +} + +void +FFmpeg::setup_general () +{ + av_register_all (); + + if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) { + throw OpenFileError (_ffmpeg_content->file().string ()); + } + + if (avformat_find_stream_info (_format_context, 0) < 0) { + throw DecodeError (_("could not find stream information")); + } + + /* Find video stream */ + + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { + AVStream* s = _format_context->streams[i]; + if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + _video_stream = i; + } + } + + if (_video_stream < 0) { + throw DecodeError (N_("could not find video stream")); + } + + _frame = avcodec_alloc_frame (); + if (_frame == 0) { + throw DecodeError (N_("could not allocate frame")); + } +} + +void +FFmpeg::setup_video () +{ + boost::mutex::scoped_lock lm (_mutex); + + AVCodecContext* context = _format_context->streams[_video_stream]->codec; + AVCodec* codec = avcodec_find_decoder (context->codec_id); + + if (codec == 0) { + throw DecodeError (_("could not find video decoder")); + } + + if (avcodec_open2 (context, codec, 0) < 0) { + throw DecodeError (N_("could not open video decoder")); + } +} + +void +FFmpeg::setup_audio () +{ + boost::mutex::scoped_lock lm (_mutex); + + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { + AVCodecContext* context = _format_context->streams[i]->codec; + if (context->codec_type != AVMEDIA_TYPE_AUDIO) { + continue; + } + + AVCodec* codec = avcodec_find_decoder (context->codec_id); + if (codec == 0) { + throw DecodeError (_("could not find audio decoder")); + } + + if (avcodec_open2 (context, codec, 0) < 0) { + throw DecodeError (N_("could not open audio decoder")); + } + } +} + + +AVCodecContext * +FFmpeg::video_codec_context () const +{ + return _format_context->streams[_video_stream]->codec; +} + +AVCodecContext * +FFmpeg::audio_codec_context () const +{ + return _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec; +} diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h new file mode 100644 index 000000000..4d1a45da3 --- /dev/null +++ b/src/lib/ffmpeg.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_FFMPEG_H +#define DCPOMATIC_FFMPEG_H + +#include <vector> +#include <boost/shared_ptr.hpp> +#include <boost/thread/mutex.hpp> +extern "C" { +#include <libavcodec/avcodec.h> +} + +struct AVFilterGraph; +struct AVCodecContext; +struct AVFilterContext; +struct AVFormatContext; +struct AVFrame; +struct AVBufferContext; +struct AVCodec; +struct AVStream; + +class FFmpegContent; + +class FFmpeg +{ +public: + FFmpeg (boost::shared_ptr<const FFmpegContent>); + virtual ~FFmpeg (); + + boost::shared_ptr<const FFmpegContent> ffmpeg_content () const { + return _ffmpeg_content; + } + +protected: + AVCodecContext* video_codec_context () const; + AVCodecContext* audio_codec_context () const; + + boost::shared_ptr<const FFmpegContent> _ffmpeg_content; + + AVFormatContext* _format_context; + AVPacket _packet; + AVFrame* _frame; + + int _video_stream; + + /* It would appear (though not completely verified) that one must have + a mutex around calls to avcodec_open* and avcodec_close... and here + it is. + */ + static boost::mutex _mutex; + +private: + void setup_general (); + void setup_video (); + void setup_audio (); +}; + +#endif diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc new file mode 100644 index 000000000..1135cc9a3 --- /dev/null +++ b/src/lib/ffmpeg_content.cc @@ -0,0 +1,376 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libcxml/cxml.h> +#include "ffmpeg_content.h" +#include "ffmpeg_examiner.h" +#include "compose.hpp" +#include "job.h" +#include "util.h" +#include "filter.h" +#include "film.h" +#include "log.h" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using std::vector; +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::lexical_cast; + +int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; +int const FFmpegContentProperty::SUBTITLE_STREAM = 101; +int const FFmpegContentProperty::AUDIO_STREAMS = 102; +int const FFmpegContentProperty::AUDIO_STREAM = 103; +int const FFmpegContentProperty::FILTERS = 104; + +FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , VideoContent (f, p) + , AudioContent (f, p) +{ + +} + +FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + : Content (f, node) + , VideoContent (f, node) + , AudioContent (f, node) +{ + list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream"); + for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) { + _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i))); + if ((*i)->optional_number_child<int> ("Selected")) { + _subtitle_stream = _subtitle_streams.back (); + } + } + + c = node->node_children ("AudioStream"); + for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) { + _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i))); + if ((*i)->optional_number_child<int> ("Selected")) { + _audio_stream = _audio_streams.back (); + } + } + + c = node->node_children ("Filter"); + for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) { + _filters.push_back (Filter::from_id ((*i)->content ())); + } + + _first_video = node->optional_number_child<Time> ("FirstVideo"); +} + +FFmpegContent::FFmpegContent (FFmpegContent const & o) + : Content (o) + , VideoContent (o) + , AudioContent (o) + , _subtitle_streams (o._subtitle_streams) + , _subtitle_stream (o._subtitle_stream) + , _audio_streams (o._audio_streams) + , _audio_stream (o._audio_stream) +{ + +} + +void +FFmpegContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("FFmpeg"); + Content::as_xml (node); + VideoContent::as_xml (node); + AudioContent::as_xml (node); + + boost::mutex::scoped_lock lm (_mutex); + + for (vector<shared_ptr<FFmpegSubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { + xmlpp::Node* t = node->add_child("SubtitleStream"); + if (_subtitle_stream && *i == _subtitle_stream) { + t->add_child("Selected")->add_child_text("1"); + } + (*i)->as_xml (t); + } + + for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) { + xmlpp::Node* t = node->add_child("AudioStream"); + if (_audio_stream && *i == _audio_stream) { + t->add_child("Selected")->add_child_text("1"); + } + (*i)->as_xml (t); + } + + for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { + node->add_child("Filter")->add_child_text ((*i)->id ()); + } + + if (_first_video) { + node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ())); + } +} + +void +FFmpegContent::examine (shared_ptr<Job> job) +{ + job->set_progress_unknown (); + + Content::examine (job); + + shared_ptr<const Film> film = _film.lock (); + assert (film); + + shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ())); + + VideoContent::Frame video_length = 0; + video_length = examiner->video_length (); + film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length)); + + { + boost::mutex::scoped_lock lm (_mutex); + + _video_length = video_length; + + _subtitle_streams = examiner->subtitle_streams (); + if (!_subtitle_streams.empty ()) { + _subtitle_stream = _subtitle_streams.front (); + } + + _audio_streams = examiner->audio_streams (); + if (!_audio_streams.empty ()) { + _audio_stream = _audio_streams.front (); + } + + _first_video = examiner->first_video (); + } + + take_from_video_examiner (examiner); + + signal_changed (ContentProperty::LENGTH); + signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS); + signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); + signal_changed (FFmpegContentProperty::AUDIO_STREAMS); + signal_changed (FFmpegContentProperty::AUDIO_STREAM); + signal_changed (AudioContentProperty::AUDIO_CHANNELS); +} + +string +FFmpegContent::summary () const +{ + return String::compose (_("Movie: %1"), file().filename().string()); +} + +string +FFmpegContent::information () const +{ + if (video_length() == 0 || video_frame_rate() == 0) { + return ""; + } + + stringstream s; + + s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n"; + s << VideoContent::information (); + + return s.str (); +} + +void +FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _subtitle_stream = s; + } + + signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); +} + +void +FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_stream = s; + } + + signal_changed (FFmpegContentProperty::AUDIO_STREAM); +} + +AudioContent::Frame +FFmpegContent::audio_length () const +{ + int const cafr = content_audio_frame_rate (); + int const vfr = video_frame_rate (); + VideoContent::Frame const vl = video_length (); + + boost::mutex::scoped_lock lm (_mutex); + if (!_audio_stream) { + return 0; + } + + return video_frames_to_audio_frames (vl, cafr, vfr); +} + +int +FFmpegContent::audio_channels () const +{ + boost::mutex::scoped_lock lm (_mutex); + + if (!_audio_stream) { + return 0; + } + + return _audio_stream->channels; +} + +int +FFmpegContent::content_audio_frame_rate () const +{ + boost::mutex::scoped_lock lm (_mutex); + + if (!_audio_stream) { + return 0; + } + + return _audio_stream->frame_rate; +} + +int +FFmpegContent::output_audio_frame_rate () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + + /* Resample to a DCI-approved sample rate */ + double t = dcp_audio_frame_rate (content_audio_frame_rate ()); + + FrameRateConversion frc (video_frame_rate(), film->dcp_video_frame_rate()); + + /* Compensate if the DCP is being run at a different frame rate + to the source; that is, if the video is run such that it will + look different in the DCP compared to the source (slower or faster). + skip/repeat doesn't come into effect here. + */ + + if (frc.change_speed) { + t *= video_frame_rate() * frc.factor() / film->dcp_video_frame_rate(); + } + + return rint (t); +} + +bool +operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b) +{ + return a.id == b.id; +} + +bool +operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b) +{ + return a.id == b.id; +} + +FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node) +{ + name = node->string_child ("Name"); + id = node->number_child<int> ("Id"); + frame_rate = node->number_child<int> ("FrameRate"); + channels = node->number_child<int64_t> ("Channels"); + mapping = AudioMapping (node->node_child ("Mapping")); + first_audio = node->optional_number_child<Time> ("FirstAudio"); +} + +void +FFmpegAudioStream::as_xml (xmlpp::Node* root) const +{ + root->add_child("Name")->add_child_text (name); + root->add_child("Id")->add_child_text (lexical_cast<string> (id)); + root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate)); + root->add_child("Channels")->add_child_text (lexical_cast<string> (channels)); + if (first_audio) { + root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio)); + } + mapping.as_xml (root->add_child("Mapping")); +} + +/** Construct a SubtitleStream from a value returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + */ +FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node) +{ + name = node->string_child ("Name"); + id = node->number_child<int> ("Id"); +} + +void +FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const +{ + root->add_child("Name")->add_child_text (name); + root->add_child("Id")->add_child_text (lexical_cast<string> (id)); +} + +shared_ptr<Content> +FFmpegContent::clone () const +{ + return shared_ptr<Content> (new FFmpegContent (*this)); +} + +Time +FFmpegContent::length () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + + FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ()); + return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate (); +} + +AudioMapping +FFmpegContent::audio_mapping () const +{ + boost::mutex::scoped_lock lm (_mutex); + + if (!_audio_stream) { + return AudioMapping (); + } + + return _audio_stream->mapping; +} + +void +FFmpegContent::set_filters (vector<Filter const *> const & filters) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _filters = filters; + } + + signal_changed (FFmpegContentProperty::FILTERS); +} + +void +FFmpegContent::set_audio_mapping (AudioMapping m) +{ + audio_stream()->mapping = m; + signal_changed (AudioContentProperty::AUDIO_MAPPING); +} diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h new file mode 100644 index 000000000..c5ccee77a --- /dev/null +++ b/src/lib/ffmpeg_content.h @@ -0,0 +1,153 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_FFMPEG_CONTENT_H +#define DCPOMATIC_FFMPEG_CONTENT_H + +#include <boost/enable_shared_from_this.hpp> +#include "video_content.h" +#include "audio_content.h" + +class Filter; + +class FFmpegAudioStream +{ +public: + FFmpegAudioStream (std::string n, int i, int f, int c) + : name (n) + , id (i) + , frame_rate (f) + , channels (c) + , mapping (c) + {} + + FFmpegAudioStream (boost::shared_ptr<const cxml::Node>); + + void as_xml (xmlpp::Node *) const; + + std::string name; + int id; + int frame_rate; + int channels; + AudioMapping mapping; + boost::optional<double> first_audio; +}; + +extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b); + +class FFmpegSubtitleStream +{ +public: + FFmpegSubtitleStream (std::string n, int i) + : name (n) + , id (i) + {} + + FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>); + + void as_xml (xmlpp::Node *) const; + + std::string name; + int id; +}; + +extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b); + +class FFmpegContentProperty : public VideoContentProperty +{ +public: + static int const SUBTITLE_STREAMS; + static int const SUBTITLE_STREAM; + static int const AUDIO_STREAMS; + static int const AUDIO_STREAM; + static int const FILTERS; +}; + +class FFmpegContent : public VideoContent, public AudioContent +{ +public: + FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path); + FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + FFmpegContent (FFmpegContent const &); + + boost::shared_ptr<FFmpegContent> shared_from_this () { + return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ()); + } + + void examine (boost::shared_ptr<Job>); + std::string summary () const; + std::string information () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr<Content> clone () const; + Time length () const; + + /* AudioContent */ + int audio_channels () const; + AudioContent::Frame audio_length () const; + int content_audio_frame_rate () const; + int output_audio_frame_rate () const; + AudioMapping audio_mapping () const; + void set_audio_mapping (AudioMapping); + + void set_filters (std::vector<Filter const *> const &); + + std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const { + boost::mutex::scoped_lock lm (_mutex); + return _subtitle_streams; + } + + boost::shared_ptr<FFmpegSubtitleStream> subtitle_stream () const { + boost::mutex::scoped_lock lm (_mutex); + return _subtitle_stream; + } + + std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_streams; + } + + boost::shared_ptr<FFmpegAudioStream> audio_stream () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_stream; + } + + std::vector<Filter const *> filters () const { + boost::mutex::scoped_lock lm (_mutex); + return _filters; + } + + void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>); + void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>); + + boost::optional<Time> first_video () const { + boost::mutex::scoped_lock lm (_mutex); + return _first_video; + } + +private: + std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams; + boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream; + std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; + boost::shared_ptr<FFmpegAudioStream> _audio_stream; + boost::optional<Time> _first_video; + /** Video filters that should be used when generating DCPs */ + std::vector<Filter const *> _filters; +}; + +#endif diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index c2143b949..bf0949130 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -28,19 +28,13 @@ #include <iostream> #include <stdint.h> #include <boost/lexical_cast.hpp> +#include <sndfile.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> -#include <libswscale/swscale.h> -#include <libpostproc/postprocess.h> } -#include <sndfile.h> #include "film.h" -#include "format.h" -#include "transcoder.h" -#include "job.h" #include "filter.h" -#include "options.h" #include "exceptions.h" #include "image.h" #include "util.h" @@ -48,6 +42,7 @@ extern "C" { #include "ffmpeg_decoder.h" #include "filter_graph.h" #include "subtitle.h" +#include "audio_buffers.h" #include "i18n.h" @@ -56,182 +51,66 @@ using std::string; using std::vector; using std::stringstream; using std::list; +using std::min; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; using libdcp::Size; -boost::mutex FFmpegDecoder::_mutex; - -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) - , VideoDecoder (f, o) - , AudioDecoder (f, o) - , _format_context (0) - , _video_stream (-1) - , _frame (0) - , _video_codec_context (0) - , _video_codec (0) - , _audio_codec_context (0) - , _audio_codec (0) +FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio) + : Decoder (f) + , VideoDecoder (f) + , AudioDecoder (f) + , FFmpeg (c) , _subtitle_codec_context (0) , _subtitle_codec (0) + , _decode_video (video) + , _decode_audio (audio) + , _pts_offset (0) { - setup_general (); - setup_video (); - setup_audio (); setup_subtitle (); -} - -FFmpegDecoder::~FFmpegDecoder () -{ - boost::mutex::scoped_lock lm (_mutex); - - if (_audio_codec_context) { - avcodec_close (_audio_codec_context); - } - - if (_video_codec_context) { - avcodec_close (_video_codec_context); - } - - if (_subtitle_codec_context) { - avcodec_close (_subtitle_codec_context); - } - - av_free (_frame); - - avformat_close_input (&_format_context); -} - -void -FFmpegDecoder::setup_general () -{ - av_register_all (); - - if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) { - throw OpenFileError (_film->content_path ()); - } - - if (avformat_find_stream_info (_format_context, 0) < 0) { - throw DecodeError (_("could not find stream information")); - } - - /* Find video, audio and subtitle streams and choose the first of each */ - - for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { - AVStream* s = _format_context->streams[i]; - if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) { - _video_stream = i; - } else if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - - /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up, - so bodge it here. No idea why we should have to do this. - */ - - if (s->codec->channel_layout == 0) { - s->codec->channel_layout = av_get_default_channel_layout (s->codec->channels); - } - - _audio_streams.push_back ( - shared_ptr<AudioStream> ( - new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout) - ) - ); - - } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { - _subtitle_streams.push_back ( - shared_ptr<SubtitleStream> ( - new SubtitleStream (stream_name (s), i) - ) - ); - } - } - - if (_video_stream < 0) { - throw DecodeError (N_("could not find video stream")); - } - _frame = avcodec_alloc_frame (); - if (_frame == 0) { - throw DecodeError (N_("could not allocate frame")); + if (video && audio && c->audio_stream() && c->first_video() && c->audio_stream()->first_audio) { + _pts_offset = compute_pts_offset (c->first_video().get(), c->audio_stream()->first_audio.get(), c->video_frame_rate()); } } -void -FFmpegDecoder::setup_video () +double +FFmpegDecoder::compute_pts_offset (double first_video, double first_audio, float video_frame_rate) { - boost::mutex::scoped_lock lm (_mutex); + double const old_first_video = first_video; - _video_codec_context = _format_context->streams[_video_stream]->codec; - _video_codec = avcodec_find_decoder (_video_codec_context->codec_id); - - if (_video_codec == 0) { - throw DecodeError (_("could not find video decoder")); + /* Round the first video to a frame boundary */ + if (fabs (rint (first_video * video_frame_rate) - first_video * video_frame_rate) > 1e-6) { + first_video = ceil (first_video * video_frame_rate) / video_frame_rate; } - if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) { - throw DecodeError (N_("could not open video decoder")); - } + /* Compute the required offset (also removing any common start delay) */ + return first_video - old_first_video - min (first_video, first_audio); } -void -FFmpegDecoder::setup_audio () +FFmpegDecoder::~FFmpegDecoder () { boost::mutex::scoped_lock lm (_mutex); - - if (!_audio_stream) { - return; - } - - shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); - assert (ffa); - - _audio_codec_context = _format_context->streams[ffa->id()]->codec; - _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id); - if (_audio_codec == 0) { - throw DecodeError (_("could not find audio decoder")); - } - - if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) { - throw DecodeError (N_("could not open audio decoder")); + if (_subtitle_codec_context) { + avcodec_close (_subtitle_codec_context); } -} +} void -FFmpegDecoder::setup_subtitle () -{ - boost::mutex::scoped_lock lm (_mutex); - - if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) { - return; - } - - _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec; - _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); - - if (_subtitle_codec == 0) { - throw DecodeError (_("could not find subtitle decoder")); - } - - if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) { - throw DecodeError (N_("could not open subtitle decoder")); - } -} - - -bool FFmpegDecoder::pass () { int r = av_read_frame (_format_context, &_packet); - + if (r < 0) { if (r != AVERROR_EOF) { /* Maybe we should fail here, but for now we'll just finish off instead */ char buf[256]; av_strerror (r, buf, sizeof(buf)); - _film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); + shared_ptr<const Film> film = _film.lock (); + assert (film); + film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); } /* Get any remaining frames */ @@ -241,41 +120,28 @@ FFmpegDecoder::pass () /* XXX: should we reset _packet.data and size after each *_decode_* call? */ - int frame_finished; - - if (_opt.decode_video) { - while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - filter_and_emit_video (); - } + if (_decode_video) { + while (decode_video_packet ()); } - - if (_audio_stream && _opt.decode_audio) { + + if (_ffmpeg_content->audio_stream() && _decode_audio) { decode_audio_packet (); } - - return true; + + /* Stop us being asked for any more data */ + _video_position = _ffmpeg_content->video_length (); + _audio_position = _ffmpeg_content->audio_length (); + return; } avcodec_get_frame_defaults (_frame); - shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); - - if (_packet.stream_index == _video_stream && _opt.decode_video) { - - int frame_finished; - int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet); - if (r >= 0 && frame_finished) { - - if (r != _packet.size) { - _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size)); - } - - filter_and_emit_video (); - } - - } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) { + if (_packet.stream_index == _video_stream && _decode_video) { + decode_video_packet (); + } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) { decode_audio_packet (); - } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) { + } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id) { +#if 0 int got_subtitle; AVSubtitle sub; @@ -286,19 +152,19 @@ FFmpegDecoder::pass () if (sub.num_rects > 0) { shared_ptr<TimedSubtitle> ts; try { - emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub))); + subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub))); } catch (...) { /* some problem with the subtitle; we probably didn't understand it */ } } else { - emit_subtitle (shared_ptr<TimedSubtitle> ()); + subtitle (shared_ptr<TimedSubtitle> ()); } avsubtitle_free (&sub); } +#endif } - + av_free_packet (&_packet); - return false; } /** @param data pointer to array of pointers to buffers. @@ -307,19 +173,16 @@ FFmpegDecoder::pass () shared_ptr<AudioBuffers> FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) { - assert (_film->audio_channels()); + assert (_ffmpeg_content->audio_channels()); assert (bytes_per_audio_sample()); - shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); - assert (ffa); - /* Deinterleave and convert to float */ - assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0); + assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0); int const total_samples = size / bytes_per_audio_sample(); - int const frames = total_samples / _film->audio_channels(); - shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames)); + int const frames = total_samples / _ffmpeg_content->audio_channels(); + shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames)); switch (audio_sample_format()) { case AV_SAMPLE_FMT_S16: @@ -331,7 +194,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) audio->data(channel)[sample] = float(*p++) / (1 << 15); ++channel; - if (channel == _film->audio_channels()) { + if (channel == _ffmpeg_content->audio_channels()) { channel = 0; ++sample; } @@ -342,7 +205,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) case AV_SAMPLE_FMT_S16P: { int16_t** p = reinterpret_cast<int16_t **> (data); - for (int i = 0; i < _film->audio_channels(); ++i) { + for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) { for (int j = 0; j < frames; ++j) { audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15); } @@ -359,7 +222,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31); ++channel; - if (channel == _film->audio_channels()) { + if (channel == _ffmpeg_content->audio_channels()) { channel = 0; ++sample; } @@ -376,7 +239,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) audio->data(channel)[sample] = *p++; ++channel; - if (channel == _film->audio_channels()) { + if (channel == _ffmpeg_content->audio_channels()) { channel = 0; ++sample; } @@ -387,7 +250,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) case AV_SAMPLE_FMT_FLTP: { float** p = reinterpret_cast<float**> (data); - for (int i = 0; i < _film->audio_channels(); ++i) { + for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) { memcpy (audio->data(i), p[i], frames * sizeof(float)); } } @@ -400,89 +263,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) return audio; } -float -FFmpegDecoder::frames_per_second () const -{ - AVStream* s = _format_context->streams[_video_stream]; - - if (s->avg_frame_rate.num && s->avg_frame_rate.den) { - return av_q2d (s->avg_frame_rate); - } - - return av_q2d (s->r_frame_rate); -} - AVSampleFormat FFmpegDecoder::audio_sample_format () const { - if (_audio_codec_context == 0) { + if (!_ffmpeg_content->audio_stream()) { return (AVSampleFormat) 0; } - return _audio_codec_context->sample_fmt; -} - -libdcp::Size -FFmpegDecoder::native_size () const -{ - return libdcp::Size (_video_codec_context->width, _video_codec_context->height); -} - -PixelFormat -FFmpegDecoder::pixel_format () const -{ - return _video_codec_context->pix_fmt; -} - -int -FFmpegDecoder::time_base_numerator () const -{ - return _video_codec_context->time_base.num; -} - -int -FFmpegDecoder::time_base_denominator () const -{ - return _video_codec_context->time_base.den; -} - -int -FFmpegDecoder::sample_aspect_ratio_numerator () const -{ - return _video_codec_context->sample_aspect_ratio.num; -} - -int -FFmpegDecoder::sample_aspect_ratio_denominator () const -{ - return _video_codec_context->sample_aspect_ratio.den; -} - -string -FFmpegDecoder::stream_name (AVStream* s) const -{ - stringstream n; - - if (s->metadata) { - AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0); - if (lang) { - n << lang->value; - } - - AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0); - if (title) { - if (!n.str().empty()) { - n << N_(" "); - } - n << title->value; - } - } - - if (n.str().empty()) { - n << N_("unknown"); - } - - return n.str (); + return audio_codec_context()->sample_fmt; } int @@ -492,91 +280,29 @@ FFmpegDecoder::bytes_per_audio_sample () const } void -FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s) +FFmpegDecoder::seek (VideoContent::Frame frame) { - AudioDecoder::set_audio_stream (s); - setup_audio (); + do_seek (frame, false, false); } void -FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) -{ - VideoDecoder::set_subtitle_stream (s); - setup_subtitle (); - OutputChanged (); -} - -void -FFmpegDecoder::filter_and_emit_video () +FFmpegDecoder::seek_back () { - int64_t const bet = av_frame_get_best_effort_timestamp (_frame); - if (bet == AV_NOPTS_VALUE) { - _film->log()->log ("Dropping frame without PTS"); + if (_video_position == 0) { return; } - shared_ptr<FilterGraph> graph; - - { - boost::mutex::scoped_lock lm (_filter_graphs_mutex); - - list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); - while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { - ++i; - } - - if (i == _filter_graphs.end ()) { - graph = filter_graph_factory (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format); - _filter_graphs.push_back (graph); - _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); - } else { - graph = *i; - } - } - - list<shared_ptr<Image> > images = graph->process (_frame); - - for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { - emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base)); - } -} - -bool -FFmpegDecoder::seek (double p) -{ - return do_seek (p, false, false); -} - -bool -FFmpegDecoder::seek_to_last () -{ - /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time - (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than - staying in the same place. - */ - return do_seek (last_source_time(), true, false); -} - -void -FFmpegDecoder::seek_back () -{ - do_seek (last_source_time() - 2.5 / frames_per_second (), true, true); + do_seek (_video_position - 1, true, true); } void -FFmpegDecoder::seek_forward () -{ - do_seek (last_source_time() - 0.5 / frames_per_second(), true, true); -} - -bool -FFmpegDecoder::do_seek (double p, bool backwards, bool accurate) +FFmpegDecoder::do_seek (VideoContent::Frame frame, bool backwards, bool accurate) { - int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base); + int64_t const vt = frame * _ffmpeg_content->video_frame_rate() / av_q2d (_format_context->streams[_video_stream]->time_base); + av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0); + _video_position = frame; - int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0); - - avcodec_flush_buffers (_video_codec_context); + avcodec_flush_buffers (video_codec_context()); if (_subtitle_codec_context) { avcodec_flush_buffers (_subtitle_codec_context); } @@ -585,17 +311,19 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate) while (1) { int r = av_read_frame (_format_context, &_packet); if (r < 0) { - return true; + return; } avcodec_get_frame_defaults (_frame); if (_packet.stream_index == _video_stream) { int finished = 0; - int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet); + int const r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); if (r >= 0 && finished) { int64_t const bet = av_frame_get_best_effort_timestamp (_frame); if (bet > vt) { + _video_position = (bet * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset) + * _ffmpeg_content->video_frame_rate(); break; } } @@ -604,123 +332,156 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate) av_free_packet (&_packet); } } - - return r < 0; } -shared_ptr<FFmpegAudioStream> -FFmpegAudioStream::create (string t, optional<int> v) +void +FFmpegDecoder::decode_audio_packet () { - if (!v) { - /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ - return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v)); - } + /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4 + several times. + */ + + AVPacket copy_packet = _packet; - stringstream s (t); - string type; - s >> type; - if (type != N_("ffmpeg")) { - return shared_ptr<FFmpegAudioStream> (); - } + while (copy_packet.size > 0) { - return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v)); + int frame_finished; + int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet); + if (decode_result >= 0) { + if (frame_finished) { + + if (_audio_position == 0) { + /* Where we are in the source, in seconds */ + double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) + * av_frame_get_best_effort_timestamp(_frame) - _pts_offset; + + if (pts > 0) { + /* Emit some silence */ + shared_ptr<AudioBuffers> silence ( + new AudioBuffers ( + _ffmpeg_content->audio_channels(), + pts * _ffmpeg_content->content_audio_frame_rate() + ) + ); + + silence->make_silent (); + audio (silence, _audio_position); + } + } + + copy_packet.data += decode_result; + copy_packet.size -= decode_result; + } + } + } } -FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version) +bool +FFmpegDecoder::decode_video_packet () { - stringstream n (t); - - int name_index = 4; - if (!version) { - name_index = 2; - int channels; - n >> _id >> channels; - _channel_layout = av_get_default_channel_layout (channels); - _sample_rate = 0; - } else { - string type; - /* Current (marked version 1) */ - n >> type >> _id >> _sample_rate >> _channel_layout; - assert (type == N_("ffmpeg")); + int frame_finished; + if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, &_packet) < 0 || !frame_finished) { + return false; } + + boost::mutex::scoped_lock lm (_filter_graphs_mutex); - for (int i = 0; i < name_index; ++i) { - size_t const s = t.find (' '); - if (s != string::npos) { - t = t.substr (s + 1); - } + shared_ptr<FilterGraph> graph; + + list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); + while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { + ++i; } - _name = t; -} + if (i == _filter_graphs.end ()) { + shared_ptr<const Film> film = _film.lock (); + assert (film); -string -FFmpegAudioStream::to_string () const -{ - return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name); -} + graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); + _filter_graphs.push_back (graph); -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 (); + film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); + } else { + graph = *i; } - OutputChanged (); - break; - default: - break; + list<shared_ptr<Image> > images = graph->process (_frame); + + string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second; + + for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { + + shared_ptr<Image> image = *i; + if (!post_process.empty ()) { + image = image->post_process (post_process, true); + } + + int64_t const bet = av_frame_get_best_effort_timestamp (_frame); + if (bet != AV_NOPTS_VALUE) { + + double const pts = bet * av_q2d (_format_context->streams[_video_stream]->time_base) - _pts_offset; + double const next = _video_position / _ffmpeg_content->video_frame_rate(); + double const one_frame = 1 / _ffmpeg_content->video_frame_rate (); + double delta = pts - next; + + while (delta > one_frame) { + /* This PTS is more than one frame forward in time of where we think we should be; emit + a black frame. + */ + boost::shared_ptr<Image> black ( + new SimpleImage ( + static_cast<AVPixelFormat> (_frame->format), + libdcp::Size (video_codec_context()->width, video_codec_context()->height), + true + ) + ); + + black->make_black (); + video (image, false, _video_position); + delta -= one_frame; + } + + if (delta > -one_frame) { + /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */ + video (image, false, _video_position); + } + } else { + shared_ptr<const Film> film = _film.lock (); + assert (film); + film->log()->log ("Dropping frame without PTS"); + } } -} -/** @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(); + return true; } + void -FFmpegDecoder::decode_audio_packet () +FFmpegDecoder::setup_subtitle () { - shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); - assert (ffa); - - /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4 - several times. - */ + boost::mutex::scoped_lock lm (_mutex); - AVPacket copy_packet = _packet; + if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) { + return; + } - while (copy_packet.size > 0) { + _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec; + _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); - int frame_finished; - int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, ©_packet); - if (decode_result < 0) { - /* error */ - break; - } - - if (frame_finished) { - - /* Where we are in the source, in seconds */ - double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame); - - int const data_size = av_samples_get_buffer_size ( - 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1 - ); - - assert (_audio_codec_context->channels == _film->audio_channels()); - Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds); - } - - copy_packet.data += decode_result; - copy_packet.size -= decode_result; + if (_subtitle_codec == 0) { + throw DecodeError (_("could not find subtitle decoder")); + } + + if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) { + throw DecodeError (N_("could not open subtitle decoder")); } } + +bool +FFmpegDecoder::done () const +{ + bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length()); + bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length()); + return vd && ad; +} + diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 198f4294e..8f0482aad 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -35,119 +35,54 @@ extern "C" { #include "decoder.h" #include "video_decoder.h" #include "audio_decoder.h" -#include "film.h" - -struct AVFilterGraph; -struct AVCodecContext; -struct AVFilterContext; -struct AVFormatContext; -struct AVFrame; -struct AVBufferContext; -struct AVCodec; -struct AVStream; -class Job; -class Options; -class Image; -class Log; - -class FFmpegAudioStream : public AudioStream -{ -public: - FFmpegAudioStream (std::string n, int i, int s, int64_t c) - : AudioStream (s, c) - , _name (n) - , _id (i) - {} - - std::string to_string () const; - - std::string name () const { - return _name; - } +#include "ffmpeg.h" - int id () const { - return _id; - } - - static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v); - -private: - friend class stream_test; - - FFmpegAudioStream (std::string t, boost::optional<int> v); - - std::string _name; - int _id; -}; +class Film; +class ffmpeg_pts_offset_test; /** @class FFmpegDecoder * @brief A decoder using FFmpeg to decode content. */ -class FFmpegDecoder : public VideoDecoder, public AudioDecoder +class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public FFmpeg { public: - FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions); + FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio); ~FFmpegDecoder (); - float frames_per_second () const; - libdcp::Size native_size () const; - SourceFrame length () const; - int time_base_numerator () const; - int time_base_denominator () const; - int sample_aspect_ratio_numerator () const; - int sample_aspect_ratio_denominator () const; - - void set_audio_stream (boost::shared_ptr<AudioStream>); - void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); - - bool seek (double); - bool seek_to_last (); - void seek_forward (); + void pass (); + void seek (VideoContent::Frame); void seek_back (); + bool done () const; private: + friend class ::ffmpeg_pts_offset_test; /* No copy construction */ FFmpegDecoder (FFmpegDecoder const &); + FFmpegDecoder& operator= (FFmpegDecoder const &); - bool pass (); - bool do_seek (double p, bool, bool); - PixelFormat pixel_format () const; - AVSampleFormat audio_sample_format () const; - int bytes_per_audio_sample () const; - - void filter_and_emit_video (); + static double compute_pts_offset (double, double, float); - void setup_general (); - void setup_video (); - void setup_audio (); void setup_subtitle (); + AVSampleFormat audio_sample_format () const; + int bytes_per_audio_sample () const; + void do_seek (VideoContent::Frame, bool, bool); + + bool decode_video_packet (); void decode_audio_packet (); 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; - int _video_stream; - - AVFrame* _frame; - - AVCodecContext* _video_codec_context; - AVCodec* _video_codec; - AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio - AVCodec* _audio_codec; ///< may be 0 if there is no audio AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle - - AVPacket _packet; - + std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; boost::mutex _filter_graphs_mutex; - static boost::mutex _mutex; + bool _decode_video; + bool _decode_audio; + + double _pts_offset; }; diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc new file mode 100644 index 000000000..f45b0fe52 --- /dev/null +++ b/src/lib/ffmpeg_examiner.cc @@ -0,0 +1,168 @@ +/* + Copyright (C) 2013 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. + +*/ + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +} +#include "ffmpeg_examiner.h" +#include "ffmpeg_content.h" + +using std::string; +using std::cout; +using std::stringstream; +using boost::shared_ptr; +using boost::optional; + +FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c) + : FFmpeg (c) +{ + /* Find audio and subtitle streams */ + + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { + AVStream* s = _format_context->streams[i]; + if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + + /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up, + so bodge it here. No idea why we should have to do this. + */ + + if (s->codec->channel_layout == 0) { + s->codec->channel_layout = av_get_default_channel_layout (s->codec->channels); + } + + _audio_streams.push_back ( + shared_ptr<FFmpegAudioStream> ( + new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channels) + ) + ); + + } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { + _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (stream_name (s), i))); + } + } + + /* Run through until we find the first audio (for each stream) and video */ + + while (1) { + int r = av_read_frame (_format_context, &_packet); + if (r < 0) { + break; + } + + int frame_finished; + avcodec_get_frame_defaults (_frame); + + cout << "got packet " << _packet.stream_index << "\n"; + + AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec; + + if (_packet.stream_index == _video_stream && !_first_video) { + if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + _first_video = frame_time (_video_stream); + } + } else { + for (size_t i = 0; i < _audio_streams.size(); ++i) { + if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->first_audio) { + if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->id); + } + } + } + } + + bool have_all_audio = true; + size_t i = 0; + while (i < _audio_streams.size() && have_all_audio) { + have_all_audio = _audio_streams[i]->first_audio; + ++i; + } + + if (_first_video && have_all_audio) { + break; + } + + av_free_packet (&_packet); + } +} + +optional<double> +FFmpegExaminer::frame_time (int stream) const +{ + optional<double> t; + + int64_t const bet = av_frame_get_best_effort_timestamp (_frame); + if (bet != AV_NOPTS_VALUE) { + t = bet * av_q2d (_format_context->streams[stream]->time_base); + } + + return t; +} + +float +FFmpegExaminer::video_frame_rate () const +{ + AVStream* s = _format_context->streams[_video_stream]; + + if (s->avg_frame_rate.num && s->avg_frame_rate.den) { + return av_q2d (s->avg_frame_rate); + } + + return av_q2d (s->r_frame_rate); +} + +libdcp::Size +FFmpegExaminer::video_size () const +{ + return libdcp::Size (video_codec_context()->width, video_codec_context()->height); +} + +/** @return Length (in video frames) according to our content's header */ +VideoContent::Frame +FFmpegExaminer::video_length () const +{ + return (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate(); +} + +string +FFmpegExaminer::stream_name (AVStream* s) const +{ + stringstream n; + + if (s->metadata) { + AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0); + if (lang) { + n << lang->value; + } + + AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0); + if (title) { + if (!n.str().empty()) { + n << " "; + } + n << title->value; + } + } + + if (n.str().empty()) { + n << "unknown"; + } + + return n.str (); +} diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h new file mode 100644 index 000000000..ec84865ed --- /dev/null +++ b/src/lib/ffmpeg_examiner.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/optional.hpp> +#include "ffmpeg.h" +#include "video_examiner.h" + +class FFmpegAudioStream; +class FFmpegSubtitleStream; + +class FFmpegExaminer : public FFmpeg, public VideoExaminer +{ +public: + FFmpegExaminer (boost::shared_ptr<const FFmpegContent>); + + float video_frame_rate () const; + libdcp::Size video_size () const; + VideoContent::Frame video_length () const; + + std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const { + return _subtitle_streams; + } + + std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const { + return _audio_streams; + } + + boost::optional<double> first_video () const { + return _first_video; + } + +private: + std::string stream_name (AVStream* s) const; + boost::optional<double> frame_time (int) const; + + std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams; + std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; + boost::optional<double> _first_video; +}; diff --git a/src/lib/film.cc b/src/lib/film.cc index ce555ac8b..fa75ab1f1 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -29,29 +29,29 @@ #include <boost/algorithm/string.hpp> #include <boost/lexical_cast.hpp> #include <boost/date_time.hpp> +#include <libxml++/libxml++.h> +#include <libcxml/cxml.h> #include "film.h" -#include "format.h" #include "job.h" #include "filter.h" -#include "transcoder.h" #include "util.h" #include "job_manager.h" -#include "ab_transcode_job.h" #include "transcode_job.h" #include "scp_dcp_job.h" #include "log.h" -#include "options.h" #include "exceptions.h" #include "examine_content_job.h" #include "scaler.h" -#include "decoder_factory.h" #include "config.h" #include "version.h" #include "ui_signaller.h" -#include "video_decoder.h" -#include "audio_decoder.h" -#include "sndfile_decoder.h" -#include "analyse_audio_job.h" +#include "playlist.h" +#include "player.h" +#include "ffmpeg_content.h" +#include "imagemagick_content.h" +#include "sndfile_content.h" +#include "dcp_content_type.h" +#include "ratio.h" #include "cross.h" #include "i18n.h" @@ -68,9 +68,12 @@ using std::setfill; using std::min; using std::make_pair; using std::endl; +using std::cout; using std::list; using boost::shared_ptr; +using boost::weak_ptr; using boost::lexical_cast; +using boost::dynamic_pointer_cast; using boost::to_upper_copy; using boost::ends_with; using boost::starts_with; @@ -79,40 +82,32 @@ using libdcp::Size; int const Film::state_version = 4; -/** Construct a Film object in a given directory, reading any metadata - * file that exists in that directory. An exception will be thrown if - * must_exist is true and the specified directory does not exist. +/** Construct a Film object in a given directory. * * @param d Film directory. - * @param must_exist true to throw an exception if does not exist. */ -Film::Film (string d, bool must_exist) - : _use_dci_name (true) - , _trust_content_header (true) +Film::Film (string d) + : _playlist (new Playlist) + , _use_dci_name (true) , _dcp_content_type (Config::instance()->default_dcp_content_type ()) - , _format (Config::instance()->default_format ()) + , _container (Config::instance()->default_container ()) , _scaler (Scaler::from_id ("bicubic")) - , _trim_start (0) - , _trim_end (0) - , _trim_type (CPL) - , _dcp_ab (false) - , _use_content_audio (true) - , _audio_gain (0) - , _audio_delay (0) - , _still_duration (10) , _with_subtitles (false) , _subtitle_offset (0) , _subtitle_scale (1) , _colour_lut (0) , _j2k_bandwidth (200000000) , _dci_metadata (Config::instance()->default_dci_metadata ()) - , _dcp_frame_rate (0) + , _dcp_video_frame_rate (24) + , _dcp_audio_channels (MAX_AUDIO_CHANNELS) , _minimum_audio_channels (0) - , _source_frame_rate (0) , _dirty (false) { set_dci_date_today (); + + _playlist->Changed.connect (bind (&Film::playlist_changed, this)); + _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2)); /* Make state.directory a complete path without ..s (where possible) (Code swiped from Adam Bowen on stackoverflow) @@ -133,103 +128,46 @@ Film::Film (string d, bool must_exist) } set_directory (result.string ()); - - if (!boost::filesystem::exists (directory())) { - if (must_exist) { - throw OpenFileError (directory()); - } else { - boost::filesystem::create_directory (directory()); - } - } - - _sndfile_stream = SndfileStream::create (); - - _log.reset (new FileLog (file ("log"))); - - if (must_exist) { - read_metadata (); - } else { - write_metadata (); - } } Film::Film (Film const & o) : boost::enable_shared_from_this<Film> (o) /* note: the copied film shares the original's log */ , _log (o._log) + , _playlist (new Playlist (o._playlist)) , _directory (o._directory) , _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) - , _filters (o._filters) + , _container (o._container) , _scaler (o._scaler) - , _trim_start (o._trim_start) - , _trim_end (o._trim_end) - , _trim_type (o._trim_type) - , _dcp_ab (o._dcp_ab) - , _content_audio_stream (o._content_audio_stream) - , _external_audio (o._external_audio) - , _use_content_audio (o._use_content_audio) - , _audio_gain (o._audio_gain) - , _audio_delay (o._audio_delay) - , _still_duration (o._still_duration) - , _subtitle_stream (o._subtitle_stream) , _with_subtitles (o._with_subtitles) , _subtitle_offset (o._subtitle_offset) , _subtitle_scale (o._subtitle_scale) , _colour_lut (o._colour_lut) , _j2k_bandwidth (o._j2k_bandwidth) , _dci_metadata (o._dci_metadata) + , _dcp_video_frame_rate (o._dcp_video_frame_rate) , _dci_date (o._dci_date) - , _dcp_frame_rate (o._dcp_frame_rate) , _minimum_audio_channels (o._minimum_audio_channels) - , _size (o._size) - , _length (o._length) - , _content_digest (o._content_digest) - , _content_audio_streams (o._content_audio_streams) - , _sndfile_stream (o._sndfile_stream) - , _subtitle_streams (o._subtitle_streams) - , _source_frame_rate (o._source_frame_rate) , _dirty (o._dirty) { - -} - -Film::~Film () -{ - + _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2)); } string Film::video_state_identifier () const { - assert (format ()); + assert (container ()); LocaleGuard lg; - pair<string, string> f = Filter::ffmpeg_strings (filters()); - stringstream s; - s << format()->id() - << "_" << content_digest() - << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom - << "_" << _dcp_frame_rate - << "_" << f.first << "_" << f.second + s << container()->id() + << "_" << _playlist->video_digest() + << "_" << _dcp_video_frame_rate << "_" << scaler()->id() << "_" << j2k_bandwidth() - << "_" << boost::lexical_cast<int> (colour_lut()); - - if (trim_type() == ENCODE) { - s << "_" << trim_start() << "_" << trim_end(); - } - - if (dcp_ab()) { - pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); - s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; - } + << "_" << lexical_cast<int> (colour_lut()); return s.str (); } @@ -284,13 +222,12 @@ Film::filename_safe_name () const return o; } -string -Film::audio_analysis_path () const +boost::filesystem::path +Film::audio_analysis_path (shared_ptr<const AudioContent> c) const { - boost::filesystem::path p; - p /= "analysis"; - p /= content_digest(); - return file (p.string ()); + boost::filesystem::path p = dir ("analysis"); + p /= c->digest(); + return p; } /** Add suitable Jobs to the JobManager to create a DCP for this Film */ @@ -303,7 +240,7 @@ Film::make_dcp () throw BadSettingError (_("name"), _("cannot contain slashes")); } - log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary())); + log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary())); { char buffer[128]; @@ -311,23 +248,18 @@ Film::make_dcp () log()->log (String::compose ("Starting to make DCP on %1", buffer)); } - log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video")))); - if (length()) { - log()->log (String::compose ("Content length %1", length().get())); - } - log()->log (String::compose ("Content digest %1", content_digest())); - log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate())); +// log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video")))); +// if (length()) { +// log()->log (String::compose ("Content length %1", length().get())); +// } +// log()->log (String::compose ("Content digest %1", content_digest())); +// log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate())); log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads())); log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth())); - if (use_content_audio()) { - log()->log ("Using content's audio"); - } else { - log()->log (String::compose ("Using external audio (%1 files)", external_audio().size())); - } -#ifdef DVDOMATIC_DEBUG - log()->log ("DVD-o-matic built in debug mode."); +#ifdef DCPOMATIC_DEBUG + log()->log ("DCP-o-matic built in debug mode."); #else - log()->log ("DVD-o-matic built in optimised mode."); + log()->log ("DCP-o-matic built in optimised mode."); #endif #ifdef LIBDCP_DEBUG log()->log ("libdcp built in debug mode."); @@ -341,12 +273,12 @@ Film::make_dcp () log()->log (String::compose ("Mount: %1 %2", i->first, i->second)); } - if (format() == 0) { - throw MissingSettingError (_("format")); + if (container() == 0) { + throw MissingSettingError (_("container")); } - if (content().empty ()) { - throw MissingSettingError (_("content")); + if (_playlist->content().empty ()) { + throw StringError (_("You must add some content to the DCP before creating it")); } if (dcp_content_type() == 0) { @@ -357,60 +289,7 @@ Film::make_dcp () throw MissingSettingError (_("name")); } - DecodeOptions od; - od.decode_subtitles = with_subtitles (); - - shared_ptr<Job> r; - - if (dcp_ab()) { - r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od))); - } else { - r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od))); - } -} - -/** Start a job to analyse the audio of our content file */ -void -Film::analyse_audio () -{ - if (_analyse_audio_job) { - return; - } - - _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this())); - _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this)); - JobManager::instance()->add (_analyse_audio_job); -} - -/** Start a job to examine our content file */ -void -Film::examine_content () -{ - if (_examine_content_job) { - return; - } - - _examine_content_job.reset (new ExamineContentJob (shared_from_this())); - _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this)); - JobManager::instance()->add (_examine_content_job); -} - -void -Film::analyse_audio_finished () -{ - ensure_ui_thread (); - - if (_analyse_audio_job->finished_ok ()) { - AudioAnalysisSucceeded (); - } - - _analyse_audio_job.reset (); -} - -void -Film::examine_content_finished () -{ - _examine_content_job.reset (); + JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this()))); } /** Start a job to send our DCP to the configured TMS */ @@ -427,7 +306,7 @@ Film::send_dcp_to_tms () int Film::encoded_frames () const { - if (format() == 0) { + if (container() == 0) { return 0; } @@ -444,87 +323,44 @@ Film::encoded_frames () const void Film::write_metadata () const { + if (!boost::filesystem::exists (directory())) { + boost::filesystem::create_directory (directory()); + } + boost::mutex::scoped_lock lm (_state_mutex); LocaleGuard lg; boost::filesystem::create_directories (directory()); - string const m = file ("metadata"); - ofstream f (m.c_str ()); - if (!f.good ()) { - throw CreateFileError (m); - } + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("Metadata"); - f << "version " << state_version << endl; + root->add_child("Version")->add_child_text (lexical_cast<string> (state_version)); + root->add_child("Name")->add_child_text (_name); + root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0"); - /* User stuff */ - f << "name " << _name << endl; - f << "use_dci_name " << _use_dci_name << endl; - f << "content " << _content << endl; - f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl; if (_dcp_content_type) { - f << "dcp_content_type " << _dcp_content_type->dci_name () << endl; - } - if (_format) { - f << "format " << _format->as_metadata () << endl; - } - f << "left_crop " << _crop.left << endl; - f << "right_crop " << _crop.right << endl; - f << "top_crop " << _crop.top << endl; - f << "bottom_crop " << _crop.bottom << endl; - for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - f << "filter " << (*i)->id () << endl; - } - f << "scaler " << _scaler->id () << endl; - f << "trim_start " << _trim_start << endl; - f << "trim_end " << _trim_end << endl; - switch (_trim_type) { - case CPL: - f << "trim_type cpl\n"; - break; - case ENCODE: - f << "trim_type encode\n"; - break; - } - f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl; - if (_content_audio_stream) { - f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl; + root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ()); } - for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) { - f << "external_audio " << *i << endl; - } - f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl; - f << "audio_gain " << _audio_gain << endl; - f << "audio_delay " << _audio_delay << endl; - f << "still_duration " << _still_duration << endl; - if (_subtitle_stream) { - f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl; - } - f << "with_subtitles " << _with_subtitles << endl; - f << "subtitle_offset " << _subtitle_offset << endl; - f << "subtitle_scale " << _subtitle_scale << endl; - f << "colour_lut " << _colour_lut << endl; - f << "j2k_bandwidth " << _j2k_bandwidth << endl; - _dci_metadata.write (f); - f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl; - f << "dcp_frame_rate " << _dcp_frame_rate << endl; - f << "minimum_audio_channels " << _minimum_audio_channels << endl; - f << "width " << _size.width << endl; - f << "height " << _size.height << endl; - f << "length " << _length.get_value_or(0) << endl; - f << "content_digest " << _content_digest << endl; - - for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { - f << "content_audio_stream " << (*i)->to_string () << endl; - } - - f << "external_audio_stream " << _sndfile_stream->to_string() << endl; - for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { - f << "subtitle_stream " << (*i)->to_string () << endl; + if (_container) { + root->add_child("Container")->add_child_text (_container->id ()); } - f << "source_frame_rate " << _source_frame_rate << endl; + root->add_child("Scaler")->add_child_text (_scaler->id ()); + root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); + root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset)); + root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale)); + root->add_child("ColourLUT")->add_child_text (lexical_cast<string> (_colour_lut)); + root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth)); + _dci_metadata.as_xml (root->add_child ("DCIMetadata")); + root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast<string> (_dcp_video_frame_rate)); + root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date)); + root->add_child("DCPAudioChannels")->add_child_text (lexical_cast<string> (_dcp_audio_channels)); + root->add_child("MinimumAudioChannels")->add_child_text (lexical_cast<string> (_minimum_audio_channels)); + _playlist->as_xml (root->add_child ("Playlist")); + + doc.write_to_file_formatted (file ("metadata.xml")); _dirty = false; } @@ -536,179 +372,46 @@ Film::read_metadata () boost::mutex::scoped_lock lm (_state_mutex); LocaleGuard lg; - _external_audio.clear (); - _content_audio_streams.clear (); - _subtitle_streams.clear (); - - boost::optional<int> version; - - /* Backward compatibility things */ - boost::optional<int> audio_sample_rate; - boost::optional<int> audio_stream_index; - boost::optional<int> subtitle_stream_index; - - ifstream f (file ("metadata").c_str()); - if (!f.good()) { - throw OpenFileError (file ("metadata")); + if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) { + throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!")); } - - multimap<string, string> kv = read_key_value (f); - /* We need version before anything else */ - multimap<string, string>::iterator v = kv.find ("version"); - if (v != kv.end ()) { - version = atoi (v->second.c_str()); - } + cxml::File f (file ("metadata.xml"), "Metadata"); - for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) { - string const k = i->first; - string const v = i->second; + _name = f.string_child ("Name"); + _use_dci_name = f.bool_child ("UseDCIName"); - if (k == "audio_sample_rate") { - audio_sample_rate = atoi (v.c_str()); - } - - /* User-specified stuff */ - if (k == "name") { - _name = v; - } else if (k == "use_dci_name") { - _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") { - if (version < 3) { - _dcp_content_type = DCPContentType::from_pretty_name (v); - } else { - _dcp_content_type = DCPContentType::from_dci_name (v); - } - } else if (k == "format") { - _format = Format::from_metadata (v); - } else if (k == "left_crop") { - _crop.left = atoi (v.c_str ()); - } else if (k == "right_crop") { - _crop.right = atoi (v.c_str ()); - } else if (k == "top_crop") { - _crop.top = atoi (v.c_str ()); - } else if (k == "bottom_crop") { - _crop.bottom = atoi (v.c_str ()); - } else if (k == "filter") { - _filters.push_back (Filter::from_id (v)); - } else if (k == "scaler") { - _scaler = Scaler::from_id (v); - } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") { - _trim_start = atoi (v.c_str ()); - } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") { - _trim_end = atoi (v.c_str ()); - } else if (k == "trim_type") { - if (v == "cpl") { - _trim_type = CPL; - } else if (v == "encode") { - _trim_type = ENCODE; - } - } else if (k == "dcp_ab") { - _dcp_ab = (v == "1"); - } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) { - if (!version) { - audio_stream_index = atoi (v.c_str ()); - } else { - _content_audio_stream = audio_stream_factory (v, version); - } - } else if (k == "external_audio") { - _external_audio.push_back (v); - } else if (k == "use_content_audio") { - _use_content_audio = (v == "1"); - } else if (k == "audio_gain") { - _audio_gain = atof (v.c_str ()); - } else if (k == "audio_delay") { - _audio_delay = atoi (v.c_str ()); - } else if (k == "still_duration") { - _still_duration = atoi (v.c_str ()); - } else if (k == "selected_subtitle_stream") { - if (!version) { - subtitle_stream_index = atoi (v.c_str ()); - } else { - _subtitle_stream = subtitle_stream_factory (v, version); - } - } else if (k == "with_subtitles") { - _with_subtitles = (v == "1"); - } else if (k == "subtitle_offset") { - _subtitle_offset = atoi (v.c_str ()); - } else if (k == "subtitle_scale") { - _subtitle_scale = atof (v.c_str ()); - } else if (k == "colour_lut") { - _colour_lut = atoi (v.c_str ()); - } else if (k == "j2k_bandwidth") { - _j2k_bandwidth = atoi (v.c_str ()); - } else if (k == "dci_date") { - _dci_date = boost::gregorian::from_undelimited_string (v); - } else if (k == "dcp_frame_rate") { - _dcp_frame_rate = atoi (v.c_str ()); - } else if (k == "minimum_audio_channels") { - _minimum_audio_channels = atoi (v.c_str ()); + { + optional<string> c = f.optional_string_child ("DCPContentType"); + if (c) { + _dcp_content_type = DCPContentType::from_dci_name (c.get ()); } + } - _dci_metadata.read (k, v); - - /* Cached stuff */ - if (k == "width") { - _size.width = atoi (v.c_str ()); - } else if (k == "height") { - _size.height = atoi (v.c_str ()); - } else if (k == "length") { - int const vv = atoi (v.c_str ()); - if (vv) { - _length = vv; - } - } else if (k == "content_digest") { - _content_digest = v; - } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) { - _content_audio_streams.push_back (audio_stream_factory (v, version)); - } else if (k == "external_audio_stream") { - _sndfile_stream = audio_stream_factory (v, version); - } else if (k == "subtitle_stream") { - _subtitle_streams.push_back (subtitle_stream_factory (v, version)); - } else if (k == "source_frame_rate") { - _source_frame_rate = atof (v.c_str ()); - } else if (version < 4 && k == "frames_per_second") { - _source_frame_rate = atof (v.c_str ()); - /* Fill in what would have been used for DCP frame rate by the older version */ - _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate); + { + optional<string> c = f.optional_string_child ("Container"); + if (c) { + _container = Ratio::from_id (c.get ()); } } - if (!version) { - if (audio_sample_rate) { - /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */ - for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { - (*i)->set_sample_rate (audio_sample_rate.get()); - } - } + _scaler = Scaler::from_id (f.string_child ("Scaler")); + _with_subtitles = f.bool_child ("WithSubtitles"); + _subtitle_offset = f.number_child<float> ("SubtitleOffset"); + _subtitle_scale = f.number_child<float> ("SubtitleScale"); + _colour_lut = f.number_child<int> ("ColourLUT"); + _j2k_bandwidth = f.number_child<int> ("J2KBandwidth"); + _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); + _dcp_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate"); + _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); + _dcp_audio_channels = f.number_child<int> ("DCPAudioChannels"); + _minimum_audio_channels = f.number_child<int> ("MinimumAudioChannels"); - /* also the selected stream was specified as an index */ - if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) { - _content_audio_stream = _content_audio_streams[audio_stream_index.get()]; - } + _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist")); - /* similarly the subtitle */ - if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) { - _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()]; - } - } - _dirty = false; } -libdcp::Size -Film::cropped_size (libdcp::Size s) const -{ - boost::mutex::scoped_lock lm (_state_mutex); - s.width -= _crop.left + _crop.right; - s.height -= _crop.top + _crop.bottom; - return s; -} - /** Given a directory name, return its full path within the Film's directory. * The directory (and its parents) will be created if they do not exist. */ @@ -744,67 +447,6 @@ Film::file (string f) const return p.string (); } -/** @return full path of the content (actual video) file - * of the Film. - */ -string -Film::content_path () const -{ - boost::mutex::scoped_lock lm (_state_mutex); - if (boost::filesystem::path(_content).has_root_directory ()) { - return _content; - } - - return file (_content); -} - -ContentType -Film::content_type () const -{ - if (boost::filesystem::is_directory (_content)) { - /* Directory of images, we assume */ - return VIDEO; - } - - if (still_image_file (_content)) { - return STILL; - } - - return VIDEO; -} - -/** @return The sampling rate that we will resample the audio to */ -int -Film::target_audio_sample_rate () const -{ - if (!audio_stream()) { - return 0; - } - - /* Resample to a DCI-approved sample rate */ - double t = dcp_audio_sample_rate (audio_stream()->sample_rate()); - - FrameRateConversion frc (source_frame_rate(), dcp_frame_rate()); - - /* Compensate if the DCP is being run at a different frame rate - to the source; that is, if the video is run such that it will - look different in the DCP compared to the source (slower or faster). - skip/repeat doesn't come into effect here. - */ - - if (frc.change_speed) { - t *= source_frame_rate() * frc.factor() / dcp_frame_rate(); - } - - return rint (t); -} - -int -Film::still_duration_in_frames () const -{ - return still_duration() * source_frame_rate(); -} - /** @return a DCI-compliant name for a DCP of this film */ string Film::dci_name (bool if_created_now) const @@ -829,8 +471,8 @@ Film::dci_name (bool if_created_now) const d << "_" << dcp_content_type()->dci_name(); } - if (format()) { - d << "_" << format()->dci_name(); + if (container()) { + d << "_" << container()->dci_name(); } DCIMetadata const dm = dci_metadata (); @@ -851,22 +493,7 @@ Film::dci_name (bool if_created_now) const } } - switch (audio_channels()) { - case 1: - d << "_10"; - break; - case 2: - d << "_20"; - break; - case 6: - d << "_51"; - break; - case 8: - d << "_71"; - break; - } - - d << "_2K"; + d << "_51_2K"; if (!dm.studio.empty ()) { d << "_" << dm.studio; @@ -930,110 +557,6 @@ Film::set_use_dci_name (bool u) } void -Film::set_content (string c) -{ - string check = directory (); - - boost::filesystem::path slash ("/"); - string platform_slash = slash.make_preferred().string (); - - if (!ends_with (check, platform_slash)) { - check += platform_slash; - } - - if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) { - c = c.substr (_directory.length() + 1); - } - - string old_content; - - { - boost::mutex::scoped_lock lm (_state_mutex); - if (c == _content) { - return; - } - - old_content = _content; - _content = c; - } - - /* Do this before we start using FFmpeg ourselves */ - run_ffprobe (c, file ("ffprobe.log"), _log); - - /* Reset streams here in case the new content doesn't have one or the other */ - _content_audio_stream = shared_ptr<AudioStream> (); - _subtitle_stream = shared_ptr<SubtitleStream> (); - - /* Start off using content audio */ - set_use_content_audio (true); - - /* Create a temporary decoder so that we can get information - about the content. - */ - - try { - Decoders d = decoder_factory (shared_from_this(), DecodeOptions()); - - set_size (d.video->native_size ()); - set_source_frame_rate (d.video->frames_per_second ()); - set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ())); - set_subtitle_streams (d.video->subtitle_streams ()); - if (d.audio) { - set_content_audio_streams (d.audio->audio_streams ()); - } - - { - boost::mutex::scoped_lock lm (_state_mutex); - _content = c; - } - - signal_changed (CONTENT); - - /* Start off with the first audio and subtitle streams */ - if (d.audio && !d.audio->audio_streams().empty()) { - set_content_audio_stream (d.audio->audio_streams().front()); - } - - if (!d.video->subtitle_streams().empty()) { - set_subtitle_stream (d.video->subtitle_streams().front()); - } - - examine_content (); - - } catch (...) { - - boost::mutex::scoped_lock lm (_state_mutex); - _content = old_content; - throw; - - } - - /* Default format */ - set_format (Config::instance()->default_format ()); - - /* Still image DCPs must use external audio */ - if (content_type() == STILL) { - set_use_content_audio (false); - } -} - -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 Film::set_dcp_content_type (DCPContentType const * t) { { @@ -1044,90 +567,13 @@ Film::set_dcp_content_type (DCPContentType const * t) } void -Film::set_format (Format const * f) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _format = f; - } - signal_changed (FORMAT); -} - -void -Film::set_crop (Crop c) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _crop = c; - } - signal_changed (CROP); -} - -void -Film::set_left_crop (int c) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - - if (_crop.left == c) { - return; - } - - _crop.left = c; - } - signal_changed (CROP); -} - -void -Film::set_right_crop (int c) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - if (_crop.right == c) { - return; - } - - _crop.right = c; - } - signal_changed (CROP); -} - -void -Film::set_top_crop (int c) +Film::set_container (Ratio const * c) { { boost::mutex::scoped_lock lm (_state_mutex); - if (_crop.top == c) { - return; - } - - _crop.top = c; - } - signal_changed (CROP); -} - -void -Film::set_bottom_crop (int c) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - if (_crop.bottom == c) { - return; - } - - _crop.bottom = c; + _container = c; } - signal_changed (CROP); -} - -void -Film::set_filters (vector<Filter const *> f) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _filters = f; - } - signal_changed (FILTERS); + signal_changed (CONTAINER); } void @@ -1141,123 +587,6 @@ Film::set_scaler (Scaler const * s) } void -Film::set_trim_start (int t) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _trim_start = t; - } - signal_changed (TRIM_START); -} - -void -Film::set_trim_end (int t) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _trim_end = t; - } - signal_changed (TRIM_END); -} - -void -Film::set_trim_type (TrimType t) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _trim_type = t; - } - signal_changed (TRIM_TYPE); -} - -void -Film::set_dcp_ab (bool a) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_ab = a; - } - signal_changed (DCP_AB); -} - -void -Film::set_content_audio_stream (shared_ptr<AudioStream> s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_audio_stream = s; - } - signal_changed (CONTENT_AUDIO_STREAM); -} - -void -Film::set_external_audio (vector<string> a) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _external_audio = a; - } - - shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions())); - if (decoder->audio_stream()) { - _sndfile_stream = decoder->audio_stream (); - } - - signal_changed (EXTERNAL_AUDIO); -} - -void -Film::set_use_content_audio (bool e) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _use_content_audio = e; - } - - signal_changed (USE_CONTENT_AUDIO); -} - -void -Film::set_audio_gain (float g) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_gain = g; - } - signal_changed (AUDIO_GAIN); -} - -void -Film::set_audio_delay (int d) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_delay = d; - } - signal_changed (AUDIO_DELAY); -} - -void -Film::set_still_duration (int d) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _still_duration = d; - } - signal_changed (STILL_DURATION); -} - -void -Film::set_subtitle_stream (shared_ptr<SubtitleStream> s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_stream = s; - } - signal_changed (SUBTITLE_STREAM); -} - -void Film::set_with_subtitles (bool w) { { @@ -1319,16 +648,6 @@ Film::set_dci_metadata (DCIMetadata m) void -Film::set_dcp_frame_rate (int f) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_frame_rate = f; - } - signal_changed (DCP_FRAME_RATE); -} - -void Film::set_minimum_audio_channels (int c) { { @@ -1339,76 +658,16 @@ Film::set_minimum_audio_channels (int c) } void -Film::set_size (libdcp::Size s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _size = s; - } - signal_changed (SIZE); -} - -void -Film::set_length (SourceFrame l) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = l; - } - signal_changed (LENGTH); -} - -void -Film::unset_length () -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = boost::none; - } - signal_changed (LENGTH); -} - -void -Film::set_content_digest (string d) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_digest = d; - } - _dirty = true; -} - -void -Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_audio_streams = s; - } - signal_changed (CONTENT_AUDIO_STREAMS); -} - -void -Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s) +Film::set_dcp_video_frame_rate (int f) { { boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_streams = s; + _dcp_video_frame_rate = f; } - signal_changed (SUBTITLE_STREAMS); + signal_changed (DCP_VIDEO_FRAME_RATE); } void -Film::set_source_frame_rate (float f) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _source_frame_rate = f; - } - signal_changed (SOURCE_FRAME_RATE); -} - -void Film::signal_changed (Property p) { { @@ -1416,20 +675,17 @@ Film::signal_changed (Property p) _dirty = true; } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Changed), p)); + switch (p) { + case Film::CONTENT: + set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ()); + break; + default: + break; } -} -int -Film::audio_channels () const -{ - shared_ptr<AudioStream> s = audio_stream (); - if (!s) { - return 0; + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (Changed), p)); } - - return s->channels (); } void @@ -1438,16 +694,6 @@ Film::set_dci_date_today () _dci_date = boost::gregorian::day_clock::local_day (); } -boost::shared_ptr<AudioStream> -Film::audio_stream () const -{ - if (use_content_audio()) { - return _content_audio_stream; - } - - return _sndfile_stream; -} - string Film::info_path (int f) const { @@ -1503,20 +749,146 @@ Film::have_dcp () const return true; } -bool -Film::has_audio () const +shared_ptr<Player> +Film::player () const +{ + return shared_ptr<Player> (new Player (shared_from_this (), _playlist)); +} + +shared_ptr<Playlist> +Film::playlist () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _playlist; +} + +Playlist::ContentList +Film::content () const +{ + return _playlist->content (); +} + +void +Film::examine_and_add_content (shared_ptr<Content> c) { - if (use_content_audio()) { - return audio_stream(); + shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c)); + j->Finished.connect (bind (&Film::add_content_weak, this, boost::weak_ptr<Content> (c))); + JobManager::instance()->add (j); +} + +void +Film::add_content_weak (weak_ptr<Content> c) +{ + shared_ptr<Content> content = c.lock (); + if (content) { + add_content (content); } +} - vector<string> const e = external_audio (); - for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) { - if (!i->empty ()) { - return true; - } +void +Film::add_content (shared_ptr<Content> c) +{ + /* Add video content after any existing content */ + if (dynamic_pointer_cast<VideoContent> (c)) { + c->set_start (_playlist->video_end ()); + } + + _playlist->add (c); +} + +void +Film::remove_content (shared_ptr<Content> c) +{ + _playlist->remove (c); +} + +Time +Film::length () const +{ + return _playlist->length (); +} + +bool +Film::has_subtitles () const +{ + return _playlist->has_subtitles (); +} + +OutputVideoFrame +Film::best_dcp_video_frame_rate () const +{ + return _playlist->best_dcp_frame_rate (); +} + +void +Film::playlist_content_changed (boost::weak_ptr<Content> c, int p) +{ + if (p == VideoContentProperty::VIDEO_FRAME_RATE) { + set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ()); + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); } +} + +void +Film::playlist_changed () +{ + signal_changed (CONTENT); +} - return false; +int +Film::loop () const +{ + return _playlist->loop (); +} + +void +Film::set_loop (int c) +{ + _playlist->set_loop (c); +} + +OutputAudioFrame +Film::time_to_audio_frames (Time t) const +{ + return t * dcp_audio_frame_rate () / TIME_HZ; +} + +OutputVideoFrame +Film::time_to_video_frames (Time t) const +{ + return t * dcp_video_frame_rate () / TIME_HZ; +} + +Time +Film::audio_frames_to_time (OutputAudioFrame f) const +{ + return f * TIME_HZ / dcp_audio_frame_rate (); +} + +Time +Film::video_frames_to_time (OutputVideoFrame f) const +{ + return f * TIME_HZ / dcp_video_frame_rate (); +} + +OutputAudioFrame +Film::dcp_audio_frame_rate () const +{ + /* XXX */ + return 48000; } +void +Film::set_sequence_video (bool s) +{ + _playlist->set_sequence_video (s); +} + +libdcp::Size +Film::full_frame () const +{ + return libdcp::Size (2048, 1080); +} diff --git a/src/lib/film.h b/src/lib/film.h index ca9bd57f4..f5a7c1246 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -18,12 +18,12 @@ */ /** @file src/film.h - * @brief A representation of a piece of video (with sound), including naming, - * the source content file, and how it should be presented in a DCP. + * @brief A representation of some audio and video content, and details of + * how they should be presented in a DCP. */ -#ifndef DVDOMATIC_FILM_H -#define DVDOMATIC_FILM_H +#ifndef DCPOMATIC_FILM_H +#define DCPOMATIC_FILM_H #include <string> #include <vector> @@ -32,49 +32,43 @@ #include <boost/thread.hpp> #include <boost/signals2.hpp> #include <boost/enable_shared_from_this.hpp> -extern "C" { -#include <libavcodec/avcodec.h> -} -#include "dcp_content_type.h" #include "util.h" -#include "stream.h" #include "dci_metadata.h" +#include "types.h" +#include "ffmpeg_content.h" +#include "playlist.h" -class Format; +class DCPContentType; class Job; class Filter; class Log; class ExamineContentJob; class AnalyseAudioJob; class ExternalAudioStream; +class Content; +class Player; /** @class Film - * @brief A representation of a video, maybe with sound. - * - * A representation of a piece of video (maybe with sound), including naming, - * the source content file, and how it should be presented in a DCP. + * @brief A representation of some audio and video content, and details of + * how they should be presented in a DCP. */ class Film : public boost::enable_shared_from_this<Film> { public: - Film (std::string d, bool must_exist = true); + Film (std::string d); Film (Film const &); - ~Film (); std::string info_dir () const; std::string j2c_path (int f, bool t) const; std::string info_path (int f) const; std::string internal_video_mxf_dir () const; std::string internal_video_mxf_filename () const; - std::string audio_analysis_path () const; + boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const; std::string dcp_video_mxf_filename () const; std::string dcp_audio_mxf_filename () const; - void examine_content (); - void analyse_audio (); void send_dcp_to_tms (); - void make_dcp (); /** @return Logger. @@ -89,15 +83,9 @@ public: std::string file (std::string f) const; std::string dir (std::string d) const; - std::string content_path () const; - ContentType content_type () const; - - int target_audio_sample_rate () const; - - void write_metadata () const; void read_metadata (); + void write_metadata () const; - libdcp::Size cropped_size (libdcp::Size) const; std::string dci_name (bool if_created_now) const; std::string dcp_name (bool if_created_now = false) const; @@ -106,16 +94,32 @@ public: return _dirty; } - int audio_channels () const; - - void set_dci_date_today (); + libdcp::Size full_frame () const; bool have_dcp () const; - enum TrimType { - CPL, - ENCODE - }; + boost::shared_ptr<Player> player () const; + boost::shared_ptr<Playlist> playlist () const; + + OutputAudioFrame dcp_audio_frame_rate () const; + + OutputAudioFrame time_to_audio_frames (Time) const; + OutputVideoFrame time_to_video_frames (Time) const; + Time video_frames_to_time (OutputVideoFrame) const; + Time audio_frames_to_time (OutputAudioFrame) const; + + /* Proxies for some Playlist methods */ + + Playlist::ContentList content () const; + + Time length () const; + bool has_subtitles () const; + OutputVideoFrame best_dcp_video_frame_rate () const; + + void set_loop (int); + int loop () const; + + void set_sequence_video (bool); /** Identifiers for the parts of our state; used for signalling changes. @@ -124,36 +128,19 @@ public: NONE, NAME, USE_DCI_NAME, + /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */ CONTENT, - TRUST_CONTENT_HEADER, + LOOP, DCP_CONTENT_TYPE, - FORMAT, - CROP, - FILTERS, + CONTAINER, SCALER, - TRIM_START, - TRIM_END, - TRIM_TYPE, - DCP_AB, - CONTENT_AUDIO_STREAM, - EXTERNAL_AUDIO, - USE_CONTENT_AUDIO, - AUDIO_GAIN, - AUDIO_DELAY, - STILL_DURATION, - SUBTITLE_STREAM, WITH_SUBTITLES, SUBTITLE_OFFSET, SUBTITLE_SCALE, COLOUR_LUT, J2K_BANDWIDTH, DCI_METADATA, - SIZE, - LENGTH, - CONTENT_AUDIO_STREAMS, - SUBTITLE_STREAMS, - SOURCE_FRAME_RATE, - DCP_FRAME_RATE, + DCP_VIDEO_FRAME_RATE, MINIMUM_AUDIO_CHANNELS }; @@ -175,34 +162,14 @@ public: return _use_dci_name; } - std::string content () const { - boost::mutex::scoped_lock lm (_state_mutex); - 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; } - Format const * format () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _format; - } - - Crop crop () const { + Ratio const * container () const { boost::mutex::scoped_lock lm (_state_mutex); - return _crop; - } - - std::vector<Filter const *> filters () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _filters; + return _container; } Scaler const * scaler () const { @@ -210,63 +177,6 @@ public: return _scaler; } - int trim_start () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _trim_start; - } - - int trim_end () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _trim_end; - } - - TrimType trim_type () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _trim_type; - } - - bool dcp_ab () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_ab; - } - - boost::shared_ptr<AudioStream> content_audio_stream () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _content_audio_stream; - } - - std::vector<std::string> external_audio () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _external_audio; - } - - bool use_content_audio () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _use_content_audio; - } - - float audio_gain () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_gain; - } - - int audio_delay () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_delay; - } - - int still_duration () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _still_duration; - } - - int still_duration_in_frames () const; - - boost::shared_ptr<SubtitleStream> subtitle_stream () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_stream; - } - bool with_subtitles () const { boost::mutex::scoped_lock lm (_state_mutex); return _with_subtitles; @@ -297,43 +207,15 @@ public: return _dci_metadata; } - int dcp_frame_rate () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_frame_rate; - } - - libdcp::Size size () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _size; - } - - boost::optional<SourceFrame> length () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _length; - } - - std::string content_digest () const { + /* XXX: -> "video_frame_rate" */ + int dcp_video_frame_rate () const { boost::mutex::scoped_lock lm (_state_mutex); - return _content_digest; - } - - std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _content_audio_streams; + return _dcp_video_frame_rate; } - std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_streams; - } - - float source_frame_rate () const { + int dcp_audio_channels () const { boost::mutex::scoped_lock lm (_state_mutex); - if (content_type() == STILL) { - return 24; - } - - return _source_frame_rate; + return _dcp_audio_channels; } int minimum_audio_channels () const { @@ -341,75 +223,48 @@ public: return _minimum_audio_channels; } - boost::shared_ptr<AudioStream> audio_stream () const; - bool has_audio () const; - /* SET */ void set_directory (std::string); void set_name (std::string); void set_use_dci_name (bool); - void set_content (std::string); - void set_trust_content_header (bool); + void examine_and_add_content (boost::shared_ptr<Content>); + void add_content (boost::shared_ptr<Content>); + void remove_content (boost::shared_ptr<Content>); void set_dcp_content_type (DCPContentType const *); - void set_format (Format const *); - void set_crop (Crop); - void set_left_crop (int); - void set_right_crop (int); - void set_top_crop (int); - void set_bottom_crop (int); - void set_filters (std::vector<Filter const *>); + void set_container (Ratio const *); void set_scaler (Scaler const *); - void set_trim_start (int); - void set_trim_end (int); - void set_trim_type (TrimType); - void set_dcp_ab (bool); - void set_content_audio_stream (boost::shared_ptr<AudioStream>); - void set_external_audio (std::vector<std::string>); - void set_use_content_audio (bool); - void set_audio_gain (float); - void set_audio_delay (int); - void set_still_duration (int); - void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); void set_with_subtitles (bool); void set_subtitle_offset (int); void set_subtitle_scale (float); void set_colour_lut (int); void set_j2k_bandwidth (int); void set_dci_metadata (DCIMetadata); - void set_dcp_frame_rate (int); - void set_size (libdcp::Size); - void set_length (SourceFrame); - void unset_length (); - void set_content_digest (std::string); - void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >); - void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >); - void set_source_frame_rate (float); + void set_dcp_video_frame_rate (int); + void set_dci_date_today (); void set_minimum_audio_channels (int); - /** Emitted when some property has changed */ + /** Emitted when some property has of the Film has changed */ mutable boost::signals2::signal<void (Property)> Changed; - boost::signals2::signal<void ()> AudioAnalysisSucceeded; + /** Emitted when some property of our content has changed */ + mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged; /** Current version number of the state file */ static int const state_version; private: - /** Log to write to */ - boost::shared_ptr<Log> _log; - - /** Any running ExamineContentJob, or 0 */ - boost::shared_ptr<ExamineContentJob> _examine_content_job; - /** Any running AnalyseAudioJob, or 0 */ - boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job; - void signal_changed (Property); - void examine_content_finished (); - void analyse_audio_finished (); std::string video_state_identifier () const; + void playlist_changed (); + void playlist_content_changed (boost::weak_ptr<Content>, int); std::string filename_safe_name () const; + void add_content_weak (boost::weak_ptr<Content>); + + /** Log to write to */ + boost::shared_ptr<Log> _log; + boost::shared_ptr<Playlist> _playlist; /** Complete path to directory containing the film metadata; * must not be relative. @@ -418,54 +273,16 @@ private: /** Mutex for _directory */ mutable boost::mutex _directory_mutex; - /** Name for DVD-o-matic */ + /** Name for DCP-o-matic */ std::string _name; /** True if a auto-generated DCI-compliant name should be used for our DCP */ bool _use_dci_name; - /** File or directory containing content; may be relative to our directory - * or an absolute path. - */ - std::string _content; - /** If this is true, we will believe the length specified by the content - * file's header; if false, we will run through the whole content file - * the first time we see it in order to obtain the length. - */ - 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.) */ - Format const * _format; - /** The crop to apply to the source */ - Crop _crop; - /** Video filters that should be used when generating DCPs */ - std::vector<Filter const *> _filters; + /** The container to put this Film in (flat, scope, etc.) */ + Ratio const * _container; /** Scaler algorithm to use */ Scaler const * _scaler; - /** Frames to trim off the start of the DCP */ - int _trim_start; - /** Frames to trim off the end of the DCP */ - int _trim_end; - TrimType _trim_type; - /** true to create an A/B comparison DCP, where the left half of the image - is the video without any filters or post-processing, and the right half - has the specified filters and post-processing. - */ - bool _dcp_ab; - /** The audio stream to use from our content */ - boost::shared_ptr<AudioStream> _content_audio_stream; - /** List of filenames of external audio files, in channel order - (L, R, C, Lfe, Ls, Rs) - */ - std::vector<std::string> _external_audio; - /** true to use audio from our content file; false to use external audio */ - bool _use_content_audio; - /** Gain to apply to audio in dB */ - float _audio_gain; - /** Delay to apply to audio (positive moves audio later) in milliseconds */ - int _audio_delay; - /** Duration to make still-sourced films (in seconds) */ - int _still_duration; - boost::shared_ptr<SubtitleStream> _subtitle_stream; /** True if subtitles should be shown for this film */ bool _with_subtitles; /** y offset for placing subtitles, in source pixels; +ve is further down @@ -481,32 +298,15 @@ private: int _colour_lut; /** bandwidth for J2K files in bits per second */ int _j2k_bandwidth; - /** DCI naming stuff */ DCIMetadata _dci_metadata; + /** Frames per second to run our DCP at */ + int _dcp_video_frame_rate; /** The date that we should use in a DCI name */ boost::gregorian::date _dci_date; - /** Frames per second to run our DCP at */ - int _dcp_frame_rate; + int _dcp_audio_channels; int _minimum_audio_channels; - /* Data which are cached to speed things up */ - - /** Size, in pixels, of the source (ignoring cropping) */ - libdcp::Size _size; - /** The length of the source, in video frames (as far as we know) */ - boost::optional<SourceFrame> _length; - /** MD5 digest of our content file */ - std::string _content_digest; - /** The audio streams in our content */ - std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams; - /** A stream to represent possible external audio (will always exist) */ - boost::shared_ptr<AudioStream> _sndfile_stream; - /** the subtitle streams that we can use */ - std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams; - /** Frames per second of the source */ - float _source_frame_rate; - /** true if our state has changed since we last saved it */ mutable bool _dirty; diff --git a/src/lib/filter.h b/src/lib/filter.h index 205d92482..7587312c2 100644 --- a/src/lib/filter.h +++ b/src/lib/filter.h @@ -21,8 +21,8 @@ * @brief A class to describe one of FFmpeg's video or post-processing filters. */ -#ifndef DVDOMATIC_FILTER_H -#define DVDOMATIC_FILTER_H +#ifndef DCPOMATIC_FILTER_H +#define DCPOMATIC_FILTER_H #include <string> #include <vector> diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 8ff5e75df..275c46909 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -33,24 +33,23 @@ extern "C" { #include "filter.h" #include "exceptions.h" #include "image.h" -#include "film.h" -#include "ffmpeg_decoder.h" #include "i18n.h" using std::stringstream; using std::string; using std::list; +using std::cout; using boost::shared_ptr; +using boost::weak_ptr; using libdcp::Size; -/** Construct a FFmpegFilterGraph for the settings in a film. - * @param film Film. - * @param decoder Decoder that we are using. +/** Construct a FilterGraph for the settings in a piece of content. + * @param content Content. * @param s Size of the images to process. * @param p Pixel format of the images to process. */ -FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p) +FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) @@ -58,12 +57,13 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco { _frame = av_frame_alloc (); - string filters = Filter::ffmpeg_strings (film->filters()).first; + string filters = Filter::ffmpeg_strings (content->filters()).first; if (!filters.empty ()) { - filters += N_(","); + filters += ","; } - filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size())); + /* XXX; remove */ + filters += crop_string (Position (), _size); AVFilterGraph* graph = avfilter_graph_alloc(); if (graph == 0) { @@ -83,8 +83,8 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco stringstream a; a << "video_size=" << _size.width << "x" << _size.height << ":" << "pix_fmt=" << _pixel_format << ":" - << "time_base=" << decoder->time_base_numerator() << "/" << decoder->time_base_denominator() << ":" - << "pixel_aspect=" << decoder->sample_aspect_ratio_numerator() << "/" << decoder->sample_aspect_ratio_denominator(); + << "time_base=1/1:" + << "pixel_aspect=1/1"; int r; @@ -127,7 +127,7 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco /* XXX: leaking `inputs' / `outputs' ? */ } -FFmpegFilterGraph::~FFmpegFilterGraph () +FilterGraph::~FilterGraph () { av_frame_free (&_frame); } @@ -136,7 +136,7 @@ FFmpegFilterGraph::~FFmpegFilterGraph () * set of Images. Caller handles memory management of the input frame. */ list<shared_ptr<Image> > -FFmpegFilterGraph::process (AVFrame* frame) +FilterGraph::process (AVFrame* frame) { list<shared_ptr<Image> > images; @@ -161,25 +161,7 @@ FFmpegFilterGraph::process (AVFrame* frame) * @return true if this chain can process images with `s' and `p', otherwise false. */ bool -FFmpegFilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const +FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const { return (_size == s && _pixel_format == p); } - -list<shared_ptr<Image> > -EmptyFilterGraph::process (AVFrame* frame) -{ - list<shared_ptr<Image> > im; - im.push_back (shared_ptr<Image> (new SimpleImage (frame))); - return im; -} - -shared_ptr<FilterGraph> -filter_graph_factory (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size size, AVPixelFormat pixel_format) -{ - if (film->filters().empty() && film->crop() == Crop()) { - return shared_ptr<FilterGraph> (new EmptyFilterGraph); - } - - return shared_ptr<FilterGraph> (new FFmpegFilterGraph (film, decoder, size, pixel_format)); -} diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index ee378af4c..e294812c2 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -21,41 +21,22 @@ * @brief A graph of FFmpeg filters. */ -#ifndef DVDOMATIC_FILTER_GRAPH_H -#define DVDOMATIC_FILTER_GRAPH_H +#ifndef DCPOMATIC_FILTER_GRAPH_H +#define DCPOMATIC_FILTER_GRAPH_H #include "util.h" class Image; class VideoFilter; -class FFmpegDecoder; -class FilterGraph -{ -public: - virtual ~FilterGraph () {} - virtual bool can_process (libdcp::Size, AVPixelFormat) const = 0; - virtual std::list<boost::shared_ptr<Image> > process (AVFrame *) = 0; -}; - -class EmptyFilterGraph : public FilterGraph -{ -public: - bool can_process (libdcp::Size, AVPixelFormat) const { - return true; - } - - std::list<boost::shared_ptr<Image> > process (AVFrame *); -}; - -/** @class FFmpegFilterGraph +/** @class FilterGraph * @brief A graph of FFmpeg filters. */ -class FFmpegFilterGraph : public FilterGraph +class FilterGraph { public: - FFmpegFilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p); - ~FFmpegFilterGraph (); + FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p); + ~FilterGraph (); bool can_process (libdcp::Size s, AVPixelFormat p) const; std::list<boost::shared_ptr<Image> > process (AVFrame * frame); @@ -68,6 +49,4 @@ private: AVFrame* _frame; }; -boost::shared_ptr<FilterGraph> filter_graph_factory (boost::shared_ptr<Film>, FFmpegDecoder *, libdcp::Size, AVPixelFormat); - #endif diff --git a/src/lib/format.cc b/src/lib/format.cc deleted file mode 100644 index 78e200847..000000000 --- a/src/lib/format.cc +++ /dev/null @@ -1,240 +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/format.cc - * @brief Class to describe a format (aspect ratio) that a Film should - * be shown in. - */ - -#include <sstream> -#include <cstdlib> -#include <cassert> -#include <iomanip> -#include <iostream> -#include "format.h" -#include "film.h" - -#include "i18n.h" - -using std::string; -using std::setprecision; -using std::stringstream; -using std::vector; -using boost::shared_ptr; -using libdcp::Size; - -vector<Format const *> Format::_formats; - -/** @return A name to be presented to the user */ -string -FixedFormat::name () const -{ - stringstream s; - if (!_nickname.empty ()) { - s << _nickname << N_(" ("); - } - - s << setprecision(3) << _ratio << N_(":1"); - - if (!_nickname.empty ()) { - s << N_(")"); - } - - return s.str (); -} - -/** @return Identifier for this format as metadata for a Film's metadata file */ -string -Format::as_metadata () const -{ - return _id; -} - -/** Fill our _formats vector with all available formats */ -void -Format::setup_formats () -{ - /// TRANSLATORS: these are film picture aspect ratios; "Academy" means 1.37, "Flat" 1.85 and "Scope" 2.39. - _formats.push_back ( - new FixedFormat (1.19, libdcp::Size (1285, 1080), "119", _("1.19"), "F" - )); - - _formats.push_back ( - new FixedFormat (4.0 / 3.0, libdcp::Size (1436, 1080), "133", _("4:3"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.38, libdcp::Size (1485, 1080), "138", _("1.375"), "F" - )); - - _formats.push_back ( - new FixedFormat (4.0 / 3.0, libdcp::Size (1998, 1080), "133-in-flat", _("4:3 within Flat"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.37, libdcp::Size (1480, 1080), "137", _("Academy"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.66, libdcp::Size (1793, 1080), "166", _("1.66"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.66, libdcp::Size (1998, 1080), "166-in-flat", _("1.66 within Flat"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.78, libdcp::Size (1998, 1080), "178-in-flat", _("16:9 within Flat"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.78, libdcp::Size (1920, 1080), "178", _("16:9"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.85, libdcp::Size (1998, 1080), "185", _("Flat"), "F" - )); - - _formats.push_back ( - new FixedFormat (1.78, libdcp::Size (2048, 858), "178-in-scope", _("16:9 within Scope"), "S" - )); - - _formats.push_back ( - new FixedFormat (2.39, libdcp::Size (2048, 858), "239", _("Scope"), "S" - )); - - _formats.push_back ( - new FixedFormat (1.896, libdcp::Size (2048, 1080), "full-frame", _("Full frame"), "C" - )); - - _formats.push_back ( - new VariableFormat (libdcp::Size (1998, 1080), "var-185", _("Flat without stretch"), "F" - )); - - _formats.push_back ( - new VariableFormat (libdcp::Size (2048, 858), "var-239", _("Scope without stretch"), "S" - )); -} - -/** @param n Nickname. - * @return Matching Format, or 0. - */ -Format const * -Format::from_nickname (string n) -{ - vector<Format const *>::iterator i = _formats.begin (); - while (i != _formats.end() && (*i)->nickname() != n) { - ++i; - } - - if (i == _formats.end ()) { - return 0; - } - - return *i; -} - -/** @param i Id. - * @return Matching Format, or 0. - */ -Format const * -Format::from_id (string i) -{ - vector<Format const *>::iterator j = _formats.begin (); - while (j != _formats.end() && (*j)->id() != i) { - ++j; - } - - if (j == _formats.end ()) { - return 0; - } - - return *j; -} - - -/** @param m Metadata, as returned from as_metadata(). - * @return Matching Format, or 0. - */ -Format const * -Format::from_metadata (string m) -{ - return from_id (m); -} - -/** @return All available formats */ -vector<Format const *> -Format::all () -{ - return _formats; -} - -/** @param r Ratio - * @param dcp Size (in pixels) of the images that we should put in a DCP. - * @param id ID (e.g. 185) - * @param n Nick name (e.g. Flat) - */ -FixedFormat::FixedFormat (float r, libdcp::Size dcp, string id, string n, string d) - : Format (dcp, id, n, d) - , _ratio (r) -{ - -} - -/** @return Number of pixels (int the DCP image) to pad either side of the film - * (so there are dcp_padding() pixels on the left and dcp_padding() on the right) - */ -int -Format::dcp_padding (shared_ptr<const Film> f) const -{ - int p = rint ((_dcp_size.width - (_dcp_size.height * ratio(f))) / 2.0); - - /* This comes out -ve for Scope; bodge it */ - if (p < 0) { - p = 0; - } - - return p; -} - -float -Format::container_ratio () const -{ - return static_cast<float> (_dcp_size.width) / _dcp_size.height; -} - -VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d) - : Format (dcp, id, n, d) -{ - -} - -float -VariableFormat::ratio (shared_ptr<const Film> f) const -{ - libdcp::Size const c = f->cropped_size (f->size ()); - return float (c.width) / c.height; -} - -/** @return A name to be presented to the user */ -string -VariableFormat::name () const -{ - return _nickname; -} diff --git a/src/lib/format.h b/src/lib/format.h deleted file mode 100644 index e95306232..000000000 --- a/src/lib/format.h +++ /dev/null @@ -1,126 +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/format.h - * @brief Classes to describe a format (aspect ratio) that a Film should - * be shown in. - */ - -#include <string> -#include <vector> -#include "util.h" - -class Film; - -class Format -{ -public: - Format (libdcp::Size dcp, std::string id, std::string n, std::string d) - : _dcp_size (dcp) - , _id (id) - , _nickname (n) - , _dci_name (d) - {} - - /** @return the ratio of the container (including any padding) */ - float container_ratio () const; - - int dcp_padding (boost::shared_ptr<const Film> f) const; - - /** @return size in pixels of the images that we should - * put in a DCP for this ratio. This size will not correspond - * to the ratio when we are doing things like 16:9 in a Flat frame. - */ - libdcp::Size dcp_size () const { - return _dcp_size; - } - - std::string id () const { - return _id; - } - - /** @return Full name to present to the user */ - virtual std::string name () const = 0; - - /** @return Nickname (e.g. Flat, Scope) */ - std::string nickname () const { - return _nickname; - } - - std::string dci_name () const { - return _dci_name; - } - - std::string as_metadata () const; - - static Format const * from_nickname (std::string n); - static Format const * from_metadata (std::string m); - static Format const * from_id (std::string i); - static std::vector<Format const *> all (); - static void setup_formats (); - -protected: - /** @return the ratio */ - virtual float ratio (boost::shared_ptr<const Film> f) const = 0; - - /** libdcp::Size in pixels of the images that we should - * put in a DCP for this ratio. This size will not correspond - * to the ratio when we are doing things like 16:9 in a Flat frame. - */ - libdcp::Size _dcp_size; - /** id for use in metadata */ - std::string _id; - /** nickname (e.g. Flat, Scope) */ - std::string _nickname; - std::string _dci_name; - -private: - /** all available formats */ - static std::vector<Format const *> _formats; -}; - -/** @class FixedFormat - * @brief Class to describe a format whose ratio is fixed regardless - * of source size. - */ -class FixedFormat : public Format -{ -public: - FixedFormat (float, libdcp::Size, std::string, std::string, std::string); - - float ratio (boost::shared_ptr<const Film>) const { - return _ratio; - } - - std::string name () const; - -private: - - float _ratio; -}; - -class VariableFormat : public Format -{ -public: - VariableFormat (libdcp::Size, std::string, std::string, std::string); - - float ratio (boost::shared_ptr<const Film> f) const; - - std::string name () const; -}; diff --git a/src/lib/gain.cc b/src/lib/gain.cc deleted file mode 100644 index ccd779d71..000000000 --- a/src/lib/gain.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include "gain.h" - -using boost::shared_ptr; - -/** @param gain gain in dB */ -Gain::Gain (shared_ptr<Log> log, float gain) - : AudioProcessor (log) - , _gain (gain) -{ - -} - -void -Gain::process_audio (shared_ptr<const AudioBuffers> b) -{ - if (_gain != 0) { - float const linear_gain = pow (10, _gain / 20); - for (int i = 0; i < b->channels(); ++i) { - for (int j = 0; j < b->frames(); ++j) { - b->data(i)[j] *= linear_gain; - } - } - } - - Audio (b); -} diff --git a/src/lib/i18n.h b/src/lib/i18n.h index 46bb1d565..890313bc6 100644 --- a/src/lib/i18n.h +++ b/src/lib/i18n.h @@ -19,5 +19,5 @@ #include <libintl.h> -#define _(x) dgettext ("libdvdomatic", x) +#define _(x) dgettext ("libdcpomatic", x) #define N_(x) x diff --git a/src/lib/image.cc b/src/lib/image.cc index f28652d4e..722ff5d3c 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -43,8 +43,9 @@ extern "C" { #include "i18n.h" -using namespace std; -using namespace boost; +using std::string; +using std::min; +using boost::shared_ptr; using libdcp::Size; void @@ -53,22 +54,28 @@ 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. - */ int -Image::lines (int n) const +Image::line_factor (int n) const { if (n == 0) { - return size().height; + return 1; } - + AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format); if (!d) { throw PixelFormatError (N_("lines()"), _pixel_format); } - return size().height / pow(2.0f, d->log2_chroma_h); + return pow (2.0f, d->log2_chroma_h); +} + +/** @param n Component index. + * @return Number of lines in the image for the given component. + */ +int +Image::lines (int n) const +{ + return rint (ceil (static_cast<double>(size().height) / line_factor (n))); } /** @return Number of components */ @@ -121,7 +128,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) * @param scaler Scaler to use. */ shared_ptr<Image> -Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool result_aligned) const +Image::scale_and_convert_to_rgb (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) const { assert (scaler); /* Empirical testing suggests that sws_scale() will crash if @@ -129,14 +136,11 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons */ assert (aligned ()); - libdcp::Size content_size = out_size; - content_size.width -= (padding * 2); - - shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, result_aligned)); + shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), - content_size.width, content_size.height, PIX_FMT_RGB24, + out_size.width, out_size.height, PIX_FMT_RGB24, scaler->ffmpeg_id (), 0, 0, 0 ); @@ -148,28 +152,6 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons rgb->data(), rgb->stride() ); - /* Put the image in the right place in a black frame if are padding; this is - a bit grubby and expensive, but probably inconsequential in the great - scheme of things. - */ - if (padding > 0) { - shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned)); - padded_rgb->make_black (); - - /* XXX: we are cheating a bit here; we know the frame is RGB so we can - make assumptions about its composition. - */ - uint8_t* p = padded_rgb->data()[0] + padding * 3; - uint8_t* q = rgb->data()[0]; - for (int j = 0; j < rgb->lines(0); ++j) { - memcpy (p, q, rgb->line_size()[0]); - p += padded_rgb->stride()[0]; - q += rgb->stride()[0]; - } - - rgb = padded_rgb; - } - sws_freeContext (scale_context); return rgb; @@ -232,12 +214,12 @@ Image::crop (Crop crop, bool aligned) const 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* in_p = data()[c] + (crop.top / out->line_factor(c)) * stride()[c]; uint8_t* out_p = out->data()[c]; - - for (int y = 0; y < cropped_size.height; ++y) { + + for (int y = 0; y < out->lines(c); ++y) { memcpy (out_p, in_p + crop_left_in_bytes, cropped_width_in_bytes); in_p += stride()[c]; out_p += out->stride()[c]; @@ -385,6 +367,21 @@ Image::alpha_blend (shared_ptr<const Image> other, Position position) } void +Image::copy (shared_ptr<const Image> other, Position position) +{ + /* Only implemented for RGB24 onto RGB24 so far */ + assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGB24); + assert (position.x >= 0 && position.y >= 0); + + int const N = min (position.x + other->size().width, size().width) - position.x; + for (int ty = position.y, oy = 0; ty < size().height && oy < other->size().height; ++ty, ++oy) { + uint8_t * const tp = data()[0] + ty * stride()[0] + position.x * 3; + uint8_t * const op = other->data()[0] + oy * other->stride()[0]; + memcpy (tp, op, N * 3); + } +} + +void Image::read_from_socket (shared_ptr<Socket> socket) { for (int i = 0; i < components(); ++i) { @@ -512,12 +509,11 @@ SimpleImage::SimpleImage (AVFrame* frame) } } -SimpleImage::SimpleImage (shared_ptr<const Image> other) +SimpleImage::SimpleImage (shared_ptr<const Image> other, bool aligned) : Image (*other.get()) + , _size (other->size()) + , _aligned (aligned) { - _size = other->size (); - _aligned = true; - allocate (); for (int i = 0; i < components(); ++i) { diff --git a/src/lib/image.h b/src/lib/image.h index 70dacfaee..5407ce66e 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -21,8 +21,8 @@ * @brief A set of classes to describe video images. */ -#ifndef DVDOMATIC_IMAGE_H -#define DVDOMATIC_IMAGE_H +#ifndef DCPOMATIC_IMAGE_H +#define DCPOMATIC_IMAGE_H #include <string> #include <boost/shared_ptr.hpp> @@ -69,12 +69,14 @@ public: virtual bool aligned () const = 0; int components () const; + int line_factor (int) const; int lines (int) const; - boost::shared_ptr<Image> scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool aligned) const; + boost::shared_ptr<Image> scale_and_convert_to_rgb (libdcp::Size, Scaler const *, bool) const; boost::shared_ptr<Image> scale (libdcp::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); + void copy (boost::shared_ptr<const Image> image, Position pos); boost::shared_ptr<Image> crop (Crop c, bool aligned) const; void make_black (); @@ -108,7 +110,7 @@ public: SimpleImage (AVPixelFormat, libdcp::Size, bool); SimpleImage (AVFrame *); SimpleImage (SimpleImage const &); - SimpleImage (boost::shared_ptr<const Image>); + SimpleImage (boost::shared_ptr<const Image>, bool); SimpleImage& operator= (SimpleImage const &); ~SimpleImage (); diff --git a/src/lib/options.h b/src/lib/imagemagick.h index 0d2c07fd5..5a1712a3a 100644 --- a/src/lib/options.h +++ b/src/lib/imagemagick.h @@ -17,27 +17,19 @@ */ -#ifndef DVDOMATIC_OPTIONS_H -#define DVDOMATIC_OPTIONS_H +class ImageMagickContent; -/** @file src/options.h - * @brief Options for a decoding operation. - */ - -class DecodeOptions +class ImageMagick { public: - DecodeOptions () - : decode_video (true) - , decode_audio (true) - , decode_subtitles (false) - , video_sync (true) + ImageMagick (boost::shared_ptr<const ImageMagickContent> c) + : _imagemagick_content (c) {} - bool decode_video; - bool decode_audio; - bool decode_subtitles; - bool video_sync; -}; + boost::shared_ptr<const ImageMagickContent> content () const { + return _imagemagick_content; + } -#endif +protected: + boost::shared_ptr<const ImageMagickContent> _imagemagick_content; +}; diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc new file mode 100644 index 000000000..2fd65ffa0 --- /dev/null +++ b/src/lib/imagemagick_content.cc @@ -0,0 +1,109 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libcxml/cxml.h> +#include "imagemagick_content.h" +#include "imagemagick_examiner.h" +#include "config.h" +#include "compose.hpp" +#include "film.h" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using boost::shared_ptr; + +ImageMagickContent::ImageMagickContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , VideoContent (f, p) +{ + +} + +ImageMagickContent::ImageMagickContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + : Content (f, node) + , VideoContent (f, node) +{ + +} + +string +ImageMagickContent::summary () const +{ + return String::compose (_("Image: %1"), file().filename().string()); +} + +bool +ImageMagickContent::valid_file (boost::filesystem::path f) +{ + string ext = f.extension().string(); + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); + return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp"); +} + +void +ImageMagickContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("ImageMagick"); + Content::as_xml (node); + VideoContent::as_xml (node); +} + +void +ImageMagickContent::examine (shared_ptr<Job> job) +{ + Content::examine (job); + + shared_ptr<const Film> film = _film.lock (); + assert (film); + + shared_ptr<ImageMagickExaminer> examiner (new ImageMagickExaminer (film, shared_from_this())); + + set_video_length (Config::instance()->default_still_length() * 24); + take_from_video_examiner (examiner); +} + +shared_ptr<Content> +ImageMagickContent::clone () const +{ + return shared_ptr<Content> (new ImageMagickContent (*this)); +} + +void +ImageMagickContent::set_video_length (VideoContent::Frame len) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _video_length = len; + } + + signal_changed (ContentProperty::LENGTH); +} + +Time +ImageMagickContent::length () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + + FrameRateConversion frc (24, film->dcp_video_frame_rate ()); + return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate (); +} + diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h new file mode 100644 index 000000000..04425af08 --- /dev/null +++ b/src/lib/imagemagick_content.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_IMAGEMAGICK_CONTENT_H +#define DCPOMATIC_IMAGEMAGICK_CONTENT_H + +#include <boost/enable_shared_from_this.hpp> +#include "video_content.h" + +namespace cxml { + class Node; +} + +class ImageMagickContent : public VideoContent +{ +public: + ImageMagickContent (boost::shared_ptr<const Film>, boost::filesystem::path); + ImageMagickContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + + boost::shared_ptr<ImageMagickContent> shared_from_this () { + return boost::dynamic_pointer_cast<ImageMagickContent> (Content::shared_from_this ()); + }; + + void examine (boost::shared_ptr<Job>); + std::string summary () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr<Content> clone () const; + Time length () const; + + void set_video_length (VideoContent::Frame); + + static bool valid_file (boost::filesystem::path); +}; + +#endif diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index 5ce22c296..04d3d9df7 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2013 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 @@ -20,6 +20,7 @@ #include <iostream> #include <boost/filesystem.hpp> #include <Magick++.h> +#include "imagemagick_content.h" #include "imagemagick_decoder.h" #include "image.h" #include "film.h" @@ -31,66 +32,36 @@ using std::cout; using boost::shared_ptr; using libdcp::Size; -ImageMagickDecoder::ImageMagickDecoder ( - boost::shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) - , VideoDecoder (f, o) +ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c) + : Decoder (f) + , VideoDecoder (f) + , ImageMagick (c) { - if (boost::filesystem::is_directory (_film->content_path())) { - for ( - boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (_film->content_path()); - i != boost::filesystem::directory_iterator(); - ++i) { - - if (still_image_file (i->path().string())) { - _files.push_back (i->path().string()); - } - } - } else { - _files.push_back (_film->content_path ()); - } - - _iter = _files.begin (); -} - -libdcp::Size -ImageMagickDecoder::native_size () const -{ - if (_files.empty ()) { - throw DecodeError (_("no still image files found")); - } - - /* Look at the first file and assume its size holds for all */ - using namespace MagickCore; - Magick::Image* image = new Magick::Image (_film->content_path ()); - libdcp::Size const s = libdcp::Size (image->columns(), image->rows()); - delete image; - return s; } -bool +void ImageMagickDecoder::pass () { - if (_iter == _files.end()) { - if (video_frame() >= _film->still_duration_in_frames()) { - return true; - } + if (_video_position >= _imagemagick_content->video_length ()) { + return; + } - emit_video (_image, true, double (video_frame()) / frames_per_second()); - return false; + if (_image) { + video (_image, true, _video_position); + return; } + + Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ()); + _video_size = libdcp::Size (magick_image->columns(), magick_image->rows()); - Magick::Image* magick_image = new Magick::Image (_film->content_path ()); - - libdcp::Size size = native_size (); - shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false)); + _image.reset (new SimpleImage (PIX_FMT_RGB24, _video_size.get(), 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) { + uint8_t* p = _image->data()[0]; + for (int y = 0; y < _video_size->height; ++y) { + for (int x = 0; x < _video_size->width; ++x) { Magick::Color c = magick_image->pixelColor (x, y); *p++ = c.redQuantum() * 255 / QuantumRange; *p++ = c.greenQuantum() * 255 / QuantumRange; @@ -100,59 +71,25 @@ ImageMagickDecoder::pass () delete magick_image; - _image = image->crop (_film->crop(), true); - - emit_video (_image, false, double (video_frame()) / frames_per_second()); - - ++_iter; - return false; + video (_image, false, _video_position); } -PixelFormat -ImageMagickDecoder::pixel_format () const -{ - /* XXX: always true? */ - return PIX_FMT_RGB24; -} - -bool -ImageMagickDecoder::seek_to_last () -{ - if (_iter == _files.end()) { - _iter = _files.begin(); - } else { - --_iter; - } - - return false; -} - -bool -ImageMagickDecoder::seek (double t) +void +ImageMagickDecoder::seek (VideoContent::Frame frame) { - int const f = t * frames_per_second(); - - _iter = _files.begin (); - for (int i = 0; i < f; ++i) { - if (_iter == _files.end()) { - return true; - } - ++_iter; - } - - return false; + _video_position = frame; } void -ImageMagickDecoder::film_changed (Film::Property p) +ImageMagickDecoder::seek_back () { - if (p == Film::CROP) { - OutputChanged (); + if (_video_position > 0) { + _video_position--; } } -float -ImageMagickDecoder::frames_per_second () const +bool +ImageMagickDecoder::done () const { - return _film->source_frame_rate (); + return _video_position >= _imagemagick_content->video_length (); } diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index 80a08f81f..286f47337 100644 --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2013 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 @@ -18,67 +18,27 @@ */ #include "video_decoder.h" +#include "imagemagick.h" namespace Magick { class Image; } -class ImageMagickDecoder : public VideoDecoder +class ImageMagickContent; + +class ImageMagickDecoder : public VideoDecoder, public ImageMagick { public: - ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions); - - float frames_per_second () const; - - libdcp::Size native_size () const; - - SourceFrame length () const { - /* We don't know */ - return 0; - } - - int audio_channels () const { - return 0; - } - - int audio_sample_rate () const { - return 0; - } + ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>); - int64_t audio_channel_layout () const { - return 0; - } + /* Decoder */ - bool seek (double); - bool seek_to_last (); - -protected: - bool pass (); - PixelFormat pixel_format () const; - - int time_base_numerator () const { - return 0; - } - - int time_base_denominator () const { - return 0; - } - - int sample_aspect_ratio_numerator () const { - /* XXX */ - return 1; - } - - int sample_aspect_ratio_denominator () const { - /* XXX */ - return 1; - } + void pass (); + void seek (VideoContent::Frame); + void seek_back (); + bool done () const; private: - void film_changed (Film::Property); - - std::list<std::string> _files; - std::list<std::string>::iterator _iter; - boost::shared_ptr<Image> _image; + mutable boost::optional<libdcp::Size> _video_size; }; diff --git a/src/lib/imagemagick_examiner.cc b/src/lib/imagemagick_examiner.cc new file mode 100644 index 000000000..0eee0d425 --- /dev/null +++ b/src/lib/imagemagick_examiner.cc @@ -0,0 +1,63 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <iostream> +#include <Magick++.h> +#include "imagemagick_content.h" +#include "imagemagick_examiner.h" +#include "film.h" + +#include "i18n.h" + +using std::cout; +using boost::shared_ptr; + +ImageMagickExaminer::ImageMagickExaminer (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c) + : ImageMagick (c) + , _film (f) +{ + using namespace MagickCore; + Magick::Image* image = new Magick::Image (_imagemagick_content->file().string()); + _video_size = libdcp::Size (image->columns(), image->rows()); + delete image; +} + +libdcp::Size +ImageMagickExaminer::video_size () const +{ + return _video_size; +} + +int +ImageMagickExaminer::video_length () const +{ + return _imagemagick_content->video_length (); +} + +float +ImageMagickExaminer::video_frame_rate () const +{ + boost::shared_ptr<const Film> f = _film.lock (); + if (!f) { + return 24; + } + + return f->dcp_video_frame_rate (); +} + diff --git a/src/lib/imagemagick_examiner.h b/src/lib/imagemagick_examiner.h new file mode 100644 index 000000000..801ede442 --- /dev/null +++ b/src/lib/imagemagick_examiner.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "imagemagick.h" +#include "video_examiner.h" + +namespace Magick { + class Image; +} + +class ImageMagickContent; + +class ImageMagickExaminer : public ImageMagick, public VideoExaminer +{ +public: + ImageMagickExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>); + + float video_frame_rate () const; + libdcp::Size video_size () const; + VideoContent::Frame video_length () const; + +private: + boost::weak_ptr<const Film> _film; + libdcp::Size _video_size; +}; diff --git a/src/lib/job.cc b/src/lib/job.cc index 8bb43a91f..080d1eaf6 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -36,9 +36,7 @@ using std::list; using std::stringstream; using boost::shared_ptr; -/** @param s Film that we are operating on. - */ -Job::Job (shared_ptr<Film> f) +Job::Job (shared_ptr<const Film> f) : _film (f) , _thread (0) , _state (NEW) @@ -95,7 +93,7 @@ Job::run_wrapper () set_state (FINISHED_ERROR); set_error ( e.what (), - _("It is not known what caused this error. The best idea is to report the problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)") + _("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)") ); } catch (...) { @@ -104,7 +102,7 @@ Job::run_wrapper () set_state (FINISHED_ERROR); set_error ( _("Unknown error"), - _("It is not known what caused this error. The best idea is to report the problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)") + _("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)") ); } @@ -204,7 +202,7 @@ Job::set_progress (float p) boost::this_thread::interruption_point (); if (paused ()) { - dvdomatic_sleep (1); + dcpomatic_sleep (1); } } diff --git a/src/lib/job.h b/src/lib/job.h index 37fa56d20..791a9101b 100644 --- a/src/lib/job.h +++ b/src/lib/job.h @@ -21,8 +21,8 @@ * @brief A parent class to represent long-running tasks which are run in their own thread. */ -#ifndef DVDOMATIC_JOB_H -#define DVDOMATIC_JOB_H +#ifndef DCPOMATIC_JOB_H +#define DCPOMATIC_JOB_H #include <string> #include <boost/thread/mutex.hpp> @@ -38,7 +38,7 @@ class Film; class Job : public boost::enable_shared_from_this<Job> { public: - Job (boost::shared_ptr<Film> s); + Job (boost::shared_ptr<const Film>); virtual ~Job() {} /** @return user-readable name of this job */ @@ -71,7 +71,7 @@ public: void descend (float); float overall_progress () const; - /** Emitted by the JobManagerView from the UI thread */ + /** Emitted from the UI thread when the job is finished */ boost::signals2::signal<void()> Finished; protected: @@ -91,8 +91,7 @@ protected: void set_state (State); void set_error (std::string s, std::string d); - /** Film for this job */ - boost::shared_ptr<Film> _film; + boost::shared_ptr<const Film> _film; private: diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc index 910597628..f96275467 100644 --- a/src/lib/job_manager.cc +++ b/src/lib/job_manager.cc @@ -126,7 +126,7 @@ JobManager::scheduler () } } - dvdomatic_sleep (1); + dcpomatic_sleep (1); } } diff --git a/src/lib/log.h b/src/lib/log.h index 3a2cfcbfd..3ad6516c1 100644 --- a/src/lib/log.h +++ b/src/lib/log.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_LOG_H -#define DVDOMATIC_LOG_H +#ifndef DCPOMATIC_LOG_H +#define DCPOMATIC_LOG_H /** @file src/log.h * @brief A very simple logging class. diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc deleted file mode 100644 index 4acb82afa..000000000 --- a/src/lib/matcher.cc +++ /dev/null @@ -1,224 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include "matcher.h" -#include "image.h" -#include "log.h" - -#include "i18n.h" - -using std::min; -using std::cout; -using std::list; -using boost::shared_ptr; - -Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second) - : Processor (log) - , _sample_rate (sample_rate) - , _frames_per_second (frames_per_second) - , _video_frames (0) - , _audio_frames (0) - , _had_first_video (false) - , _had_first_audio (false) -{ - -} - -void -Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t) -{ - _pixel_format = image->pixel_format (); - _size = image->size (); - - _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size())); - - if (!_first_input || t < _first_input.get()) { - _first_input = t; - } - - bool const this_is_first_video = !_had_first_video; - _had_first_video = true; - - if (!_had_first_audio) { - /* No audio yet; we must postpone these data until we have some */ - _pending_video.push_back (VideoRecord (image, same, sub, t)); - } else if (this_is_first_video && _had_first_audio) { - /* First video since we got audio */ - _pending_video.push_back (VideoRecord (image, same, sub, t)); - fix_start (); - } else { - /* Normal running */ - - /* Difference between where this video is and where it should be */ - double const delta = t - _first_input.get() - _video_frames / _frames_per_second; - double const one_frame = 1 / _frames_per_second; - - if (delta > one_frame) { - /* Insert frames to make up the difference */ - int const extra = rint (delta / one_frame); - for (int i = 0; i < extra; ++i) { - repeat_last_video (); - _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second)); - } - } - - if (delta > -one_frame) { - Video (image, same, sub); - ++_video_frames; - } else { - /* We are omitting a frame to keep things right */ - _log->log (String::compose ("Frame removed at %1s; delta %2; first input was at %3", t, delta, _first_input.get())); - } - - _last_image = image; - _last_subtitle = sub; - } -} - -void -Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t) -{ - _channels = b->channels (); - - _log->log (String::compose ( - "Matcher audio (%1 frames) @ %2 [video=%3, audio=%4, pending_video=%5, pending_audio=%6]", - b->frames(), t, _video_frames, _audio_frames, _pending_video.size(), _pending_audio.size() - ) - ); - - if (!_first_input || t < _first_input.get()) { - _first_input = t; - } - - bool const this_is_first_audio = !_had_first_audio; - _had_first_audio = true; - - if (!_had_first_video) { - /* No video yet; we must postpone these data until we have some */ - _pending_audio.push_back (AudioRecord (b, t)); - } else if (this_is_first_audio && _had_first_video) { - /* First audio since we got video */ - _pending_audio.push_back (AudioRecord (b, t)); - fix_start (); - } else { - /* Normal running. We assume audio time stamps are consecutive, so there's no equivalent of - the checking / insertion of repeat frames that there is for video. - */ - Audio (b); - _audio_frames += b->frames (); - } -} - -void -Matcher::process_end () -{ - if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) { - /* We won't do anything */ - return; - } - - _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames", - _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames)); - - match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second)); -} - -void -Matcher::fix_start () -{ - assert (!_pending_video.empty ()); - assert (!_pending_audio.empty ()); - - _log->log (String::compose ("Fixing start; video at %1, audio at %2", _pending_video.front().time, _pending_audio.front().time)); - - match (_pending_video.front().time - _pending_audio.front().time); - - for (list<VideoRecord>::iterator i = _pending_video.begin(); i != _pending_video.end(); ++i) { - process_video (i->image, i->same, i->subtitle, i->time); - } - - _pending_video.clear (); - - for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) { - process_audio (i->audio, i->time); - } - - _pending_audio.clear (); -} - -void -Matcher::match (double extra_video_needed) -{ - _log->log (String::compose ("Match %1", extra_video_needed)); - - if (extra_video_needed > 0) { - - /* Emit black video frames */ - - int const black_video_frames = ceil (extra_video_needed * _frames_per_second); - - _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames)); - - shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true)); - black->make_black (); - for (int i = 0; i < black_video_frames; ++i) { - Video (black, i != 0, shared_ptr<Subtitle>()); - ++_video_frames; - } - - extra_video_needed -= black_video_frames / _frames_per_second; - } - - if (extra_video_needed < 0) { - - /* Emit silence */ - - int64_t to_do = -extra_video_needed * _sample_rate; - _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do)); - - /* Do things in half second blocks as I think there may be limits - to what FFmpeg (and in particular the resampler) can cope with. - */ - int64_t const block = _sample_rate / 2; - shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block)); - b->make_silent (); - - while (to_do > 0) { - int64_t const this_time = min (to_do, block); - b->set_frames (this_time); - Audio (b); - _audio_frames += b->frames (); - to_do -= this_time; - } - } -} - -void -Matcher::repeat_last_video () -{ - if (!_last_image) { - shared_ptr<Image> im (new SimpleImage (_pixel_format.get(), _size.get(), true)); - im->make_black (); - _last_image = im; - } - - Video (_last_image, true, _last_subtitle); - ++_video_frames; -} - diff --git a/src/lib/matcher.h b/src/lib/matcher.h deleted file mode 100644 index 61fd81436..000000000 --- a/src/lib/matcher.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <boost/optional.hpp> -#include "processor.h" - -class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource -{ -public: - Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second); - void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double); - void process_audio (boost::shared_ptr<const AudioBuffers>, double); - void process_end (); - -private: - void fix_start (); - void match (double); - void repeat_last_video (); - - int _sample_rate; - float _frames_per_second; - int _video_frames; - int64_t _audio_frames; - boost::optional<AVPixelFormat> _pixel_format; - boost::optional<libdcp::Size> _size; - boost::optional<int> _channels; - - struct VideoRecord { - VideoRecord (boost::shared_ptr<const Image> i, bool s, boost::shared_ptr<Subtitle> u, double t) - : image (i) - , same (s) - , subtitle (u) - , time (t) - {} - - boost::shared_ptr<const Image> image; - bool same; - boost::shared_ptr<Subtitle> subtitle; - double time; - }; - - struct AudioRecord { - AudioRecord (boost::shared_ptr<const AudioBuffers> a, double t) - : audio (a) - , time (t) - {} - - boost::shared_ptr<const AudioBuffers> audio; - double time; - }; - - std::list<VideoRecord> _pending_video; - std::list<AudioRecord> _pending_audio; - - boost::optional<double> _first_input; - boost::shared_ptr<const Image> _last_image; - boost::shared_ptr<Subtitle> _last_subtitle; - - bool _had_first_video; - bool _had_first_audio; -}; diff --git a/src/lib/player.cc b/src/lib/player.cc new file mode 100644 index 000000000..9969fbf9e --- /dev/null +++ b/src/lib/player.cc @@ -0,0 +1,499 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <stdint.h> +#include "player.h" +#include "film.h" +#include "ffmpeg_decoder.h" +#include "ffmpeg_content.h" +#include "imagemagick_decoder.h" +#include "imagemagick_content.h" +#include "sndfile_decoder.h" +#include "sndfile_content.h" +#include "playlist.h" +#include "job.h" +#include "image.h" +#include "ratio.h" +#include "resampler.h" + +using std::list; +using std::cout; +using std::min; +using std::max; +using std::vector; +using std::pair; +using std::map; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; + +#define DEBUG_PLAYER 1 + +class Piece +{ +public: + Piece (shared_ptr<Content> c) + : content (c) + , video_position (c->start ()) + , audio_position (c->start ()) + {} + + Piece (shared_ptr<Content> c, shared_ptr<Decoder> d) + : content (c) + , decoder (d) + , video_position (c->start ()) + , audio_position (c->start ()) + {} + + shared_ptr<Content> content; + shared_ptr<Decoder> decoder; + Time video_position; + Time audio_position; +}; + +#ifdef DEBUG_PLAYER +std::ostream& operator<<(std::ostream& s, Piece const & p) +{ + if (dynamic_pointer_cast<FFmpegContent> (p.content)) { + s << "\tffmpeg "; + } else if (dynamic_pointer_cast<ImageMagickContent> (p.content)) { + s << "\timagemagick"; + } else if (dynamic_pointer_cast<SndfileContent> (p.content)) { + s << "\tsndfile "; + } + + s << " at " << p.content->start() << " until " << p.content->end(); + + return s; +} +#endif + +Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) + : _film (f) + , _playlist (p) + , _video (true) + , _audio (true) + , _have_valid_pieces (false) + , _video_position (0) + , _audio_position (0) + , _audio_buffers (f->dcp_audio_channels(), 0) +{ + _playlist->Changed.connect (bind (&Player::playlist_changed, this)); + _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2)); + set_video_container_size (_film->container()->size (_film->full_frame ())); +} + +void +Player::disable_video () +{ + _video = false; +} + +void +Player::disable_audio () +{ + _audio = false; +} + +bool +Player::pass () +{ + if (!_have_valid_pieces) { + setup_pieces (); + _have_valid_pieces = true; + } + +#ifdef DEBUG_PLAYER + cout << "= PASS\n"; +#endif + + Time earliest_t = TIME_MAX; + shared_ptr<Piece> earliest; + enum { + VIDEO, + AUDIO + } type = VIDEO; + + for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + if ((*i)->decoder->done ()) { + continue; + } + + if (dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) { + if ((*i)->video_position < earliest_t) { + earliest_t = (*i)->video_position; + earliest = *i; + type = VIDEO; + } + } + + if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) { + if ((*i)->audio_position < earliest_t) { + earliest_t = (*i)->audio_position; + earliest = *i; + type = AUDIO; + } + } + } + + if (!earliest) { +#ifdef DEBUG_PLAYER + cout << "no earliest piece.\n"; +#endif + + flush (); + return true; + } + + switch (type) { + case VIDEO: + if (earliest_t > _video_position) { +#ifdef DEBUG_PLAYER + cout << "no video here; emitting black frame.\n"; +#endif + emit_black (); + } else { +#ifdef DEBUG_PLAYER + cout << "Pass " << *earliest << "\n"; +#endif + earliest->decoder->pass (); + } + break; + + case AUDIO: + if (earliest_t > _audio_position) { +#ifdef DEBUG_PLAYER + cout << "no audio here; emitting silence.\n"; +#endif + emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position)); + } else { +#ifdef DEBUG_PLAYER + cout << "Pass " << *earliest << "\n"; +#endif + earliest->decoder->pass (); + } + break; + } + +#ifdef DEBUG_PLAYER + cout << "\tpost pass " << _video_position << " " << _audio_position << "\n"; +#endif + + return false; +} + +void +Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, bool same, VideoContent::Frame frame) +{ + shared_ptr<Piece> piece = weak_piece.lock (); + if (!piece) { + return; + } + + shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content); + assert (content); + + FrameRateConversion frc (content->video_frame_rate(), _film->dcp_video_frame_rate()); + if (frc.skip && (frame % 2) == 1) { + return; + } + + image = image->crop (content->crop(), true); + + libdcp::Size const image_size = content->ratio()->size (_video_container_size); + + image = image->scale_and_convert_to_rgb (image_size, _film->scaler(), true); + +#if 0 + if (film->with_subtitles ()) { + shared_ptr<Subtitle> sub; + if (_timed_subtitle && _timed_subtitle->displayed_at (t)) { + sub = _timed_subtitle->subtitle (); + } + + if (sub) { + dcpomatic::Rect const tx = subtitle_transformed_area ( + float (image_size.width) / content->video_size().width, + float (image_size.height) / content->video_size().height, + sub->area(), film->subtitle_offset(), film->subtitle_scale() + ); + + shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true); + image->alpha_blend (im, tx.position()); + } + } +#endif + + if (image_size != _video_container_size) { + assert (image_size.width <= _video_container_size.width); + assert (image_size.height <= _video_container_size.height); + shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true)); + im->make_black (); + im->copy (image, Position ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2)); + image = im; + } + + Time time = content->start() + (frame * frc.factor() * TIME_HZ / _film->dcp_video_frame_rate()); + + Video (image, same, time); + time += TIME_HZ / _film->dcp_video_frame_rate(); + + if (frc.repeat) { + Video (image, true, time); + time += TIME_HZ / _film->dcp_video_frame_rate(); + } + + _video_position = piece->video_position = time; +} + +void +Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame) +{ + shared_ptr<Piece> piece = weak_piece.lock (); + if (!piece) { + return; + } + + shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content); + assert (content); + + if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) { + audio = resampler(content)->run (audio); + } + + /* Remap channels */ + shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->dcp_audio_channels(), audio->frames())); + dcp_mapped->make_silent (); + list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp (); + for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) { + dcp_mapped->accumulate_channel (audio.get(), i->first, i->second); + } + + /* The time of this audio may indicate that some of our buffered audio is not going to + be added to any more, so it can be emitted. + */ + + Time const time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate()); + + if (time > _audio_position) { + /* We can emit some audio from our buffers */ + OutputAudioFrame const N = _film->time_to_audio_frames (time - _audio_position); + assert (N <= _audio_buffers.frames()); + shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N)); + emit->copy_from (&_audio_buffers, N, 0, 0); + Audio (emit, _audio_position); + _audio_position = piece->audio_position = time + _film->audio_frames_to_time (N); + + /* And remove it from our buffers */ + if (_audio_buffers.frames() > N) { + _audio_buffers.move (N, 0, _audio_buffers.frames() - N); + } + _audio_buffers.set_frames (_audio_buffers.frames() - N); + } + + /* Now accumulate the new audio into our buffers */ + _audio_buffers.ensure_size (_audio_buffers.frames() + audio->frames()); + _audio_buffers.accumulate_frames (audio.get(), 0, 0, audio->frames ()); + _audio_buffers.set_frames (_audio_buffers.frames() + audio->frames()); +} + +void +Player::flush () +{ + if (_audio_buffers.frames() > 0) { + shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), _audio_buffers.frames())); + emit->copy_from (&_audio_buffers, _audio_buffers.frames(), 0, 0); + Audio (emit, _audio_position); + _audio_position += _film->audio_frames_to_time (_audio_buffers.frames ()); + _audio_buffers.set_frames (0); + } + + while (_video_position < _audio_position) { + emit_black (); + } + + while (_audio_position < _video_position) { + emit_silence (_film->time_to_audio_frames (_video_position - _audio_position)); + } + +} + +/** @return true on error */ +void +Player::seek (Time t) +{ + if (!_have_valid_pieces) { + setup_pieces (); + _have_valid_pieces = true; + } + + if (_pieces.empty ()) { + return; + } + + for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content); + if (!vc) { + continue; + } + + Time s = t - vc->start (); + s = max (static_cast<Time> (0), s); + s = min (vc->length(), s); + + FrameRateConversion frc (vc->video_frame_rate(), _film->dcp_video_frame_rate()); + VideoContent::Frame f = s * _film->dcp_video_frame_rate() / (frc.factor() * TIME_HZ); + dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f); + } + + /* XXX: don't seek audio because we don't need to... */ +} + + +void +Player::seek_back () +{ + +} + +void +Player::setup_pieces () +{ + list<shared_ptr<Piece> > old_pieces = _pieces; + + _pieces.clear (); + + Playlist::ContentList content = _playlist->content (); + sort (content.begin(), content.end(), ContentSorter ()); + + for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) { + + shared_ptr<Piece> piece (new Piece (*i)); + + /* XXX: into content? */ + + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + if (fc) { + shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio)); + + fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3)); + fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2)); + + piece->decoder = fd; + } + + shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i); + if (ic) { + shared_ptr<ImageMagickDecoder> id; + + /* See if we can re-use an old ImageMagickDecoder */ + for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) { + shared_ptr<ImageMagickDecoder> imd = dynamic_pointer_cast<ImageMagickDecoder> ((*j)->decoder); + if (imd && imd->content() == ic) { + id = imd; + } + } + + if (!id) { + id.reset (new ImageMagickDecoder (_film, ic)); + id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3)); + } + + piece->decoder = id; + } + + shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); + if (sc) { + shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc)); + sd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2)); + + piece->decoder = sd; + } + + _pieces.push_back (piece); + } + +#ifdef DEBUG_PLAYER + cout << "=== Player setup:\n"; + for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + cout << *(i->get()) << "\n"; + } +#endif +} + +void +Player::content_changed (weak_ptr<Content> w, int p) +{ + shared_ptr<Content> c = w.lock (); + if (!c) { + return; + } + + if (p == ContentProperty::START || p == ContentProperty::LENGTH) { + _have_valid_pieces = false; + } +} + +void +Player::playlist_changed () +{ + _have_valid_pieces = false; +} + +void +Player::set_video_container_size (libdcp::Size s) +{ + _video_container_size = s; + _black_frame.reset (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true)); + _black_frame->make_black (); +} + +shared_ptr<Resampler> +Player::resampler (shared_ptr<AudioContent> c) +{ + map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c); + if (i != _resamplers.end ()) { + return i->second; + } + + shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); + _resamplers[c] = r; + return r; +} + +void +Player::emit_black () +{ + /* XXX: use same here */ + Video (_black_frame, false, _video_position); + _video_position += _film->video_frames_to_time (1); +} + +void +Player::emit_silence (OutputAudioFrame most) +{ + OutputAudioFrame N = min (most, _film->dcp_audio_frame_rate() / 2); + shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->dcp_audio_channels(), N)); + silence->make_silent (); + Audio (silence, _audio_position); + _audio_position += _film->audio_frames_to_time (N); +} + + + diff --git a/src/lib/player.h b/src/lib/player.h new file mode 100644 index 000000000..bbdb14ec2 --- /dev/null +++ b/src/lib/player.h @@ -0,0 +1,105 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_PLAYER_H +#define DCPOMATIC_PLAYER_H + +#include <list> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include "playlist.h" +#include "audio_buffers.h" +#include "content.h" + +class Job; +class Film; +class Playlist; +class AudioContent; +class Piece; +class Image; +class Resampler; + +/** @class Player + * @brief A class which can `play' a Playlist; emitting its audio and video. + */ + +class Player : public boost::enable_shared_from_this<Player> +{ +public: + Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>); + + void disable_video (); + void disable_audio (); + + bool pass (); + void seek (Time); + void seek_back (); + + Time video_position () const { + return _video_position; + } + + void set_video_container_size (libdcp::Size); + + /** Emitted when a video frame is ready. + * First parameter is the video image. + * Second parameter is true if the image is the same as the last one that was emitted. + * Third parameter is the time. + */ + boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video; + + /** Emitted when some audio data is ready */ + boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio; + +private: + + void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame); + void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); + void setup_pieces (); + void playlist_changed (); + void content_changed (boost::weak_ptr<Content>, int); + void do_seek (Time, bool); + void flush (); + void emit_black (); + void emit_silence (OutputAudioFrame); + boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>); + + boost::shared_ptr<const Film> _film; + boost::shared_ptr<const Playlist> _playlist; + + bool _video; + bool _audio; + + /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */ + bool _have_valid_pieces; + std::list<boost::shared_ptr<Piece> > _pieces; + + /** The time after the last video that we emitted */ + Time _video_position; + /** The time after the last audio that we emitted */ + Time _audio_position; + + AudioBuffers _audio_buffers; + + libdcp::Size _video_container_size; + boost::shared_ptr<Image> _black_frame; + std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers; +}; + +#endif diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc new file mode 100644 index 000000000..995067b66 --- /dev/null +++ b/src/lib/playlist.cc @@ -0,0 +1,308 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libcxml/cxml.h> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> +#include "playlist.h" +#include "sndfile_content.h" +#include "sndfile_decoder.h" +#include "video_content.h" +#include "ffmpeg_decoder.h" +#include "ffmpeg_content.h" +#include "imagemagick_decoder.h" +#include "imagemagick_content.h" +#include "job.h" +#include "config.h" +#include "util.h" + +#include "i18n.h" + +using std::list; +using std::cout; +using std::vector; +using std::min; +using std::max; +using std::string; +using std::stringstream; +using boost::optional; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; +using boost::lexical_cast; + +Playlist::Playlist () + : _loop (1) + , _sequence_video (true) + , _sequencing_video (false) +{ + +} + +Playlist::Playlist (shared_ptr<const Playlist> other) + : _loop (other->_loop) +{ + for (ContentList::const_iterator i = other->_content.begin(); i != other->_content.end(); ++i) { + _content.push_back ((*i)->clone ()); + } +} + +Playlist::~Playlist () +{ + _content.clear (); + reconnect (); +} + +void +Playlist::content_changed (weak_ptr<Content> c, int p) +{ + if (p == ContentProperty::LENGTH && _sequence_video && !_sequencing_video) { + _sequencing_video = true; + + ContentList cl = _content; + sort (cl.begin(), cl.end(), ContentSorter ()); + Time last = 0; + for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) { + if (!dynamic_pointer_cast<VideoContent> (*i)) { + continue; + } + + (*i)->set_start (last); + last = (*i)->end (); + } + + _sequencing_video = false; + } + + ContentChanged (c, p); +} + +string +Playlist::video_digest () const +{ + string t; + + for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { + if (!dynamic_pointer_cast<const VideoContent> (*i)) { + continue; + } + + t += (*i)->digest (); + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + if (fc && fc->subtitle_stream()) { + t += fc->subtitle_stream()->id; + } + } + + t += lexical_cast<string> (_loop); + + return md5_digest (t.c_str(), t.length()); +} + +/** @param node <Playlist> node */ +void +Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node) +{ + list<shared_ptr<cxml::Node> > c = node->node_children ("Content"); + for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) { + string const type = (*i)->string_child ("Type"); + + boost::shared_ptr<Content> content; + + if (type == "FFmpeg") { + content.reset (new FFmpegContent (film, *i)); + } else if (type == "ImageMagick") { + content.reset (new ImageMagickContent (film, *i)); + } else if (type == "Sndfile") { + content.reset (new SndfileContent (film, *i)); + } + + _content.push_back (content); + } + + reconnect (); + _loop = node->number_child<int> ("Loop"); + _sequence_video = node->bool_child ("SequenceVideo"); +} + +/** @param node <Playlist> node */ +void +Playlist::as_xml (xmlpp::Node* node) +{ + for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { + (*i)->as_xml (node->add_child ("Content")); + } + + node->add_child("Loop")->add_child_text(lexical_cast<string> (_loop)); + node->add_child("SequenceVideo")->add_child_text(_sequence_video ? "1" : "0"); +} + +void +Playlist::add (shared_ptr<Content> c) +{ + _content.push_back (c); + reconnect (); + Changed (); +} + +void +Playlist::remove (shared_ptr<Content> c) +{ + ContentList::iterator i = _content.begin (); + while (i != _content.end() && *i != c) { + ++i; + } + + if (i != _content.end ()) { + _content.erase (i); + Changed (); + } +} + +void +Playlist::set_loop (int l) +{ + _loop = l; + Changed (); +} + +bool +Playlist::has_subtitles () const +{ + for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i); + if (fc && !fc->subtitle_streams().empty()) { + return true; + } + } + + return false; +} + +class FrameRateCandidate +{ +public: + FrameRateCandidate (float source_, int dcp_) + : source (source_) + , dcp (dcp_) + {} + + float source; + int dcp; +}; + +int +Playlist::best_dcp_frame_rate () const +{ + list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); + + /* Work out what rates we could manage, including those achieved by using skip / repeat. */ + list<FrameRateCandidate> candidates; + + /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ + for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (*i, *i)); + } + + /* Then the skip/repeat ones */ + for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); + candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); + } + + /* Pick the best one, bailing early if we hit an exact match */ + float error = std::numeric_limits<float>::max (); + optional<FrameRateCandidate> best; + list<FrameRateCandidate>::iterator i = candidates.begin(); + while (i != candidates.end()) { + + float this_error = 0; + for (ContentList::const_iterator j = _content.begin(); j != _content.end(); ++j) { + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j); + if (!vc) { + continue; + } + + this_error += fabs (i->source - vc->video_frame_rate ()); + } + + if (this_error < error) { + error = this_error; + best = *i; + } + + ++i; + } + + if (!best) { + return 24; + } + + return best->dcp; +} + +Time +Playlist::length () const +{ + Time len = 0; + for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { + len = max (len, (*i)->end ()); + } + + return len; +} + +void +Playlist::reconnect () +{ + for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) { + i->disconnect (); + } + + _content_connections.clear (); + + for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { + _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2))); + } +} + +Time +Playlist::video_end () const +{ + Time end = 0; + for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { + if (dynamic_pointer_cast<const VideoContent> (*i)) { + end = max (end, (*i)->end ()); + } + } + + return end; +} + +void +Playlist::set_sequence_video (bool s) +{ + _sequence_video = s; +} + +bool +ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b) +{ + return a->start() < b->start(); +} diff --git a/src/lib/playlist.h b/src/lib/playlist.h new file mode 100644 index 000000000..2d243fe8f --- /dev/null +++ b/src/lib/playlist.h @@ -0,0 +1,104 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_PLAYLIST_H +#define DCPOMATIC_PLAYLIST_H + +#include <list> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include "ffmpeg_content.h" +#include "audio_mapping.h" + +class Content; +class FFmpegContent; +class FFmpegDecoder; +class ImageMagickContent; +class ImageMagickDecoder; +class SndfileContent; +class SndfileDecoder; +class Job; +class Film; +class Region; + +/** @class Playlist + * @brief A set of content files (video and audio), with knowledge of how they should be arranged into + * a DCP. + * + * This class holds Content objects, and it knows how they should be arranged. At the moment + * the ordering is implicit; video content is placed sequentially, and audio content is taken + * from the video unless any sound-only files are present. If sound-only files exist, they + * are played simultaneously (i.e. they can be split up into multiple files for different channels) + */ + +struct ContentSorter +{ + bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b); +}; + +class Playlist +{ +public: + Playlist (); + Playlist (boost::shared_ptr<const Playlist>); + ~Playlist (); + + void as_xml (xmlpp::Node *); + void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + + void add (boost::shared_ptr<Content>); + void remove (boost::shared_ptr<Content>); + + bool has_subtitles () const; + + typedef std::vector<boost::shared_ptr<Content> > ContentList; + + ContentList content () const { + return _content; + } + + std::string video_digest () const; + + int loop () const { + return _loop; + } + + void set_loop (int l); + + Time length () const; + int best_dcp_frame_rate () const; + Time video_end () const; + + void set_sequence_video (bool); + + mutable boost::signals2::signal<void ()> Changed; + mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged; + +private: + void content_changed (boost::weak_ptr<Content>, int); + void reconnect (); + + ContentList _content; + int _loop; + bool _sequence_video; + bool _sequencing_video; + std::list<boost::signals2::connection> _content_connections; +}; + +#endif diff --git a/src/lib/po/es_ES.po b/src/lib/po/es_ES.po index 39b6dfceb..7a07645b7 100644 --- a/src/lib/po/es_ES.po +++ b/src/lib/po/es_ES.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: LIBDVDOMATIC\n" +"Project-Id-Version: LIBDCPOMATIC\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-04-02 19:10-0500\n" @@ -254,10 +254,10 @@ msgstr "Horizontal deblocking filter A" #: src/lib/job.cc:97 src/lib/job.cc:106 msgid "" "It is not known what caused this error. The best idea is to report the " -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)" +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)" msgstr "" "Error desconocido. La mejor idea es informar del problema a la lista de " -"correo de DVD-O-matic (dvdomatic@carlh.net)" +"correo de DCP-o-matic (dcpomatic@carlh.net)" #: src/lib/filter.cc:82 msgid "Kernel deinterlacer" diff --git a/src/lib/po/fr_FR.po b/src/lib/po/fr_FR.po index 9dcbd42c2..f68631bbd 100644 --- a/src/lib/po/fr_FR.po +++ b/src/lib/po/fr_FR.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVD-o-matic FRENCH\n" +"Project-Id-Version: DCP-o-matic FRENCH\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-05-21 10:30+0100\n" @@ -249,13 +249,10 @@ msgstr "Filtre dé-bloc horizontal" msgid "Horizontal deblocking filter A" msgstr "Filtre dé-bloc horizontal" -#: src/lib/job.cc:97 src/lib/job.cc:106 -msgid "" -"It is not known what caused this error. The best idea is to report the " -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)" -msgstr "" -"Erreur indéterminée. Merci de rapporter le problème à la liste DVD-o-matic " -"(dvdomatic@carlh.net)" +#: src/lib/job.cc:97 +#: src/lib/job.cc:106 +msgid "It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)" +msgstr "Erreur indéterminée. Merci de rapporter le problème à la liste DCP-o-matic (dcpomatic@carlh.net)" #: src/lib/filter.cc:82 msgid "Kernel deinterlacer" diff --git a/src/lib/po/it_IT.po b/src/lib/po/it_IT.po index 9671c66a7..0ddfa7721 100644 --- a/src/lib/po/it_IT.po +++ b/src/lib/po/it_IT.po @@ -251,10 +251,10 @@ msgstr "Filtro A sblocco orizzontale" #: src/lib/job.cc:97 src/lib/job.cc:106 msgid "" "It is not known what caused this error. The best idea is to report the " -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)" +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)" msgstr "" "Non sappiamo cosa ha causato questo errore. La cosa migliore è inviare un " -"report del problema alla mailing list di DVD-o-matic (dvdomatic@carlh.net)" +"report del problema alla mailing list di DCP-o-matic (dcpomatic@carlh.net)" #: src/lib/filter.cc:82 msgid "Kernel deinterlacer" diff --git a/src/lib/po/sv_SE.po b/src/lib/po/sv_SE.po index 9391801dc..c1c62ca41 100644 --- a/src/lib/po/sv_SE.po +++ b/src/lib/po/sv_SE.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVD-o-matic\n" +"Project-Id-Version: DCP-o-matic\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-04-10 15:35+0100\n" @@ -252,10 +252,10 @@ msgstr "Filter för horisontal kantighetsutjämning A" #: src/lib/job.cc:97 src/lib/job.cc:106 msgid "" "It is not known what caused this error. The best idea is to report the " -"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)" +"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)" msgstr "" "Det är inte känt vad som orsakade detta fel. Bästa sättet att rapportera " -"problemet är till DVD-o-matics mejl-lista (dvdomatic@carlh.net)" +"problemet är till DCP-o-matics mejl-lista (dcpomatic@carlh.net)" #: src/lib/filter.cc:82 msgid "Kernel deinterlacer" diff --git a/src/lib/processor.h b/src/lib/processor.h deleted file mode 100644 index 603239f8f..000000000 --- a/src/lib/processor.h +++ /dev/null @@ -1,115 +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/processor.h - * @brief Parent class for classes which accept and then emit video or audio data. - */ - -#ifndef DVDOMATIC_PROCESSOR_H -#define DVDOMATIC_PROCESSOR_H - -#include "video_source.h" -#include "video_sink.h" -#include "audio_source.h" -#include "audio_sink.h" - -class Log; - -/** @class Processor - * @brief Base class for processors. - */ -class Processor -{ -public: - /** Construct a Processor. - * @param log Log to use. - */ - Processor (boost::shared_ptr<Log> log) - : _log (log) - {} - - virtual ~Processor() {} - - /** Will be called at the end of a processing run */ - virtual void process_end () {} - -protected: - boost::shared_ptr<Log> _log; ///< log to write to -}; - -/** @class AudioVideoProcessor - * @brief A processor which handles both video and audio data. - */ -class AudioVideoProcessor : public Processor, public VideoSource, public VideoSink, public AudioSource, public AudioSink -{ -public: - /** Construct an AudioVideoProcessor. - * @param log Log to write to. - */ - AudioVideoProcessor (boost::shared_ptr<Log> log) - : Processor (log) - {} -}; - -class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink -{ -public: - TimedAudioVideoProcessor (boost::shared_ptr<Log> log) - : Processor (log) - {} -}; - - -/** @class AudioProcessor - * @brief A processor which handles just audio data. - */ -class AudioProcessor : public Processor, public AudioSource, public AudioSink -{ -public: - /** Construct an AudioProcessor. - * @param log Log to write to. - */ - AudioProcessor (boost::shared_ptr<Log> log) - : Processor (log) - {} -}; - -/** @class VideoProcessor - * @brief A processor which handles just video data. - */ -class VideoProcessor : public Processor, public VideoSource, public VideoSink -{ -public: - /** Construct an VideoProcessor. - * @param log Log to write to. - */ - VideoProcessor (boost::shared_ptr<Log> log) - : Processor (log) - {} -}; - -class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink -{ -public: - TimedVideoProcessor (boost::shared_ptr<Log> log) - : Processor (log) - {} -}; - -#endif diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc new file mode 100644 index 000000000..5988b3418 --- /dev/null +++ b/src/lib/ratio.cc @@ -0,0 +1,71 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libdcp/types.h> +#include "ratio.h" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using std::vector; + +vector<Ratio const *> Ratio::_ratios; + +libdcp::Size +Ratio::size (libdcp::Size full_frame) const +{ + if (_ratio < static_cast<float>(full_frame.width) / full_frame.height) { + return libdcp::Size (full_frame.height * _ratio, full_frame.height); + } else { + return libdcp::Size (full_frame.width, full_frame.width / _ratio); + } + + return libdcp::Size (); +} + + +void +Ratio::setup_ratios () +{ + _ratios.push_back (new Ratio (float(1285) / 1080, "119", _("1.19"), "F")); + _ratios.push_back (new Ratio (float(1436) / 1080, "133", _("4:3"), "F")); + _ratios.push_back (new Ratio (float(1480) / 1080, "137", _("Academy"), "F")); + _ratios.push_back (new Ratio (float(1485) / 1080, "138", _("1.375"), "F")); + _ratios.push_back (new Ratio (float(1793) / 1080, "166", _("1.66"), "F")); + _ratios.push_back (new Ratio (float(1920) / 1080, "178", _("16:9"), "F")); + _ratios.push_back (new Ratio (float(1998) / 1080, "185", _("Flat"), "F")); + _ratios.push_back (new Ratio (float(2048) / 858, "239", _("Scope"), "S")); + _ratios.push_back (new Ratio (float(2048) / 1080, "full-frame", _("Full frame"), "C")); +} + +Ratio const * +Ratio::from_id (string i) +{ + vector<Ratio const *>::iterator j = _ratios.begin (); + while (j != _ratios.end() && (*j)->id() != i) { + ++j; + } + + if (j == _ratios.end ()) { + return 0; + } + + return *j; +} diff --git a/src/lib/ratio.h b/src/lib/ratio.h new file mode 100644 index 000000000..5480eee12 --- /dev/null +++ b/src/lib/ratio.h @@ -0,0 +1,71 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_RATIO_H +#define DCPOMATIC_RATIO_H + +#include <vector> +#include <libdcp/util.h> + +class Ratio +{ +public: + Ratio (float ratio, std::string id, std::string n, std::string d) + : _ratio (ratio) + , _id (id) + , _nickname (n) + , _dci_name (d) + {} + + libdcp::Size size (libdcp::Size) const; + + std::string id () const { + return _id; + } + + std::string nickname () const { + return _nickname; + } + + std::string dci_name () const { + return _dci_name; + } + + float ratio () const { + return _ratio; + } + + static void setup_ratios (); + static Ratio const * from_id (std::string i); + static std::vector<Ratio const *> all () { + return _ratios; + } + +private: + float _ratio; + /** id for use in metadata */ + std::string _id; + /** nickname (e.g. Flat, Scope) */ + std::string _nickname; + std::string _dci_name; + + static std::vector<Ratio const *> _ratios; +}; + +#endif diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc new file mode 100644 index 000000000..1235b9038 --- /dev/null +++ b/src/lib/resampler.cc @@ -0,0 +1,61 @@ +extern "C" { +#include "libavutil/channel_layout.h" +} +#include "resampler.h" +#include "audio_buffers.h" +#include "exceptions.h" + +#include "i18n.h" + +using boost::shared_ptr; + +Resampler::Resampler (int in, int out, int channels) + : _in_rate (in) + , _out_rate (out) + , _channels (channels) +{ + /* We will be using planar float data when we call the + resampler. As far as I can see, the audio channel + layout is not necessary for our purposes; it seems + only to be used get the number of channels and + decide if rematrixing is needed. It won't be, since + input and output layouts are the same. + */ + + _swr_context = swr_alloc_set_opts ( + 0, + av_get_default_channel_layout (_channels), + AV_SAMPLE_FMT_FLTP, + _out_rate, + av_get_default_channel_layout (_channels), + AV_SAMPLE_FMT_FLTP, + _in_rate, + 0, 0 + ); + + swr_init (_swr_context); +} + +Resampler::~Resampler () +{ + swr_free (&_swr_context); +} + +shared_ptr<const AudioBuffers> +Resampler::run (shared_ptr<const AudioBuffers> in) +{ + /* Compute the resampled frames count and add 32 for luck */ + int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32; + shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames)); + + int const resampled_frames = swr_convert ( + _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) in->data(), in->frames() + ); + + if (resampled_frames < 0) { + throw EncodeError (_("could not run sample-rate converter")); + } + + resampled->set_frames (resampled_frames); + return resampled; +} diff --git a/src/lib/resampler.h b/src/lib/resampler.h new file mode 100644 index 000000000..cda718934 --- /dev/null +++ b/src/lib/resampler.h @@ -0,0 +1,21 @@ +#include <boost/shared_ptr.hpp> +extern "C" { +#include <libswresample/swresample.h> +} + +class AudioBuffers; + +class Resampler +{ +public: + Resampler (int, int, int); + ~Resampler (); + + boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>); + +private: + SwrContext* _swr_context; + int _in_rate; + int _out_rate; + int _channels; +}; diff --git a/src/lib/scaler.h b/src/lib/scaler.h index c80f4b7db..a736e92de 100644 --- a/src/lib/scaler.h +++ b/src/lib/scaler.h @@ -21,8 +21,8 @@ * @brief A class to describe one of FFmpeg's software scalers. */ -#ifndef DVDOMATIC_SCALER_H -#define DVDOMATIC_SCALER_H +#ifndef DCPOMATIC_SCALER_H +#define DCPOMATIC_SCALER_H #include <string> #include <vector> diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc index a9fdfefda..8cde44f02 100644 --- a/src/lib/scp_dcp_job.cc +++ b/src/lib/scp_dcp_job.cc @@ -96,7 +96,7 @@ public: }; -SCPDCPJob::SCPDCPJob (shared_ptr<Film> f) +SCPDCPJob::SCPDCPJob (shared_ptr<const Film> f) : Job (f) , _status (_("Waiting")) { diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h index 08d8e2c78..bdc83af18 100644 --- a/src/lib/scp_dcp_job.h +++ b/src/lib/scp_dcp_job.h @@ -26,7 +26,7 @@ class SCPDCPJob : public Job { public: - SCPDCPJob (boost::shared_ptr<Film>); + SCPDCPJob (boost::shared_ptr<const Film>); std::string name () const; void run (); @@ -34,7 +34,7 @@ public: private: void set_status (std::string); - + mutable boost::mutex _status_mutex; std::string _status; }; diff --git a/src/lib/server.cc b/src/lib/server.cc index 9c5a77f68..5ca04c692 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -29,6 +29,7 @@ #include <boost/algorithm/string.hpp> #include <boost/lexical_cast.hpp> #include <boost/scoped_array.hpp> +#include <libcxml/cxml.h> #include "server.h" #include "util.h" #include "scaler.h" @@ -51,6 +52,19 @@ using boost::bind; using boost::scoped_array; using libdcp::Size; +ServerDescription::ServerDescription (shared_ptr<const cxml::Node> node) +{ + _host_name = node->string_child ("HostName"); + _threads = node->number_child<int> ("Threads"); +} + +void +ServerDescription::as_xml (xmlpp::Node* root) const +{ + root->add_child("HostName")->add_child_text (_host_name); + root->add_child("Threads")->add_child_text (boost::lexical_cast<string> (_threads)); +} + /** Create a server description from a string of metadata returned from as_metadata(). * @param v Metadata. * @return ServerDescription, or 0. @@ -68,15 +82,6 @@ ServerDescription::create_from_metadata (string v) return new ServerDescription (b[0], atoi (b[1].c_str ())); } -/** @return Description of this server as text */ -string -ServerDescription::as_metadata () const -{ - stringstream s; - s << _host_name << N_(" ") << _threads; - return s.str (); -} - Server::Server (shared_ptr<Log> log) : _log (log) { @@ -93,45 +98,25 @@ Server::process (shared_ptr<Socket> socket) stringstream s (buffer.get()); multimap<string, string> kv = read_key_value (s); - if (get_required_string (kv, N_("encode")) != N_("please")) { + if (get_required_string (kv, "encode") != "please") { return -1; } - libdcp::Size in_size (get_required_int (kv, N_("input_width")), get_required_int (kv, N_("input_height"))); - int pixel_format_int = get_required_int (kv, N_("input_pixel_format")); - libdcp::Size out_size (get_required_int (kv, N_("output_width")), get_required_int (kv, N_("output_height"))); - int padding = get_required_int (kv, N_("padding")); - int subtitle_offset = get_required_int (kv, N_("subtitle_offset")); - float subtitle_scale = get_required_float (kv, N_("subtitle_scale")); - string scaler_id = get_required_string (kv, N_("scaler")); - int frame = get_required_int (kv, N_("frame")); - int frames_per_second = get_required_int (kv, N_("frames_per_second")); - string post_process = get_optional_string (kv, N_("post_process")); - int colour_lut_index = get_required_int (kv, N_("colour_lut")); - int j2k_bandwidth = get_required_int (kv, N_("j2k_bandwidth")); - Position subtitle_position (get_optional_int (kv, N_("subtitle_x")), get_optional_int (kv, N_("subtitle_y"))); - libdcp::Size subtitle_size (get_optional_int (kv, N_("subtitle_width")), get_optional_int (kv, N_("subtitle_height"))); + libdcp::Size size (get_required_int (kv, "width"), get_required_int (kv, "height")); + int frame = get_required_int (kv, "frame"); + int frames_per_second = get_required_int (kv, "frames_per_second"); + int colour_lut_index = get_required_int (kv, "colour_lut"); + int j2k_bandwidth = get_required_int (kv, "j2k_bandwidth"); /* This checks that colour_lut_index is within range */ colour_lut_index_to_name (colour_lut_index); - PixelFormat pixel_format = (PixelFormat) pixel_format_int; - Scaler const * scaler = Scaler::from_id (scaler_id); - - shared_ptr<Image> image (new SimpleImage (pixel_format, in_size, true)); + shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, true)); image->read_from_socket (socket); - shared_ptr<Subtitle> sub; - if (subtitle_size.width && subtitle_size.height) { - 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)); - } - DCPVideoFrame dcp_video_frame ( - image, sub, out_size, padding, subtitle_offset, subtitle_scale, - scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log + image, frame, frames_per_second, colour_lut_index, j2k_bandwidth, _log ); shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally (); diff --git a/src/lib/server.h b/src/lib/server.h index 89aeca626..398401a55 100644 --- a/src/lib/server.h +++ b/src/lib/server.h @@ -26,10 +26,15 @@ #include <boost/thread.hpp> #include <boost/asio.hpp> #include <boost/thread/condition.hpp> +#include <libxml++/libxml++.h> #include "log.h" class Socket; +namespace cxml { + class Node; +} + /** @class ServerDescription * @brief Class to describe a server to which we can send encoding work. */ @@ -44,6 +49,8 @@ public: , _threads (t) {} + ServerDescription (boost::shared_ptr<const cxml::Node>); + /** @return server's host name or IP address in string form */ std::string host_name () const { return _host_name; @@ -62,7 +69,7 @@ public: _threads = t; } - std::string as_metadata () const; + void as_xml (xmlpp::Node *) const; static ServerDescription * create_from_metadata (std::string v); diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc new file mode 100644 index 000000000..beee7cd9d --- /dev/null +++ b/src/lib/sndfile_content.cc @@ -0,0 +1,160 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libcxml/cxml.h> +#include "sndfile_content.h" +#include "sndfile_decoder.h" +#include "compose.hpp" +#include "job.h" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using std::cout; +using boost::shared_ptr; +using boost::lexical_cast; + +SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , AudioContent (f, p) + , _audio_channels (0) + , _audio_length (0) + , _audio_frame_rate (0) +{ + +} + +SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + : Content (f, node) + , AudioContent (f, node) +{ + _audio_channels = node->number_child<int> ("AudioChannels"); + _audio_length = node->number_child<AudioContent::Frame> ("AudioLength"); + _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); + _audio_mapping = AudioMapping (node->node_child ("AudioMapping")); +} + +string +SndfileContent::summary () const +{ + return String::compose (_("Sound file: %1"), file().filename().string()); +} + +string +SndfileContent::information () const +{ + if (_audio_frame_rate == 0) { + return ""; + } + + stringstream s; + + s << String::compose ( + _("%1 channels, %2kHz, %3 samples"), + audio_channels(), + content_audio_frame_rate() / 1000.0, + audio_length() + ); + + return s.str (); +} + +bool +SndfileContent::valid_file (boost::filesystem::path f) +{ + /* XXX: more extensions */ + string ext = f.extension().string(); + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); + return (ext == ".wav" || ext == ".aif" || ext == ".aiff"); +} + +shared_ptr<Content> +SndfileContent::clone () const +{ + return shared_ptr<Content> (new SndfileContent (*this)); +} + +void +SndfileContent::examine (shared_ptr<Job> job) +{ + job->set_progress_unknown (); + Content::examine (job); + + shared_ptr<const Film> film = _film.lock (); + assert (film); + + SndfileDecoder dec (film, shared_from_this()); + + { + boost::mutex::scoped_lock lm (_mutex); + _audio_channels = dec.audio_channels (); + _audio_length = dec.audio_length (); + _audio_frame_rate = dec.audio_frame_rate (); + } + + signal_changed (AudioContentProperty::AUDIO_CHANNELS); + signal_changed (AudioContentProperty::AUDIO_LENGTH); + signal_changed (AudioContentProperty::AUDIO_FRAME_RATE); + + /* XXX: do this in signal_changed...? */ + _audio_mapping = AudioMapping (_audio_channels); + signal_changed (AudioContentProperty::AUDIO_MAPPING); +} + +void +SndfileContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("Sndfile"); + Content::as_xml (node); + AudioContent::as_xml (node); + node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels)); + node->add_child("AudioLength")->add_child_text (lexical_cast<string> (_audio_length)); + node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (_audio_frame_rate)); + _audio_mapping.as_xml (node->add_child("AudioMapping")); +} + +Time +SndfileContent::length () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + + return film->audio_frames_to_time (audio_length ()); +} + +int +SndfileContent::output_audio_frame_rate () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + + return film->dcp_audio_frame_rate (); +} + +void +SndfileContent::set_audio_mapping (AudioMapping m) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_mapping = m; + } + + signal_changed (AudioContentProperty::AUDIO_MAPPING); +} diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h new file mode 100644 index 000000000..876d66088 --- /dev/null +++ b/src/lib/sndfile_content.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2013 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. + +*/ + +extern "C" { +#include <libavutil/audioconvert.h> +} +#include "audio_content.h" + +namespace cxml { + class Node; +} + +class SndfileContent : public AudioContent +{ +public: + SndfileContent (boost::shared_ptr<const Film>, boost::filesystem::path); + SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + + boost::shared_ptr<SndfileContent> shared_from_this () { + return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ()); + } + + void examine (boost::shared_ptr<Job>); + std::string summary () const; + std::string information () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr<Content> clone () const; + Time length () const; + + /* AudioContent */ + int audio_channels () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_channels; + } + + AudioContent::Frame audio_length () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_length; + } + + int content_audio_frame_rate () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_frame_rate; + } + + int output_audio_frame_rate () const; + + AudioMapping audio_mapping () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_mapping; + } + + void set_audio_mapping (AudioMapping); + + static bool valid_file (boost::filesystem::path); + +private: + int _audio_channels; + AudioContent::Frame _audio_length; + int _audio_frame_rate; + AudioMapping _audio_mapping; +}; diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index 7e9e67d0f..80a6afd2b 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -19,157 +19,101 @@ #include <iostream> #include <sndfile.h> +#include "sndfile_content.h" #include "sndfile_decoder.h" #include "film.h" #include "exceptions.h" +#include "audio_buffers.h" #include "i18n.h" using std::vector; using std::string; -using std::stringstream; using std::min; using std::cout; using boost::shared_ptr; -using boost::optional; -SndfileDecoder::SndfileDecoder (shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) - , AudioDecoder (f, o) - , _done (0) - , _frames (0) +SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c) + : Decoder (f) + , AudioDecoder (f) + , _sndfile_content (c) + , _deinterleave_buffer (0) { - _done = 0; - _frames = 0; - - vector<string> const files = _film->external_audio (); - - int N = 0; - for (size_t i = 0; i < files.size(); ++i) { - if (!files[i].empty()) { - N = i + 1; - } - } - - if (N == 0) { - return; + _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info); + if (!_sndfile) { + throw DecodeError (_("could not open audio file for reading")); } - bool first = true; - - for (size_t i = 0; i < (size_t) N; ++i) { - if (files[i].empty ()) { - _sndfiles.push_back (0); - } else { - SF_INFO info; - SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info); - if (!s) { - throw DecodeError (_("could not open external audio file for reading")); - } + _done = 0; + _remaining = _info.frames; +} - if (info.channels != 1) { - throw DecodeError (_("external audio files must be mono")); - } - - _sndfiles.push_back (s); - - if (first) { - shared_ptr<SndfileStream> st ( - new SndfileStream ( - info.samplerate, av_get_default_channel_layout (N) - ) - ); - - _audio_streams.push_back (st); - _audio_stream = st; - _frames = info.frames; - first = false; - } else { - if (info.frames != _frames) { - throw DecodeError (_("external audio files have differing lengths")); - } - } - } - } +SndfileDecoder::~SndfileDecoder () +{ + sf_close (_sndfile); + delete[] _deinterleave_buffer; } -bool +void SndfileDecoder::pass () { - if (_audio_streams.empty ()) { - return true; - } - /* Do things in half second blocks as I think there may be limits to what FFmpeg (and in particular the resampler) can cope with. */ - sf_count_t const block = _audio_stream->sample_rate() / 2; - shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block)); - sf_count_t const this_time = min (block, _frames - _done); - for (size_t i = 0; i < _sndfiles.size(); ++i) { - if (!_sndfiles[i]) { - audio->make_silent (i); - } else { - sf_read_float (_sndfiles[i], audio->data(i), this_time); - } - } + sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2; + sf_count_t const this_time = min (block, _remaining); - audio->set_frames (this_time); - Audio (audio, double(_done) / _audio_stream->sample_rate()); - _done += this_time; - - return (_done == _frames); -} - -SndfileDecoder::~SndfileDecoder () -{ - for (size_t i = 0; i < _sndfiles.size(); ++i) { - if (_sndfiles[i]) { - sf_close (_sndfiles[i]); + int const channels = _sndfile_content->audio_channels (); + + shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time)); + + if (_sndfile_content->audio_channels() == 1) { + /* No de-interleaving required */ + sf_read_float (_sndfile, data->data(0), this_time); + } else { + /* Deinterleave */ + if (!_deinterleave_buffer) { + _deinterleave_buffer = new float[block * channels]; + } + sf_readf_float (_sndfile, _deinterleave_buffer, this_time); + vector<float*> out_ptr (channels); + for (int i = 0; i < channels; ++i) { + out_ptr[i] = data->data(i); + } + float* in_ptr = _deinterleave_buffer; + for (int i = 0; i < this_time; ++i) { + for (int j = 0; j < channels; ++j) { + *out_ptr[j]++ = *in_ptr++; + } } } + + data->set_frames (this_time); + audio (data, double(_done) / audio_frame_rate()); + _done += this_time; + _remaining -= this_time; } -shared_ptr<SndfileStream> -SndfileStream::create () -{ - return shared_ptr<SndfileStream> (new SndfileStream); -} - -shared_ptr<SndfileStream> -SndfileStream::create (string t, optional<int> v) +int +SndfileDecoder::audio_channels () const { - if (!v) { - /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ - return shared_ptr<SndfileStream> (); - } - - stringstream s (t); - string type; - s >> type; - if (type != N_("external")) { - return shared_ptr<SndfileStream> (); - } - - return shared_ptr<SndfileStream> (new SndfileStream (t, v)); + return _info.channels; } -SndfileStream::SndfileStream (string t, optional<int> v) +AudioContent::Frame +SndfileDecoder::audio_length () const { - assert (v); - - stringstream s (t); - string type; - s >> type >> _sample_rate >> _channel_layout; + return _info.frames; } -SndfileStream::SndfileStream () +int +SndfileDecoder::audio_frame_rate () const { - + return _info.samplerate; } -string -SndfileStream::to_string () const +bool +SndfileDecoder::done () const { - return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout); + return _audio_position >= _sndfile_content->audio_length (); } diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 9489cb5ec..77fa6d177 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -20,37 +20,27 @@ #include <sndfile.h> #include "decoder.h" #include "audio_decoder.h" -#include "stream.h" -class SndfileStream : public AudioStream -{ -public: - SndfileStream (int sample_rate, int64_t layout) - : AudioStream (sample_rate, layout) - {} - - std::string to_string () const; - - static boost::shared_ptr<SndfileStream> create (); - static boost::shared_ptr<SndfileStream> create (std::string t, boost::optional<int> v); - -private: - friend class stream_test; - - SndfileStream (); - SndfileStream (std::string t, boost::optional<int> v); -}; +class SndfileContent; class SndfileDecoder : public AudioDecoder { public: - SndfileDecoder (boost::shared_ptr<Film>, DecodeOptions); + SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>); ~SndfileDecoder (); - bool pass (); + void pass (); + bool done () const; + + int audio_channels () const; + AudioContent::Frame audio_length () const; + int audio_frame_rate () const; private: - std::vector<SNDFILE*> _sndfiles; - sf_count_t _done; - sf_count_t _frames; + boost::shared_ptr<const SndfileContent> _sndfile_content; + SNDFILE* _sndfile; + SF_INFO _info; + AudioContent::Frame _done; + AudioContent::Frame _remaining; + float* _deinterleave_buffer; }; diff --git a/src/lib/sound_processor.h b/src/lib/sound_processor.h index 2edf38840..bdbe72ba2 100644 --- a/src/lib/sound_processor.h +++ b/src/lib/sound_processor.h @@ -21,8 +21,8 @@ * @brief A class to describe a sound processor. */ -#ifndef DVDOMATIC_SOUND_PROCESSOR_H -#define DVDOMATIC_SOUND_PROCESSOR_H +#ifndef DCPOMATIC_SOUND_PROCESSOR_H +#define DCPOMATIC_SOUND_PROCESSOR_H #include <string> #include <vector> diff --git a/src/lib/stack.cpp b/src/lib/stack.cpp index 20a5c5be7..a8183d344 100644 --- a/src/lib/stack.cpp +++ b/src/lib/stack.cpp @@ -1,5 +1,3 @@ -/** -*- c-basic-offset: 4; default-tab-width: 4; indent-tabs-mode: nil; -*- */ - // Copyright 2007 Edd Dawson. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at diff --git a/src/lib/stack.hpp b/src/lib/stack.hpp index 2b622d020..73a13bf85 100644 --- a/src/lib/stack.hpp +++ b/src/lib/stack.hpp @@ -1,5 +1,3 @@ -/** -*- c-basic-offset: 4; default-tab-width: 4; indent-tabs-mode: nil; -*- */ - // Copyright 2007 Edd Dawson. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at diff --git a/src/lib/stream.cc b/src/lib/stream.cc deleted file mode 100644 index bfe7b5eb4..000000000 --- a/src/lib/stream.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include <sstream> -#include "compose.hpp" -#include "stream.h" -#include "ffmpeg_decoder.h" -#include "sndfile_decoder.h" - -#include "i18n.h" - -using std::string; -using std::stringstream; -using boost::shared_ptr; -using boost::optional; - -/** Construct a SubtitleStream from a value returned from to_string(). - * @param t String returned from to_string(). - * @param v State file version. - */ -SubtitleStream::SubtitleStream (string t, boost::optional<int>) -{ - stringstream n (t); - n >> _id; - - size_t const s = t.find (' '); - if (s != string::npos) { - _name = t.substr (s + 1); - } -} - -/** @return A canonical string representation of this stream */ -string -SubtitleStream::to_string () const -{ - return String::compose (N_("%1 %2"), _id, _name); -} - -/** Create a SubtitleStream from a value returned from to_string(). - * @param t String returned from to_string(). - * @param v State file version. - */ -shared_ptr<SubtitleStream> -SubtitleStream::create (string t, optional<int> v) -{ - return shared_ptr<SubtitleStream> (new SubtitleStream (t, v)); -} - -/** Create an AudioStream from a string returned from to_string(). - * @param t String returned from to_string(). - * @param v State file version. - * @return AudioStream, or 0. - */ -shared_ptr<AudioStream> -audio_stream_factory (string t, optional<int> v) -{ - shared_ptr<AudioStream> s; - - s = FFmpegAudioStream::create (t, v); - if (!s) { - s = SndfileStream::create (t, v); - } - - return s; -} - -/** Create a SubtitleStream from a string returned from to_string(). - * @param t String returned from to_string(). - * @param v State file version. - * @return SubtitleStream, or 0. - */ -shared_ptr<SubtitleStream> -subtitle_stream_factory (string t, optional<int> v) -{ - return SubtitleStream::create (t, v); -} diff --git a/src/lib/stream.h b/src/lib/stream.h deleted file mode 100644 index 16b06e4bc..000000000 --- a/src/lib/stream.h +++ /dev/null @@ -1,121 +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/lib/stream.h - * @brief Representations of audio and subtitle streams. - * - * Some content may have multiple `streams' of audio and/or subtitles; perhaps - * for multiple languages, or for stereo / surround mixes. These classes represent - * those streams, and know about their details. - */ - -#ifndef DVDOMATIC_STREAM_H -#define DVDOMATIC_STREAM_H - -#include <stdint.h> -#include <boost/shared_ptr.hpp> -#include <boost/optional.hpp> -extern "C" { -#include <libavutil/audioconvert.h> -} - -/** @class Stream - * @brief Parent class for streams. - */ -class Stream -{ -public: - virtual ~Stream () {} - virtual std::string to_string () const = 0; -}; - -/** @class AudioStream - * @brief A stream of audio data. - */ -struct AudioStream : public Stream -{ -public: - AudioStream (int r, int64_t l) - : _sample_rate (r) - , _channel_layout (l) - {} - - /* Only used for backwards compatibility for state file version < 1 */ - void set_sample_rate (int s) { - _sample_rate = s; - } - - int channels () const { - return av_get_channel_layout_nb_channels (_channel_layout); - } - - int sample_rate () const { - return _sample_rate; - } - - int64_t channel_layout () const { - return _channel_layout; - } - -protected: - AudioStream () - : _sample_rate (0) - , _channel_layout (0) - {} - - int _sample_rate; - int64_t _channel_layout; -}; - -/** @class SubtitleStream - * @brief A stream of subtitle data. - */ -class SubtitleStream : public Stream -{ -public: - SubtitleStream (std::string n, int i) - : _name (n) - , _id (i) - {} - - std::string to_string () const; - - std::string name () const { - return _name; - } - - int id () const { - return _id; - } - - static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v); - -private: - friend class stream_test; - - SubtitleStream (std::string t, boost::optional<int> v); - - std::string _name; - int _id; -}; - -boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version); -boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version); - -#endif diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc index 5c2a0d0b5..7013f1d7d 100644 --- a/src/lib/subtitle.cc +++ b/src/lib/subtitle.cc @@ -27,8 +27,7 @@ #include "i18n.h" -using namespace std; -using namespace boost; +using boost::shared_ptr; using libdcp::Size; /** Construct a TimedSubtitle. This is a subtitle image, position, @@ -45,8 +44,8 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub) double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE; /* hence start time for this sub */ - _from = packet_time + (double (sub.start_display_time) / 1e3); - _to = packet_time + (double (sub.end_display_time) / 1e3); + _from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ; + _to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ; if (sub.num_rects > 1) { throw DecodeError (_("multi-part subtitles not yet supported")); @@ -80,9 +79,9 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub) _subtitle.reset (new Subtitle (Position (rect->x, rect->y), image)); } -/** @param t Time in seconds from the start of the source */ +/** @param t Time from the start of the source */ bool -TimedSubtitle::displayed_at (double t) const +TimedSubtitle::displayed_at (Time t) const { return t >= _from && t <= _to; } @@ -108,13 +107,13 @@ Subtitle::Subtitle (Position p, shared_ptr<Image> i) * in the coordinate space of the source. * @param subtitle_scale scaling factor to apply to the subtitle image. */ -dvdomatic::Rect +dcpomatic::Rect subtitle_transformed_area ( float target_x_scale, float target_y_scale, - dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale + dcpomatic::Rect sub_area, int subtitle_offset, float subtitle_scale ) { - dvdomatic::Rect tx; + dcpomatic::Rect tx; sub_area.y += subtitle_offset; @@ -143,8 +142,8 @@ subtitle_transformed_area ( } /** @return area that this subtitle takes up, in the original uncropped source's coordinate space */ -dvdomatic::Rect +dcpomatic::Rect Subtitle::area () const { - return dvdomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height); + return dcpomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height); } diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h index e3a853695..1020397cc 100644 --- a/src/lib/subtitle.h +++ b/src/lib/subtitle.h @@ -23,7 +23,7 @@ #include <list> #include <boost/shared_ptr.hpp> -#include "util.h" +#include "types.h" struct AVSubtitle; class Image; @@ -46,17 +46,17 @@ public: return _image; } - dvdomatic::Rect area () const; + dcpomatic::Rect area () const; private: Position _position; boost::shared_ptr<Image> _image; }; -dvdomatic::Rect +dcpomatic::Rect subtitle_transformed_area ( float target_x_scale, float target_y_scale, - dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale + dcpomatic::Rect sub_area, int subtitle_offset, float subtitle_scale ); /** A Subtitle class with details of the time over which it should be shown */ @@ -65,7 +65,7 @@ class TimedSubtitle public: TimedSubtitle (AVSubtitle const &); - bool displayed_at (double t) const; + bool displayed_at (Time) const; boost::shared_ptr<Subtitle> subtitle () const { return _subtitle; @@ -74,8 +74,8 @@ public: private: /** the subtitle */ boost::shared_ptr<Subtitle> _subtitle; - /** display from time in seconds from the start of the film */ - double _from; - /** display to time in seconds from the start of the film */ - double _to; + /** display from time from the start of the content */ + Time _from; + /** display to time from the start of the content */ + Time _to; }; diff --git a/src/lib/timer.h b/src/lib/timer.h index f509a7492..173d0d961 100644 --- a/src/lib/timer.h +++ b/src/lib/timer.h @@ -22,8 +22,8 @@ * @brief Some timing classes for debugging and profiling. */ -#ifndef DVDOMATIC_TIMER_H -#define DVDOMATIC_TIMER_H +#ifndef DCPOMATIC_TIMER_H +#define DCPOMATIC_TIMER_H #include <string> #include <map> diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 234ebe051..6d5edd7c0 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -25,10 +25,8 @@ #include <iomanip> #include "transcode_job.h" #include "film.h" -#include "format.h" #include "transcoder.h" #include "log.h" -#include "encoder.h" #include "i18n.h" @@ -39,11 +37,9 @@ using std::setprecision; using boost::shared_ptr; /** @param s Film to use. - * @param o Decode options. */ -TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o) +TranscodeJob::TranscodeJob (shared_ptr<const Film> f) : Job (f) - , _decode_opt (o) { } @@ -60,11 +56,9 @@ TranscodeJob::run () try { _film->log()->log (N_("Transcode job starting")); - _film->log()->log (String::compose (N_("Audio delay is %1ms"), _film->audio_delay())); - _encoder.reset (new Encoder (_film)); - Transcoder w (_film, _decode_opt, this, _encoder); - w.go (); + _transcoder.reset (new Transcoder (_film, shared_from_this ())); + _transcoder->go (); set_progress (1); set_state (FINISHED_OK); @@ -83,11 +77,11 @@ TranscodeJob::run () string TranscodeJob::status () const { - if (!_encoder) { + if (!_transcoder) { return _("0%"); } - float const fps = _encoder->current_frames_per_second (); + float const fps = _transcoder->current_encoding_rate (); if (fps == 0) { return Job::status (); } @@ -106,24 +100,17 @@ TranscodeJob::status () const int TranscodeJob::remaining_time () const { - float fps = _encoder->current_frames_per_second (); - if (fps == 0) { + if (!_transcoder) { return 0; } + + float fps = _transcoder->current_encoding_rate (); - if (!_film->length()) { + if (fps == 0) { return 0; } /* Compute approximate proposed length here, as it's only here that we need it */ - int length = _film->length().get(); - FrameRateConversion const frc (_film->source_frame_rate(), _film->dcp_frame_rate()); - if (frc.skip) { - length /= 2; - } - /* If we are repeating it shouldn't affect transcode time, so don't take it into account */ - - /* We assume that dcp_length() is valid, if it is set */ - int const left = length - _encoder->video_frames_out(); + OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out(); return left / fps; } diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h index 9b69e4e65..9128206d2 100644 --- a/src/lib/transcode_job.h +++ b/src/lib/transcode_job.h @@ -23,9 +23,8 @@ #include <boost/shared_ptr.hpp> #include "job.h" -#include "options.h" -class Encoder; +class Transcoder; /** @class TranscodeJob * @brief A job which transcodes from one format to another. @@ -33,16 +32,14 @@ class Encoder; class TranscodeJob : public Job { public: - TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od); + TranscodeJob (boost::shared_ptr<const Film> f); std::string name () const; void run (); std::string status () const; -protected: +private: int remaining_time () const; -private: - DecodeOptions _decode_opt; - boost::shared_ptr<Encoder> _encoder; + boost::shared_ptr<Transcoder> _transcoder; }; diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index a202d440c..f4a52639a 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -28,107 +28,65 @@ #include <boost/signals2.hpp> #include "transcoder.h" #include "encoder.h" -#include "decoder_factory.h" #include "film.h" -#include "matcher.h" -#include "delay_line.h" -#include "options.h" -#include "gain.h" #include "video_decoder.h" #include "audio_decoder.h" -#include "trimmer.h" +#include "player.h" +#include "job.h" using std::string; using boost::shared_ptr; +using boost::weak_ptr; using boost::dynamic_pointer_cast; +static void +video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, bool same) +{ + shared_ptr<Encoder> e = encoder.lock (); + if (e) { + e->process_video (image, same); + } +} + +static void +audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio) +{ + shared_ptr<Encoder> e = encoder.lock (); + if (e) { + e->process_audio (audio); + } +} + /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. - * @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, DecodeOptions o, Job* j, shared_ptr<Encoder> e) +Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j) : _job (j) - , _encoder (e) - , _decoders (decoder_factory (f, o)) + , _player (f->player ()) + , _encoder (new Encoder (f, j)) { - assert (_encoder); - - shared_ptr<AudioStream> st = f->audio_stream(); - if (st && st->sample_rate ()) { - _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate())); - } - _delay_line.reset (new DelayLine (f->log(), f->audio_delay() / 1000.0f)); - _gain.reset (new Gain (f->log(), f->audio_gain())); - - int const sr = st ? st->sample_rate() : 0; - int const trim_start = f->trim_type() == Film::ENCODE ? f->trim_start() : 0; - int const trim_end = f->trim_type() == Film::ENCODE ? f->trim_end() : 0; - _trimmer.reset (new Trimmer ( - f->log(), trim_start, trim_end, f->length().get_value_or(0), - sr, f->source_frame_rate(), f->dcp_frame_rate() - )); - - /* Set up the decoder to use the film's set streams */ - _decoders.video->set_subtitle_stream (f->subtitle_stream ()); - if (f->audio_stream ()) { - _decoders.audio->set_audio_stream (f->audio_stream ()); - } - - _decoders.video->connect_video (_delay_line); - if (_matcher) { - _delay_line->connect_video (_matcher); - _matcher->connect_video (_trimmer); - } else { - _delay_line->connect_video (_trimmer); - } - _trimmer->connect_video (_encoder); - - _decoders.audio->connect_audio (_delay_line); - if (_matcher) { - _delay_line->connect_audio (_matcher); - _matcher->connect_audio (_gain); - } else { - _delay_line->connect_audio (_gain); - } - _gain->connect_audio (_trimmer); - _trimmer->connect_audio (_encoder); + _player->Video.connect (bind (video_proxy, _encoder, _1, _2)); + _player->Audio.connect (bind (audio_proxy, _encoder, _1)); } -/** Run the decoder, passing its output to the encoder, until the decoder - * has no more data to present. - */ void Transcoder::go () { _encoder->process_begin (); - - bool done[2] = { false, false }; - - while (1) { - if (!done[0]) { - done[0] = _decoders.video->pass (); - if (_job) { - _decoders.video->set_progress (_job); - } - } - - 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; - } - - if (done[0] && done[1]) { - break; - } - } - - _delay_line->process_end (); - if (_matcher) { - _matcher->process_end (); - } - _gain->process_end (); + while (!_player->pass ()) {} _encoder->process_end (); } + +float +Transcoder::current_encoding_rate () const +{ + return _encoder->current_encoding_rate (); +} + +int +Transcoder::video_frames_out () const +{ + return _encoder->video_frames_out (); +} diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index f5b8ae6e3..b3c8f888b 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -17,28 +17,21 @@ */ +#include "types.h" + /** @file src/transcoder.h - * @brief A class which takes a Film and some Options, then uses those to transcode the film. * * A decoder is selected according to the content type, and the encoder can be specified * as a parameter to the constructor. */ -#include "decoder_factory.h" - class Film; class Job; class Encoder; -class Matcher; class VideoFilter; -class Gain; -class VideoDecoder; -class AudioDecoder; -class DelayLine; -class Trimmer; +class Player; /** @class Transcoder - * @brief A class which takes a Film and some Options, then uses those to transcode the film. * * A decoder is selected according to the content type, and the encoder can be specified * as a parameter to the constructor. @@ -47,27 +40,18 @@ class Transcoder { public: Transcoder ( - boost::shared_ptr<Film> f, - DecodeOptions o, - Job* j, - boost::shared_ptr<Encoder> e + boost::shared_ptr<const Film> f, + boost::shared_ptr<Job> j ); void go (); - boost::shared_ptr<VideoDecoder> video_decoder () const { - return _decoders.video; - } + float current_encoding_rate () const; + int video_frames_out () const; -protected: +private: /** A Job that is running this Transcoder, or 0 */ - Job* _job; - /** The encoder that we will use */ + boost::shared_ptr<Job> _job; + boost::shared_ptr<Player> _player; boost::shared_ptr<Encoder> _encoder; - /** The decoders that we will use */ - Decoders _decoders; - boost::shared_ptr<Matcher> _matcher; - boost::shared_ptr<DelayLine> _delay_line; - boost::shared_ptr<Gain> _gain; - boost::shared_ptr<Trimmer> _trimmer; }; diff --git a/src/lib/types.cc b/src/lib/types.cc new file mode 100644 index 000000000..78cb4cd64 --- /dev/null +++ b/src/lib/types.cc @@ -0,0 +1,56 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "types.h" + +using std::max; +using std::min; + +bool operator== (Crop const & a, Crop const & b) +{ + return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom); +} + +bool operator!= (Crop const & a, Crop const & b) +{ + return !(a == b); +} + + +/** @param other A Rect. + * @return The intersection of this with `other'. + */ +dcpomatic::Rect +dcpomatic::Rect::intersection (Rect const & other) const +{ + int const tx = max (x, other.x); + int const ty = max (y, other.y); + + return Rect ( + tx, ty, + min (x + width, other.x + other.width) - tx, + min (y + height, other.y + other.height) - ty + ); +} + +bool +dcpomatic::Rect::contains (Position p) const +{ + return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height)); +} diff --git a/src/lib/types.h b/src/lib/types.h new file mode 100644 index 000000000..33f8239d8 --- /dev/null +++ b/src/lib/types.h @@ -0,0 +1,118 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_TYPES_H +#define DCPOMATIC_TYPES_H + +#include <vector> +#include <stdint.h> +#include <boost/shared_ptr.hpp> +#include <libdcp/util.h> + +class Content; + +typedef int64_t Time; +#define TIME_MAX INT64_MAX +#define TIME_HZ ((Time) 96000) +typedef int64_t OutputAudioFrame; +typedef int OutputVideoFrame; + +/** @struct Crop + * @brief A description of the crop of an image or video. + */ +struct Crop +{ + Crop () : left (0), right (0), top (0), bottom (0) {} + + /** Number of pixels to remove from the left-hand side */ + int left; + /** Number of pixels to remove from the right-hand side */ + int right; + /** Number of pixels to remove from the top */ + int top; + /** Number of pixels to remove from the bottom */ + int bottom; +}; + +extern bool operator== (Crop const & a, Crop const & b); +extern bool operator!= (Crop const & a, Crop const & b); + +/** @struct Position + * @brief A position. + */ +struct Position +{ + Position () + : x (0) + , y (0) + {} + + Position (int x_, int y_) + : x (x_) + , y (y_) + {} + + /** x coordinate */ + int x; + /** y coordinate */ + int y; +}; + +namespace dcpomatic { + +/** @struct Rect + * @brief A rectangle. + */ +struct Rect +{ + Rect () + : x (0) + , y (0) + , width (0) + , height (0) + {} + + Rect (int x_, int y_, int w_, int h_) + : x (x_) + , y (y_) + , width (w_) + , height (h_) + {} + + int x; + int y; + int width; + int height; + + Position position () const { + return Position (x, y); + } + + libdcp::Size size () const { + return libdcp::Size (width, height); + } + + Rect intersection (Rect const & other) const; + + bool contains (Position) const; +}; + +} + +#endif diff --git a/src/lib/ui_signaller.h b/src/lib/ui_signaller.h index 0d19660bf..73db8bff8 100644 --- a/src/lib/ui_signaller.h +++ b/src/lib/ui_signaller.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_UI_SIGNALLER_H -#define DVDOMATIC_UI_SIGNALLER_H +#ifndef DCPOMATIC_UI_SIGNALLER_H +#define DCPOMATIC_UI_SIGNALLER_H #include <boost/bind.hpp> #include <boost/asio.hpp> diff --git a/src/lib/util.cc b/src/lib/util.cc index 83980f828..d425fc8fe 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -27,7 +27,7 @@ #include <iostream> #include <fstream> #include <climits> -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX #include <execinfo.h> #include <cxxabi.h> #endif @@ -56,32 +56,37 @@ extern "C" { #include "util.h" #include "exceptions.h" #include "scaler.h" -#include "format.h" #include "dcp_content_type.h" #include "filter.h" #include "sound_processor.h" #include "config.h" -#include "film.h" -#ifdef DVDOMATIC_WINDOWS +#include "ratio.h" +#ifdef DCPOMATIC_WINDOWS #include "stack.hpp" #endif #include "i18n.h" -using std::cout; using std::string; using std::stringstream; -using std::list; +using std::setfill; using std::ostream; +using std::endl; using std::vector; +using std::hex; +using std::setw; using std::ifstream; -using std::istream; +using std::ios; using std::min; using std::max; +using std::list; using std::multimap; +using std::istream; +using std::numeric_limits; using std::pair; using std::ofstream; using boost::shared_ptr; +using boost::thread; using boost::lexical_cast; using boost::optional; using libdcp::Size; @@ -114,6 +119,12 @@ seconds_to_hms (int s) return hms.str (); } +string +time_to_hms (Time t) +{ + return seconds_to_hms (t / TIME_HZ); +} + /** @param s Number of seconds. * @return String containing an approximate description of s (e.g. "about 2 hours") */ @@ -150,7 +161,7 @@ seconds_to_approximate_hms (int s) return ap.str (); } -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX /** @param l Mangled C++ identifier. * @return Demangled version. */ @@ -249,7 +260,7 @@ seconds (struct timeval t) return t.tv_sec + (double (t.tv_usec) / 1e6); } -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *) { dbg::stack s; @@ -263,9 +274,9 @@ LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *) * Must be called from the UI thread, if there is one. */ void -dvdomatic_setup () +dcpomatic_setup () { -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS backtrace_file /= g_get_user_config_dir (); backtrace_file /= "backtrace.txt"; SetUnhandledExceptionFilter(exception_handler); @@ -273,7 +284,7 @@ dvdomatic_setup () avfilter_register_all (); - Format::setup_formats (); + Ratio::setup_ratios (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); Filter::setup_filters (); @@ -282,7 +293,7 @@ dvdomatic_setup () ui_thread = boost::this_thread::get_id (); } -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS boost::filesystem::path mo_path () { @@ -297,9 +308,9 @@ mo_path () #endif void -dvdomatic_setup_gettext_i18n (string lang) +dcpomatic_setup_gettext_i18n (string lang) { -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX lang += ".UTF8"; #endif @@ -315,15 +326,15 @@ dvdomatic_setup_gettext_i18n (string lang) } setlocale (LC_ALL, ""); - textdomain ("libdvdomatic"); + textdomain ("libdcpomatic"); -#ifdef DVDOMATIC_WINDOWS - bindtextdomain ("libdvdomatic", mo_path().string().c_str()); - bind_textdomain_codeset ("libdvdomatic", "UTF8"); +#ifdef DCPOMATIC_WINDOWS + bindtextdomain ("libdcpomatic", mo_path().string().c_str()); + bind_textdomain_codeset ("libdcpomatic", "UTF8"); #endif -#ifdef DVDOMATIC_POSIX - bindtextdomain ("libdvdomatic", POSIX_LOCALE_PREFIX); +#ifdef DCPOMATIC_POSIX + bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX); #endif } @@ -384,11 +395,11 @@ md5_digest (void const * data, int size) * @return MD5 digest of file's contents. */ string -md5_digest (string file) +md5_digest (boost::filesystem::path file) { - ifstream f (file.c_str(), std::ios::binary); + ifstream f (file.string().c_str(), std::ios::binary); if (!f.good ()) { - throw OpenFileError (file); + throw OpenFileError (file.string()); } f.seekg (0, std::ios::end); @@ -445,66 +456,11 @@ about_equal (float a, float b) return (fabs (a - b) < 1e-4); } -class FrameRateCandidate -{ -public: - FrameRateCandidate (float source_, int dcp_) - : source (source_) - , dcp (dcp_) - {} - - float source; - int dcp; -}; - -int -best_dcp_frame_rate (float source_fps) -{ - list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); - - /* Work out what rates we could manage, including those achieved by using skip / repeat. */ - list<FrameRateCandidate> candidates; - - /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ - for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { - candidates.push_back (FrameRateCandidate (*i, *i)); - } - - /* Then the skip/repeat ones */ - for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { - candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); - candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); - } - - /* Pick the best one, bailing early if we hit an exact match */ - float error = std::numeric_limits<float>::max (); - optional<FrameRateCandidate> best; - list<FrameRateCandidate>::iterator i = candidates.begin(); - while (i != candidates.end()) { - - if (about_equal (i->source, source_fps)) { - best = *i; - break; - } - - float const e = fabs (i->source - source_fps); - if (e < error) { - error = e; - best = *i; - } - - ++i; - } - - assert (best); - return best->dcp; -} - -/** @param An arbitrary sampling rate. - * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz). +/** @param An arbitrary audio frame rate. + * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ int -dcp_audio_sample_rate (int fs) +dcp_audio_frame_rate (int fs) { if (fs <= 48000) { return 48000; @@ -513,16 +469,6 @@ dcp_audio_sample_rate (int fs) return 96000; } -bool operator== (Crop const & a, Crop const & b) -{ - return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom); -} - -bool operator!= (Crop const & a, Crop const & b) -{ - return !(a == b); -} - /** @param index Colour LUT index. * @return Human-readable name. */ @@ -635,22 +581,6 @@ Socket::read_uint32 () return ntohl (v); } -/** @param other A Rect. - * @return The intersection of this with `other'. - */ -dvdomatic::Rect -dvdomatic::Rect::intersection (Rect const & other) const -{ - int const tx = max (x, other.x); - int const ty = max (y, other.y); - - return Rect ( - tx, ty, - min (x + width, other.x + other.width) - tx, - min (y + height, other.y + other.height) - ty - ); -} - /** Round a number up to the nearest multiple of another number. * @param c Index. * @param s Array of numbers to round, indexed by c. @@ -767,151 +697,6 @@ get_optional_int (multimap<string, string> const & kv, string k) return lexical_cast<int> (i->second); } -/** Construct an AudioBuffers. Audio data is undefined after this constructor. - * @param channels Number of channels. - * @param frames Number of frames to reserve space for. - */ -AudioBuffers::AudioBuffers (int channels, int frames) - : _channels (channels) - , _frames (frames) - , _allocated_frames (frames) -{ - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[frames]; - } -} - -/** Copy constructor. - * @param other Other AudioBuffers; data is copied. - */ -AudioBuffers::AudioBuffers (AudioBuffers const & other) - : _channels (other._channels) - , _frames (other._frames) - , _allocated_frames (other._frames) -{ - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[_frames]; - memcpy (_data[i], other._data[i], _frames * sizeof (float)); - } -} - -/* XXX: it's a shame that this is a copy-and-paste of the above; - probably fixable with c++0x. -*/ -AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> other) - : _channels (other->_channels) - , _frames (other->_frames) - , _allocated_frames (other->_frames) -{ - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[_frames]; - memcpy (_data[i], other->_data[i], _frames * sizeof (float)); - } -} - -/** AudioBuffers destructor */ -AudioBuffers::~AudioBuffers () -{ - for (int i = 0; i < _channels; ++i) { - delete[] _data[i]; - } - - delete[] _data; -} - -/** @param c Channel index. - * @return Buffer for this channel. - */ -float* -AudioBuffers::data (int c) const -{ - assert (c >= 0 && c < _channels); - return _data[c]; -} - -/** Set the number of frames that these AudioBuffers will report themselves - * as having. - * @param f Frames; must be less than or equal to the number of allocated frames. - */ -void -AudioBuffers::set_frames (int f) -{ - assert (f <= _allocated_frames); - _frames = f; -} - -/** Make all samples on all channels silent */ -void -AudioBuffers::make_silent () -{ - for (int i = 0; i < _channels; ++i) { - make_silent (i); - } -} - -/** Make all samples on a given channel silent. - * @param c Channel. - */ -void -AudioBuffers::make_silent (int c) -{ - assert (c >= 0 && c < _channels); - - for (int i = 0; i < _frames; ++i) { - _data[c][i] = 0; - } -} - -/** Copy data from another AudioBuffers to this one. All channels are copied. - * @param from AudioBuffers to copy from; must have the same number of channels as this. - * @param frames_to_copy Number of frames to copy. - * @param read_offset Offset to read from in `from'. - * @param write_offset Offset to write to in `to'. - */ -void -AudioBuffers::copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset) -{ - assert (from->channels() == channels()); - - assert (from); - assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames); - assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames); - - for (int i = 0; i < _channels; ++i) { - memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float)); - } -} - -/** Move audio data around. - * @param from Offset to move from. - * @param to Offset to move to. - * @param frames Number of frames to move. - */ - -void -AudioBuffers::move (int from, int to, int frames) -{ - if (frames == 0) { - return; - } - - assert (from >= 0); - assert (from < _frames); - assert (to >= 0); - assert (to < _frames); - assert (frames > 0); - assert (frames <= _frames); - assert ((from + frames) <= _frames); - assert ((to + frames) <= _frames); - - for (int i = 0; i < _channels; ++i) { - memmove (_data[i] + to, _data[i] + from, frames * sizeof(float)); - } -} - /** Trip an assert if the caller is not in the UI thread */ void ensure_ui_thread () @@ -919,30 +704,17 @@ ensure_ui_thread () assert (boost::this_thread::get_id() == ui_thread); } -/** @param v Source video frame. +/** @param v Content video frame. * @param audio_sample_rate Source audio sample rate. * @param frames_per_second Number of video frames per second. * @return Equivalent number of audio frames for `v'. */ int64_t -video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second) +video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second) { return ((int64_t) v * audio_sample_rate / frames_per_second); } -/** @param f Filename. - * @return true if this file is a still image, false if it is something else. - */ -bool -still_image_file (string f) -{ - string ext = boost::filesystem::path(f).extension().string(); - - transform (ext.begin(), ext.end(), ext.begin(), ::tolower); - - return (ext == N_(".tif") || ext == N_(".tiff") || ext == N_(".jpg") || ext == N_(".jpeg") || ext == N_(".png") || ext == N_(".bmp")); -} - string audio_channel_name (int c) { @@ -963,71 +735,6 @@ audio_channel_name (int c) return channels[c]; } -AudioMapping::AudioMapping (shared_ptr<const Film> f) - : _source_channels (f->audio_stream() ? f->audio_stream()->channels() : 0) - , _minimum_channels (f->minimum_audio_channels ()) -{ - -} - -optional<libdcp::Channel> -AudioMapping::source_to_dcp (int c) const -{ - if (c >= _source_channels) { - return optional<libdcp::Channel> (); - } - - if (_source_channels == 1) { - /* mono sources to centre */ - return libdcp::CENTRE; - } - - return static_cast<libdcp::Channel> (c); -} - -optional<int> -AudioMapping::dcp_to_source (libdcp::Channel c) const -{ - if (_source_channels == 1) { - if (c == libdcp::CENTRE) { - return 0; - } else { - return optional<int> (); - } - } - - if (static_cast<int> (c) >= _source_channels) { - return optional<int> (); - } - - return static_cast<int> (c); -} - -/** @return minimum number of DCP channels that we can allow in this - DCP, given the nature of the source. -*/ -int -AudioMapping::minimum_dcp_channels () const -{ - if (_source_channels == 1) { - /* The source is mono, so to put the mono channel into - the centre we need to generate a 5.1 soundtrack. - */ - return 6; - } - - return _source_channels; -} - -/** @return number of channels that there should be in the DCP, including - * any silent padded ones. - */ -int -AudioMapping::dcp_channels () const -{ - return max (_source_channels, _minimum_channels); -} - FrameRateConversion::FrameRateConversion (float source, int dcp) : skip (false) , repeat (false) diff --git a/src/lib/util.h b/src/lib/util.h index c9e5bef16..7af8ffedf 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -22,8 +22,8 @@ * @brief Some utility functions and classes. */ -#ifndef DVDOMATIC_UTIL_H -#define DVDOMATIC_UTIL_H +#ifndef DCPOMATIC_UTIL_H +#define DCPOMATIC_UTIL_H #include <string> #include <vector> @@ -37,8 +37,10 @@ extern "C" { #include <libavfilter/avfilter.h> } #include "compose.hpp" +#include "types.h" +#include "video_content.h" -#ifdef DVDOMATIC_DEBUG +#ifdef DCPOMATIC_DEBUG #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING); #else #define TIMING(...) @@ -53,23 +55,22 @@ class Scaler; class Film; extern std::string seconds_to_hms (int); +extern std::string time_to_hms (Time); extern std::string seconds_to_approximate_hms (int); extern void stacktrace (std::ostream &, int); extern std::string dependency_version_summary (); extern double seconds (struct timeval); -extern void dvdomatic_setup (); -extern void dvdomatic_setup_gettext_i18n (std::string); +extern void dcpomatic_setup (); +extern void dcpomatic_setup_gettext_i18n (std::string); extern std::vector<std::string> split_at_spaces_considering_quotes (std::string); -extern std::string md5_digest (std::string); +extern std::string md5_digest (boost::filesystem::path); extern std::string md5_digest (void const *, int); extern void ensure_ui_thread (); extern std::string audio_channel_name (int); -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS extern boost::filesystem::path mo_path (); #endif -typedef int SourceFrame; - struct FrameRateConversion { FrameRateConversion (float, int); @@ -105,96 +106,8 @@ struct FrameRateConversion std::string description; }; -int best_dcp_frame_rate (float); - -enum ContentType { - STILL, ///< content is still images - VIDEO ///< content is a video -}; - -/** @struct Crop - * @brief A description of the crop of an image or video. - */ -struct Crop -{ - Crop () : left (0), right (0), top (0), bottom (0) {} - - /** Number of pixels to remove from the left-hand side */ - int left; - /** Number of pixels to remove from the right-hand side */ - int right; - /** Number of pixels to remove from the top */ - int top; - /** Number of pixels to remove from the bottom */ - int bottom; -}; - -extern bool operator== (Crop const & a, Crop const & b); -extern bool operator!= (Crop const & a, Crop const & b); - -/** @struct Position - * @brief A position. - */ -struct Position -{ - Position () - : x (0) - , y (0) - {} - - Position (int x_, int y_) - : x (x_) - , y (y_) - {} - - /** x coordinate */ - int x; - /** y coordinate */ - int y; -}; - -namespace dvdomatic -{ - -/** @struct Rect - * @brief A rectangle. - */ -struct Rect -{ - Rect () - : x (0) - , y (0) - , width (0) - , height (0) - {} - - Rect (int x_, int y_, int w_, int h_) - : x (x_) - , y (y_) - , width (w_) - , height (h_) - {} - - int x; - int y; - int width; - int height; - - Position position () const { - return Position (x, y); - } - - libdcp::Size size () const { - return libdcp::Size (width, height); - } - - Rect intersection (Rect const & other) const; -}; - -} - extern std::string crop_string (Position, libdcp::Size); -extern int dcp_audio_sample_rate (int); +extern int dcp_audio_frame_rate (int); extern std::string colour_lut_index_to_name (int index); extern int stride_round_up (int, int const *, int); extern int stride_lookup (int c, int const * stride); @@ -207,7 +120,7 @@ extern std::string get_optional_string (std::multimap<std::string, std::string> /** @class Socket * @brief A class to wrap a boost::asio::ip::tcp::socket with some things - * that are useful for DVD-o-matic. + * that are useful for DCP-o-matic. * * This class wraps some things that I could not work out how to do with boost; * most notably, sync read/write calls with timeouts. @@ -241,68 +154,7 @@ private: int _timeout; }; -/** @class AudioBuffers - * @brief A class to hold multi-channel audio data in float format. - */ -class AudioBuffers -{ -public: - AudioBuffers (int channels, int frames); - AudioBuffers (AudioBuffers const &); - AudioBuffers (boost::shared_ptr<const AudioBuffers>); - ~AudioBuffers (); - - float** data () const { - return _data; - } - - float* data (int) const; - - int channels () const { - return _channels; - } - - int frames () const { - return _frames; - } - - void set_frames (int f); - - void make_silent (); - void make_silent (int c); - - void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset); - void move (int from, int to, int frames); - -private: - /** Number of channels */ - int _channels; - /** Number of frames (where a frame is one sample across all channels) */ - int _frames; - /** Number of frames that _data can hold */ - int _allocated_frames; - /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */ - float** _data; -}; - -class AudioMapping -{ -public: - AudioMapping (boost::shared_ptr<const Film>); - - boost::optional<libdcp::Channel> source_to_dcp (int c) const; - boost::optional<int> dcp_to_source (libdcp::Channel c) const; - - int minimum_dcp_channels () const; - int dcp_channels () const; - -private: - int _source_channels; - int _minimum_channels; -}; - -extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second); -extern bool still_image_file (std::string); +extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second); class LocaleGuard { diff --git a/src/lib/version.h b/src/lib/version.h index e1ec9067c..b70be8343 100644 --- a/src/lib/version.h +++ b/src/lib/version.h @@ -1,4 +1,4 @@ -extern char const * dvdomatic_version; -extern char const * dvdomatic_git_commit; -extern char const * dvdomatic_cxx_flags; +extern char const * dcpomatic_version; +extern char const * dcpomatic_git_commit; +extern char const * dcpomatic_cxx_flags; diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc new file mode 100644 index 000000000..3818fa792 --- /dev/null +++ b/src/lib/video_content.cc @@ -0,0 +1,224 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <iomanip> +#include <libcxml/cxml.h> +#include "video_content.h" +#include "video_examiner.h" +#include "ratio.h" +#include "compose.hpp" + +#include "i18n.h" + +int const VideoContentProperty::VIDEO_SIZE = 0; +int const VideoContentProperty::VIDEO_FRAME_RATE = 1; +int const VideoContentProperty::VIDEO_CROP = 2; +int const VideoContentProperty::VIDEO_RATIO = 3; + +using std::string; +using std::stringstream; +using std::setprecision; +using boost::shared_ptr; +using boost::lexical_cast; +using boost::optional; + +VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len) + : Content (f, s) + , _video_length (len) + , _video_frame_rate (0) + , _ratio (Ratio::from_id ("185")) +{ + +} + +VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , _video_length (0) + , _video_frame_rate (0) + , _ratio (Ratio::from_id ("185")) +{ + +} + +VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + : Content (f, node) +{ + _video_length = node->number_child<VideoContent::Frame> ("VideoLength"); + _video_size.width = node->number_child<int> ("VideoWidth"); + _video_size.height = node->number_child<int> ("VideoHeight"); + _video_frame_rate = node->number_child<float> ("VideoFrameRate"); + _crop.left = node->number_child<int> ("LeftCrop"); + _crop.right = node->number_child<int> ("RightCrop"); + _crop.top = node->number_child<int> ("TopCrop"); + _crop.bottom = node->number_child<int> ("BottomCrop"); + optional<string> r = node->optional_string_child ("Ratio"); + if (r) { + _ratio = Ratio::from_id (r.get ()); + } +} + +VideoContent::VideoContent (VideoContent const & o) + : Content (o) + , _video_length (o._video_length) + , _video_size (o._video_size) + , _video_frame_rate (o._video_frame_rate) + , _ratio (o._ratio) +{ + +} + +void +VideoContent::as_xml (xmlpp::Node* node) const +{ + boost::mutex::scoped_lock lm (_mutex); + node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length)); + node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width)); + node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height)); + node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate)); + node->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left)); + node->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right)); + node->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top)); + node->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom)); + if (_ratio) { + node->add_child("Ratio")->add_child_text (_ratio->id ()); + } +} + +void +VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d) +{ + /* These examiner calls could call other content methods which take a lock on the mutex */ + libdcp::Size const vs = d->video_size (); + float const vfr = d->video_frame_rate (); + + { + boost::mutex::scoped_lock lm (_mutex); + _video_size = vs; + _video_frame_rate = vfr; + } + + signal_changed (VideoContentProperty::VIDEO_SIZE); + signal_changed (VideoContentProperty::VIDEO_FRAME_RATE); +} + + +string +VideoContent::information () const +{ + if (video_size().width == 0 || video_size().height == 0) { + return ""; + } + + stringstream s; + + s << String::compose ( + _("%1x%2 pixels (%3:1)"), + video_size().width, + video_size().height, + setprecision (3), float (video_size().width) / video_size().height + ); + + return s.str (); +} + +void +VideoContent::set_crop (Crop c) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _crop = c; + } + signal_changed (VideoContentProperty::VIDEO_CROP); +} + +void +VideoContent::set_left_crop (int c) +{ + { + boost::mutex::scoped_lock lm (_mutex); + + if (_crop.left == c) { + return; + } + + _crop.left = c; + } + + signal_changed (VideoContentProperty::VIDEO_CROP); +} + +void +VideoContent::set_right_crop (int c) +{ + { + boost::mutex::scoped_lock lm (_mutex); + if (_crop.right == c) { + return; + } + + _crop.right = c; + } + + signal_changed (VideoContentProperty::VIDEO_CROP); +} + +void +VideoContent::set_top_crop (int c) +{ + { + boost::mutex::scoped_lock lm (_mutex); + if (_crop.top == c) { + return; + } + + _crop.top = c; + } + + signal_changed (VideoContentProperty::VIDEO_CROP); +} + +void +VideoContent::set_bottom_crop (int c) +{ + { + boost::mutex::scoped_lock lm (_mutex); + if (_crop.bottom == c) { + return; + } + + _crop.bottom = c; + } + + signal_changed (VideoContentProperty::VIDEO_CROP); +} + +void +VideoContent::set_ratio (Ratio const * r) +{ + { + boost::mutex::scoped_lock lm (_mutex); + if (_ratio == r) { + return; + } + + _ratio = r; + } + + signal_changed (VideoContentProperty::VIDEO_RATIO); +} diff --git a/src/lib/video_content.h b/src/lib/video_content.h new file mode 100644 index 000000000..372cab3bd --- /dev/null +++ b/src/lib/video_content.h @@ -0,0 +1,95 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef DCPOMATIC_VIDEO_CONTENT_H +#define DCPOMATIC_VIDEO_CONTENT_H + +#include "content.h" + +class VideoExaminer; +class Ratio; + +class VideoContentProperty +{ +public: + static int const VIDEO_SIZE; + static int const VIDEO_FRAME_RATE; + static int const VIDEO_CROP; + static int const VIDEO_RATIO; +}; + +class VideoContent : public virtual Content +{ +public: + typedef int Frame; + + VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame); + VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path); + VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + VideoContent (VideoContent const &); + + void as_xml (xmlpp::Node *) const; + virtual std::string information () const; + + VideoContent::Frame video_length () const { + boost::mutex::scoped_lock lm (_mutex); + return _video_length; + } + + libdcp::Size video_size () const { + boost::mutex::scoped_lock lm (_mutex); + return _video_size; + } + + float video_frame_rate () const { + boost::mutex::scoped_lock lm (_mutex); + return _video_frame_rate; + } + + void set_crop (Crop); + void set_left_crop (int); + void set_right_crop (int); + void set_top_crop (int); + void set_bottom_crop (int); + + Crop crop () const { + boost::mutex::scoped_lock lm (_mutex); + return _crop; + } + + void set_ratio (Ratio const *); + + Ratio const * ratio () const { + boost::mutex::scoped_lock lm (_mutex); + return _ratio; + } + +protected: + void take_from_video_examiner (boost::shared_ptr<VideoExaminer>); + + VideoContent::Frame _video_length; + +private: + libdcp::Size _video_size; + float _video_frame_rate; + Crop _crop; + Ratio const * _ratio; +}; + +#endif diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 16a076698..f61e63d4d 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -21,74 +21,42 @@ #include "subtitle.h" #include "film.h" #include "image.h" -#include "log.h" -#include "options.h" -#include "job.h" +#include "ratio.h" #include "i18n.h" using std::cout; using boost::shared_ptr; -using boost::optional; -VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) - , _video_frame (0) - , _last_source_time (0) +VideoDecoder::VideoDecoder (shared_ptr<const Film> f) + : Decoder (f) + , _video_position (0) { } -/** Called by subclasses to tell the world that some video data is ready. - * We find a subtitle then emit it for listeners. - * @param image frame to emit. - * @param t Time of the frame within the source, in seconds. - */ void -VideoDecoder::emit_video (shared_ptr<Image> image, bool same, double t) +VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame) { - shared_ptr<Subtitle> sub; - if (_timed_subtitle && _timed_subtitle->displayed_at (t)) { - sub = _timed_subtitle->subtitle (); - } - - Video (image, same, sub, t); - ++_video_frame; - - _last_source_time = t; + Video (image, same, frame); + _video_position = frame + 1; } -/** Set up the current subtitle. This will be put onto frames that - * fit within its time specification. s may be 0 to say that there - * is no current subtitle. +#if 0 + +/** Called by subclasses when a subtitle is ready. + * s may be 0 to say that there is no current subtitle. * @param s New current subtitle, or 0. */ void -VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s) +VideoDecoder::subtitle (shared_ptr<TimedSubtitle> s) { _timed_subtitle = s; 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)); + _timed_subtitle->subtitle()->set_position (Position (p.x - _video_content->crop().left, p.y - _video_content->crop().top)); } } +#endif -/** Set which stream of subtitles we should use from our source. - * @param s Stream to use. - */ -void -VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) -{ - _subtitle_stream = s; -} - -void -VideoDecoder::set_progress (Job* j) const -{ - assert (j); - - if (_film->length()) { - j->set_progress (float (_video_frame) / _film->length().get()); - } -} diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 6e4fd48c0..d24219d95 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -17,67 +17,33 @@ */ -#ifndef DVDOMATIC_VIDEO_DECODER_H -#define DVDOMATIC_VIDEO_DECODER_H +#ifndef DCPOMATIC_VIDEO_DECODER_H +#define DCPOMATIC_VIDEO_DECODER_H -#include "video_source.h" -#include "stream.h" #include "decoder.h" +#include "util.h" -class VideoDecoder : public TimedVideoSource, public virtual Decoder +class VideoContent; + +class VideoDecoder : public virtual Decoder { public: - VideoDecoder (boost::shared_ptr<Film>, DecodeOptions); - - /** @return video frames per second, or 0 if unknown */ - virtual float frames_per_second () const = 0; - /** @return native size in pixels */ - virtual libdcp::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; - virtual int sample_aspect_ratio_numerator () const = 0; - virtual int sample_aspect_ratio_denominator () const = 0; + VideoDecoder (boost::shared_ptr<const Film>); - virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); + virtual void seek (VideoContent::Frame) = 0; + virtual void seek_back () = 0; - void set_progress (Job *) const; + /** Emitted when a video frame is ready. + * First parameter is the video image. + * Second parameter is true if the image is the same as the last one that was emitted. + * Third parameter is the frame within our source. + */ + boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, VideoContent::Frame)> Video; - int video_frame () const { - return _video_frame; - } - - boost::shared_ptr<SubtitleStream> subtitle_stream () const { - return _subtitle_stream; - } - - std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const { - return _subtitle_streams; - } - - double last_source_time () const { - return _last_source_time; - } - protected: - - virtual PixelFormat pixel_format () const = 0; - - void emit_video (boost::shared_ptr<Image>, bool, double); - void emit_subtitle (boost::shared_ptr<TimedSubtitle>); - /** Subtitle stream to use when decoding */ - boost::shared_ptr<SubtitleStream> _subtitle_stream; - /** Subtitle streams that this decoder's content has */ - std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams; - -private: - int _video_frame; - double _last_source_time; - - boost::shared_ptr<TimedSubtitle> _timed_subtitle; + void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame); + VideoContent::Frame _video_position; }; #endif diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h new file mode 100644 index 000000000..72f6ccc12 --- /dev/null +++ b/src/lib/video_examiner.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <libdcp/types.h> +#include "types.h" +#include "video_content.h" + +class VideoExaminer +{ +public: + virtual float video_frame_rate () const = 0; + virtual libdcp::Size video_size () const = 0; + virtual VideoContent::Frame video_length () const = 0; +}; diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h deleted file mode 100644 index 0170c7350..000000000 --- a/src/lib/video_sink.h +++ /dev/null @@ -1,52 +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. - -*/ - -#ifndef DVDOMATIC_VIDEO_SINK_H -#define DVDOMATIC_VIDEO_SINK_H - -#include <boost/shared_ptr.hpp> -#include "util.h" - -class Subtitle; -class Image; - -class VideoSink -{ -public: - /** Call with a frame of video. - * @param i Video frame image. - * @param same true if i is the same as last time we were called. - * @param s A subtitle that should be on this frame, or 0. - */ - virtual void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0; -}; - -class TimedVideoSink -{ -public: - /** Call with a frame of video. - * @param i Video frame image. - * @param same true if i is the same as last time we were called. - * @param s A subtitle that should be on this frame, or 0. - * @param t Source timestamp. - */ - virtual void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s, double t) = 0; -}; - -#endif diff --git a/src/lib/video_source.h b/src/lib/video_source.h deleted file mode 100644 index 748cb6fe9..000000000 --- a/src/lib/video_source.h +++ /dev/null @@ -1,72 +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/video_source.h - * @brief Parent class for classes which emit video data. - */ - -#ifndef DVDOMATIC_VIDEO_SOURCE_H -#define DVDOMATIC_VIDEO_SOURCE_H - -#include <boost/shared_ptr.hpp> -#include <boost/signals2.hpp> -#include "util.h" - -class VideoSink; -class TimedVideoSink; -class Subtitle; -class Image; - -/** @class VideoSource - * @param A class that emits video data without timestamps. - */ -class VideoSource -{ -public: - - /** Emitted when a video frame is ready. - * First parameter is the video image. - * Second parameter is true if the image is the same as the last one that was emitted. - * Third parameter is either 0 or a subtitle that should be on this frame. - */ - boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>)> Video; - - void connect_video (boost::shared_ptr<VideoSink>); -}; - -/** @class TimedVideoSource - * @param A class that emits video data with timestamps. - */ -class TimedVideoSource -{ -public: - - /** Emitted when a video frame is ready. - * First parameter is the video image. - * Second parameter is true if the image is the same as the last one that was emitted. - * Third parameter is either 0 or a subtitle that should be on this frame. - * Fourth parameter is the source timestamp of this frame. - */ - boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double)> Video; - - void connect_video (boost::shared_ptr<VideoSink>); - void connect_video (boost::shared_ptr<TimedVideoSink>); -}; - -#endif diff --git a/src/lib/writer.cc b/src/lib/writer.cc index cff0b5be2..cbb84a940 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -23,14 +23,19 @@ #include <libdcp/sound_asset.h> #include <libdcp/picture_frame.h> #include <libdcp/reel.h> +#include <libdcp/dcp.h> #include <libdcp/cpl.h> #include "writer.h" #include "compose.hpp" #include "film.h" -#include "format.h" +#include "ratio.h" #include "log.h" #include "dcp_video_frame.h" +#include "dcp_content_type.h" +#include "player.h" +#include "audio_mapping.h" #include "config.h" +#include "job.h" #include "i18n.h" @@ -44,8 +49,9 @@ using boost::shared_ptr; int const Writer::_maximum_frames_in_memory = 8; -Writer::Writer (shared_ptr<Film> f) +Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j) : _film (f) + , _job (j) , _first_nonexistant_frame (0) , _thread (0) , _finish (false) @@ -70,28 +76,24 @@ Writer::Writer (shared_ptr<Film> f) new libdcp::MonoPictureAsset ( _film->internal_video_mxf_dir (), _film->internal_video_mxf_filename (), - _film->dcp_frame_rate (), - _film->format()->dcp_size () + _film->dcp_video_frame_rate (), + _film->container()->size (_film->full_frame ()) ) ); _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0); - AudioMapping m (_film); + _sound_asset.reset ( + new libdcp::SoundAsset ( + _film->dir (_film->dcp_name()), + _film->dcp_audio_mxf_filename (), + _film->dcp_video_frame_rate (), + _film->dcp_audio_channels (), + _film->dcp_audio_frame_rate() + ) + ); - if (m.dcp_channels() > 0) { - _sound_asset.reset ( - new libdcp::SoundAsset ( - _film->dir (_film->dcp_name()), - _film->dcp_audio_mxf_filename (), - _film->dcp_frame_rate (), - m.dcp_channels (), - dcp_audio_sample_rate (_film->audio_stream()->sample_rate()) - ) - ); - - _sound_asset_writer = _sound_asset->start_write (); - } + _sound_asset_writer = _sound_asset->start_write (); _thread = new boost::thread (boost::bind (&Writer::thread, this)); } @@ -201,6 +203,10 @@ try } } lock.lock (); + + if (_film->length ()) { + _job->set_progress (float(_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length())); + } ++_last_written_frame; } @@ -256,21 +262,11 @@ Writer::finish () _thread = 0; _picture_asset_writer->finalize (); - - if (_sound_asset_writer) { - _sound_asset_writer->finalize (); - } - + _sound_asset_writer->finalize (); + int const frames = _last_written_frame + 1; - int duration = 0; - if (_film->trim_type() == Film::CPL) { - duration = frames - _film->trim_start() - _film->trim_end(); - _picture_asset->set_entry_point (_film->trim_start ()); - } else { - duration = frames; - } - _picture_asset->set_duration (duration); + _picture_asset->set_duration (frames); /* Hard-link the video MXF into the DCP */ @@ -294,13 +290,7 @@ Writer::finish () _picture_asset->set_directory (_film->dir (_film->dcp_name ())); _picture_asset->set_file_name (_film->dcp_video_mxf_filename ()); - - if (_sound_asset) { - if (_film->trim_type() == Film::CPL) { - _sound_asset->set_entry_point (_film->trim_start ()); - } - _sound_asset->set_duration (duration); - } + _sound_asset->set_duration (frames); libdcp::DCP dcp (_film->dir (_film->dcp_name())); @@ -310,7 +300,7 @@ Writer::finish () _film->dcp_name(), _film->dcp_content_type()->libdcp_kind (), frames, - _film->dcp_frame_rate () + _film->dcp_video_frame_rate () ) ); diff --git a/src/lib/writer.h b/src/lib/writer.h index beb16c7b9..e56e12e75 100644 --- a/src/lib/writer.h +++ b/src/lib/writer.h @@ -26,6 +26,7 @@ class Film; class EncodedData; class AudioBuffers; +class Job; namespace libdcp { class MonoPictureAsset; @@ -63,7 +64,7 @@ bool operator== (QueueItem const & a, QueueItem const & b); class Writer : public ExceptionStore { public: - Writer (boost::shared_ptr<Film>); + Writer (boost::shared_ptr<const Film>, boost::shared_ptr<Job>); bool can_fake_write (int) const; @@ -79,7 +80,8 @@ private: void check_existing_picture_mxf (); /** our Film */ - boost::shared_ptr<Film> _film; + boost::shared_ptr<const Film> _film; + boost::shared_ptr<Job> _job; /** the first frame index that does not already exist in our MXF */ int _first_nonexistant_frame; diff --git a/src/lib/wscript b/src/lib/wscript index 7c7a64d58..2f8653984 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -2,53 +2,57 @@ import os import i18n sources = """ - ab_transcode_job.cc - ab_transcoder.cc analyse_audio_job.cc audio_analysis.cc + audio_buffers.cc + audio_content.cc audio_decoder.cc - audio_source.cc + audio_mapping.cc config.cc - combiner.cc + content.cc cross.cc dci_metadata.cc dcp_content_type.cc dcp_video_frame.cc decoder.cc - decoder_factory.cc - delay_line.cc dolby_cp750.cc encoder.cc examine_content_job.cc exceptions.cc filter_graph.cc + ffmpeg.cc + ffmpeg_content.cc ffmpeg_decoder.cc + ffmpeg_examiner.cc film.cc filter.cc - format.cc - gain.cc image.cc + imagemagick_content.cc imagemagick_decoder.cc + imagemagick_examiner.cc job.cc job_manager.cc log.cc lut.cc - matcher.cc + player.cc + playlist.cc + ratio.cc + resampler.cc scp_dcp_job.cc scaler.cc server.cc + sndfile_content.cc sndfile_decoder.cc sound_processor.cc - stream.cc subtitle.cc timer.cc transcode_job.cc transcoder.cc - trimmer.cc + types.cc ui_signaller.cc util.cc + video_content.cc video_decoder.cc - video_source.cc writer.cc """ @@ -58,12 +62,12 @@ def build(bld): else: obj = bld(features = 'cxx cxxshlib') - obj.name = 'libdvdomatic' + obj.name = 'libdcpomatic' obj.export_includes = ['.'] obj.uselib = """ AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 - SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB LZMA + SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA """ obj.source = sources + ' version.cc' @@ -71,13 +75,15 @@ def build(bld): if bld.env.TARGET_WINDOWS: obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI' obj.source += ' stack.cpp' + if bld.env.STATIC: + obj.uselib += ' XML++' + obj.source = sources + " version.cc" + obj.target = 'dcpomatic' - obj.target = 'dvdomatic' - - i18n.po_to_mo(os.path.join('src', 'lib'), 'libdvdomatic', bld) + i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld) def pot(bld): - i18n.pot(os.path.join('src', 'lib'), sources, 'libdvdomatic') + i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic') def pot_merge(bld): - i18n.pot_merge(os.path.join('src', 'lib'), 'libdvdomatic') + i18n.pot_merge(os.path.join('src', 'lib'), 'libdcpomatic') diff --git a/src/tools/dvdomatic.cc b/src/tools/dcpomatic.cc index e0629bb98..ac39d4fed 100644 --- a/src/tools/dvdomatic.cc +++ b/src/tools/dcpomatic.cc @@ -40,12 +40,9 @@ #include "wx/wx_ui_signaller.h" #include "wx/about_dialog.h" #include "lib/film.h" -#include "lib/format.h" #include "lib/config.h" -#include "lib/filter.h" #include "lib/util.h" -#include "lib/scaler.h" -#include "lib/exceptions.h" +#include "lib/version.h" #include "lib/ui_signaller.h" #include "lib/log.h" @@ -64,6 +61,7 @@ static FilmViewer* film_viewer = 0; static shared_ptr<Film> film; static std::string log_level; static std::string film_to_load; +static std::string film_to_create; static wxMenu* jobs_menu = 0; static void set_menu_sensitivity (); @@ -151,7 +149,6 @@ enum { ID_jobs_make_dcp, ID_jobs_send_dcp_to_tms, ID_jobs_show_dcp, - ID_jobs_analyse_audio, }; void @@ -180,12 +177,10 @@ setup_menu (wxMenuBar* m) add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM); add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM); add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM); - jobs_menu->AppendSeparator (); - add_item (jobs_menu, _("&Analyse audio"), ID_jobs_analyse_audio, NEEDS_FILM); wxMenu* help = new wxMenu; #ifdef __WXOSX__ - add_item (help, _("About DVD-o-matic"), wxID_ABOUT, ALWAYS); + add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS); #else add_item (help, _("About"), wxID_ABOUT, ALWAYS); #endif @@ -224,34 +219,24 @@ public: Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp)); Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms)); Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp)); - Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio)); Connect (wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about)); Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened)); - wxPanel* panel = new wxPanel (this); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - s->Add (panel, 1, wxEXPAND); - SetSizer (s); + film_editor = new FilmEditor (film, this); + film_viewer = new FilmViewer (film, this); + JobManagerView* job_manager_view = new JobManagerView (this, static_cast<JobManagerView::Buttons> (0)); - film_editor = new FilmEditor (film, panel); - film_viewer = new FilmViewer (film, panel); - JobManagerView* job_manager_view = new JobManagerView (panel, static_cast<JobManagerView::Buttons> (0)); + wxBoxSizer* right_sizer = new wxBoxSizer (wxVERTICAL); + right_sizer->Add (film_viewer, 2, wxEXPAND | wxALL, 6); + right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6); - _top_sizer = new wxBoxSizer (wxHORIZONTAL); - _top_sizer->Add (film_editor, 0, wxALL, 6); - _top_sizer->Add (film_viewer, 1, wxEXPAND | wxALL, 6); - - wxBoxSizer* main_sizer = new wxBoxSizer (wxVERTICAL); - main_sizer->Add (_top_sizer, 2, wxEXPAND | wxALL, 6); - main_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6); - panel->SetSizer (main_sizer); + wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL); + main_sizer->Add (film_editor, 1, wxEXPAND | wxALL, 6); + main_sizer->Add (right_sizer, 2, wxEXPAND | wxALL, 6); set_menu_sensitivity (); - /* XXX: calling these here is a bit of a hack */ - film_editor->setup_visibility (); - film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1)); if (film) { file_changed (film->directory ()); @@ -260,22 +245,11 @@ public: } set_film (); - - film_editor->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Frame::film_editor_sized), 0, this); + SetSizer (main_sizer); } private: - void film_editor_sized (wxSizeEvent &) - { - static bool in_layout = false; - if (!in_layout) { - in_layout = true; - _top_sizer->Layout (); - in_layout = false; - } - } - void menu_opened (wxMenuEvent& ev) { if (ev.GetMenu() != jobs_menu) { @@ -297,7 +271,7 @@ private: void file_changed (string f) { stringstream s; - s << wx_to_std (_("DVD-o-matic")); + s << wx_to_std (_("DCP-o-matic")); if (!f.empty ()) { s << " - " << f; } @@ -326,7 +300,8 @@ private: } maybe_save_then_delete_film (); - film.reset (new Film (d->get_path (), false)); + film.reset (new Film (d->get_path ())); + film->write_metadata (); film->log()->set_level (log_level); film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string()); set_film (); @@ -420,31 +395,26 @@ private: #endif } - void jobs_analyse_audio (wxCommandEvent &) - { - film->analyse_audio (); - } - void help_about (wxCommandEvent &) { AboutDialog* d = new AboutDialog (this); d->ShowModal (); d->Destroy (); } - - wxSizer* _top_sizer; }; #if wxMINOR_VERSION == 9 static const wxCmdLineEntryDesc command_line_description[] = { { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, - { wxCMD_LINE_PARAM, 0, 0, "film to load", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 } }; #else static const wxCmdLineEntryDesc command_line_description[] = { { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, - { wxCMD_LINE_PARAM, 0, 0, wxT("film to load"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 } }; #endif @@ -457,7 +427,7 @@ class App : public wxApp return false; } -#ifdef DVDOMATIC_LINUX +#ifdef DCPOMATIC_LINUX unsetenv ("UBUNTU_MENUPROXY"); #endif @@ -471,16 +441,16 @@ class App : public wxApp /* Enable i18n; this will create a Config object to look for a force-configured language. This Config - object will be wrong, however, because dvdomatic_setup + object will be wrong, however, because dcpomatic_setup hasn't yet been called and there aren't any scalers, filters etc. set up yet. */ - dvdomatic_setup_i18n (); + dcpomatic_setup_i18n (); /* Set things up, including scalers / filters etc. which will now be internationalised correctly. */ - dvdomatic_setup (); + dcpomatic_setup (); /* Force the configuration to be re-loaded correctly next time it is needed. @@ -490,13 +460,21 @@ class App : public wxApp if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) { try { film.reset (new Film (film_to_load)); + film->read_metadata (); film->log()->set_level (log_level); } catch (exception& e) { error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what()))); } } - Frame* f = new Frame (_("DVD-o-matic")); + if (!film_to_create.empty ()) { + film.reset (new Film (film_to_create)); + film->write_metadata (); + film->log()->set_level (log_level); + film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ()); + } + + Frame* f = new Frame (_("DCP-o-matic")); SetTopWindow (f); f->Maximize (); f->Show (); @@ -516,11 +494,15 @@ class App : public wxApp bool OnCmdLineParsed (wxCmdLineParser& parser) { if (parser.GetParamCount() > 0) { - film_to_load = wx_to_std (parser.GetParam(0)); + if (parser.Found (wxT ("new"))) { + film_to_create = wx_to_std (parser.GetParam (0)); + } else { + film_to_load = wx_to_std (parser.GetParam(0)); + } } wxString log; - if (parser.Found(wxT("log"), &log)) { + if (parser.Found (wxT ("log"), &log)) { log_level = wx_to_std (log); } diff --git a/src/tools/dvdomatic_batch.cc b/src/tools/dcpomatic_batch.cc index d9ddb9d46..b4ab054fd 100644 --- a/src/tools/dvdomatic_batch.cc +++ b/src/tools/dcpomatic_batch.cc @@ -132,11 +132,11 @@ private: void help_about (wxCommandEvent &) { wxAboutDialogInfo info; - info.SetName (_("DVD-o-matic Batch Converter")); - if (strcmp (dvdomatic_git_commit, "release") == 0) { - info.SetVersion (std_to_wx (String::compose ("version %1", dvdomatic_version))); + info.SetName (_("DCP-o-matic Batch Converter")); + if (strcmp (dcpomatic_git_commit, "release") == 0) { + info.SetVersion (std_to_wx (String::compose ("version %1", dcpomatic_version))); } else { - info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dvdomatic_version, dvdomatic_git_commit))); + info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dcpomatic_version, dcpomatic_git_commit))); } info.SetDescription (_("Free, open-source DCP generation from almost anything.")); info.SetCopyright (_("(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen")); @@ -157,7 +157,7 @@ private: translators.Add (wxT ("Adam Klotblixt")); info.SetTranslators (translators); - info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic")); + info.SetWebSite (wxT ("http://carlh.net/software/dcpomatic")); wxAboutBox (info); } @@ -177,6 +177,7 @@ private: if (r == wxID_OK) { try { shared_ptr<Film> film (new Film (wx_to_std (c->GetPath ()))); + film->read_metadata (); film->make_dcp (); } catch (std::exception& e) { wxString p = c->GetPath (); @@ -197,29 +198,29 @@ class App : public wxApp return false; } -#ifdef DVDOMATIC_LINUX +#ifdef DCPOMATIC_LINUX unsetenv ("UBUNTU_MENUPROXY"); #endif /* Enable i18n; this will create a Config object to look for a force-configured language. This Config - object will be wrong, however, because dvdomatic_setup + object will be wrong, however, because dcpomatic_setup hasn't yet been called and there aren't any scalers, filters etc. set up yet. */ - dvdomatic_setup_i18n (); + dcpomatic_setup_i18n (); /* Set things up, including scalers / filters etc. which will now be internationalised correctly. */ - dvdomatic_setup (); + dcpomatic_setup (); /* Force the configuration to be re-loaded correctly next time it is needed. */ Config::drop (); - Frame* f = new Frame (_("DVD-o-matic Batch Converter")); + Frame* f = new Frame (_("DCP-o-matic Batch Converter")); SetTopWindow (f); f->Maximize (); f->Show (); diff --git a/src/tools/makedcp.cc b/src/tools/dcpomatic_cli.cc index 1cd5145ed..ee9e2cdc0 100644 --- a/src/tools/makedcp.cc +++ b/src/tools/dcpomatic_cli.cc @@ -21,12 +21,10 @@ #include <iomanip> #include <getopt.h> #include <libdcp/version.h> -#include "format.h" #include "film.h" #include "filter.h" #include "transcode_job.h" #include "job_manager.h" -#include "ab_transcode_job.h" #include "util.h" #include "scaler.h" #include "version.h" @@ -46,9 +44,9 @@ static void help (string n) { cerr << "Syntax: " << n << " [OPTION] <FILM>\n" - << " -v, --version show DVD-o-matic version\n" + << " -v, --version show DCP-o-matic version\n" << " -h, --help show this help\n" - << " -d, --deps list DVD-o-matic dependency details\n" + << " -d, --deps list DCP-o-matic dependency details and quit\n" << " -f, --flags show flags passed to C++ compiler on build\n" << " -n, --no-progress do not print progress to stdout\n" << " -r, --no-remote do not use any remote servers\n" @@ -85,7 +83,7 @@ main (int argc, char* argv[]) switch (c) { case 'v': - cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n"; + cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n"; exit (EXIT_SUCCESS); case 'h': help (argv[0]); @@ -94,7 +92,7 @@ main (int argc, char* argv[]) cout << dependency_version_summary () << "\n"; exit (EXIT_SUCCESS); case 'f': - cout << dvdomatic_cxx_flags << "\n"; + cout << dcpomatic_cxx_flags << "\n"; exit (EXIT_SUCCESS); case 'n': progress = false; @@ -115,13 +113,13 @@ main (int argc, char* argv[]) film_dir = argv[optind]; - dvdomatic_setup (); + dcpomatic_setup (); if (no_remote) { Config::instance()->set_servers (vector<ServerDescription*> ()); } - cout << "DVD-o-matic " << dvdomatic_version << " git " << dvdomatic_git_commit; + cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit; char buf[256]; if (gethostname (buf, 256) == 0) { cout << " on " << buf; @@ -130,7 +128,8 @@ main (int argc, char* argv[]) shared_ptr<Film> film; try { - film.reset (new Film (film_dir, true)); + film.reset (new Film (film_dir)); + film->read_metadata (); } catch (std::exception& e) { cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n"; exit (EXIT_FAILURE); @@ -138,14 +137,10 @@ main (int argc, char* argv[]) film->log()->set_level ((Log::Level) log_level); - cout << "\nMaking "; - if (film->dcp_ab()) { - cout << "A/B "; - } - cout << "DCP for " << film->name() << "\n"; - cout << "Content: " << film->content() << "\n"; - pair<string, string> const f = Filter::ffmpeg_strings (film->filters ()); - cout << "Filters: " << f.first << " " << f.second << "\n"; + cout << "\nMaking DCP for " << film->name() << "\n"; +// cout << "Content: " << film->content() << "\n"; +// pair<string, string> const f = Filter::ffmpeg_strings (film->filters ()); +// cout << "Filters: " << f.first << " " << f.second << "\n"; film->make_dcp (); @@ -154,7 +149,7 @@ main (int argc, char* argv[]) bool error = false; while (!should_stop) { - dvdomatic_sleep (5); + dcpomatic_sleep (5); list<shared_ptr<Job> > jobs = JobManager::instance()->get (); diff --git a/src/tools/servomatic_gui.cc b/src/tools/dcpomatic_server.cc index 000c2019f..d3a353154 100644 --- a/src/tools/servomatic_gui.cc +++ b/src/tools/dcpomatic_server.cc @@ -61,7 +61,7 @@ class StatusDialog : public wxDialog { public: StatusDialog () - : wxDialog (0, wxID_ANY, _("DVD-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : wxDialog (0, wxID_ANY, _("DCP-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , _timer (this, ID_timer) { _sizer = new wxFlexGridSizer (1, 6, 6); @@ -105,7 +105,7 @@ public: #endif #ifndef __WXOSX__ /* XXX: fix this for OS X */ - SetIcon (icon, std_to_wx ("DVD-o-matic encode server")); + SetIcon (icon, std_to_wx ("DCP-o-matic encode server")); #endif Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status)); @@ -150,7 +150,7 @@ private: return false; } - dvdomatic_setup (); + dcpomatic_setup (); _icon = new TaskBarIcon; _thread = new thread (bind (&App::main_thread, this)); diff --git a/src/tools/servomatic_cli.cc b/src/tools/dcpomatic_server_cli.cc index 6626d45b9..76d085034 100644 --- a/src/tools/servomatic_cli.cc +++ b/src/tools/dcpomatic_server_cli.cc @@ -51,7 +51,7 @@ static void help (string n) { cerr << "Syntax: " << n << " [OPTION]\n" - << " -v, --version show DVD-o-matic version\n" + << " -v, --version show DCP-o-matic version\n" << " -h, --help show this help\n" << " -t, --threads number of parallel encoding threads to use\n"; } @@ -78,7 +78,7 @@ main (int argc, char* argv[]) switch (c) { case 'v': - cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n"; + cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n"; exit (EXIT_SUCCESS); case 'h': help (argv[0]); diff --git a/src/tools/po/es_ES.po b/src/tools/po/es_ES.po index bbae550d7..43c9b12f1 100644 --- a/src/tools/po/es_ES.po +++ b/src/tools/po/es_ES.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVDOMATIC\n" +"Project-Id-Version: DCPOMATIC\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-03-23 21:08-0500\n" @@ -90,16 +90,16 @@ msgstr "No se pudo cargar la película %s (%s)" msgid "Could not open film at %s (%s)" msgstr "No se pudo cargar la película en %s (%s)" -#: src/tools/dvdomatic.cc:300 src/tools/dvdomatic.cc:431 -#: src/tools/dvdomatic.cc:524 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +#: src/tools/dcpomatic.cc:287 src/tools/dcpomatic.cc:410 +#: src/tools/dcpomatic.cc:531 +msgid "DCP-o-matic" +msgstr "DCP-o-matic" -#: src/tools/dvdomatic.cc:79 +#: src/tools/dcpomatic.cc:75 msgid "Film changed" msgstr "Película cambiada" -#: src/tools/dvdomatic.cc:437 +#: src/tools/dvdomatic.cc:425 msgid "Free, open-source DCP generation from almost anything." msgstr "" "Generación de DCP a partir de casi cualquier fuente, libre y de código " diff --git a/src/tools/po/fr_FR.po b/src/tools/po/fr_FR.po index 8a28a0e01..b9e39b392 100644 --- a/src/tools/po/fr_FR.po +++ b/src/tools/po/fr_FR.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVD-o-matic FRENCH\n" +"Project-Id-Version: DCP-o-matic FRENCH\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-05-10 14:09+0100\n" diff --git a/src/tools/po/sv_SE.po b/src/tools/po/sv_SE.po index 662b74bb3..157b1fb19 100644 --- a/src/tools/po/sv_SE.po +++ b/src/tools/po/sv_SE.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVD-o-matic\n" +"Project-Id-Version: DCP-o-matic\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-04-09 10:12+0100\n" @@ -89,10 +89,10 @@ msgstr "Kunde inte öppna filmen %1 (%2)" msgid "Could not open film at %s (%s)" msgstr "Kunde inte öppna filmen vid %s (%s)" -#: src/tools/dvdomatic.cc:300 src/tools/dvdomatic.cc:431 -#: src/tools/dvdomatic.cc:524 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +#: src/tools/dvdomatic.cc:288 src/tools/dvdomatic.cc:419 +#: src/tools/dvdomatic.cc:506 +msgid "DCP-o-matic" +msgstr "DCP-o-matic" #: src/tools/dvdomatic.cc:79 msgid "Film changed" @@ -114,7 +114,7 @@ msgstr "&Visa DCP" #: src/tools/dvdomatic.cc:78 #, fuzzy, c-format msgid "Save changes to film \"%s\" before closing?" -msgstr "Spara ändringarna till filmen \"%1\" före avslut?" +msgstr "Spara ändringarna till filmen \"%s\" före avslut?" #: src/tools/dvdomatic.cc:340 msgid "Select film to open" diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc index 5e1cf49b4..88974eed7 100644 --- a/src/tools/servomatictest.cc +++ b/src/tools/servomatictest.cc @@ -28,13 +28,12 @@ #include "scaler.h" #include "server.h" #include "dcp_video_frame.h" -#include "options.h" #include "decoder.h" #include "exceptions.h" #include "scaler.h" #include "log.h" -#include "decoder_factory.h" #include "video_decoder.h" +#include "player.h" using std::cout; using std::cerr; @@ -146,22 +145,20 @@ main (int argc, char* argv[]) exit (EXIT_FAILURE); } - dvdomatic_setup (); + dcpomatic_setup (); server = new ServerDescription (server_host, 1); - shared_ptr<Film> film (new Film (film_dir, true)); + shared_ptr<Film> film (new Film (film_dir)); + film->read_metadata (); - DecodeOptions opt; - opt.decode_audio = false; - opt.decode_subtitles = true; - opt.video_sync = true; + shared_ptr<Player> player = film->player (); + player->disable_audio (); - Decoders decoders = decoder_factory (film, opt); try { - decoders.video->Video.connect (boost::bind (process_video, _1, _2, _3)); + player->Video.connect (boost::bind (process_video, _1, _2, _3)); bool done = false; while (!done) { - done = decoders.video->pass (); + done = player->pass (); } } catch (std::exception& e) { cerr << "Error: " << e.what() << "\n"; diff --git a/src/tools/wscript b/src/tools/wscript index 20a92cad2..c7ab44604 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -4,31 +4,31 @@ from waflib import Logs import i18n def build(bld): - for t in ['makedcp', 'servomatic_cli', 'servomatictest']: + for t in ['dcpomatic_cli', 'dcpomatic_server_cli']: obj = bld(features = 'cxx cxxprogram') - obj.uselib = 'BOOST_THREAD OPENJPEG DCP AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS' + obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS' obj.includes = ['..'] - obj.use = ['libdvdomatic'] + obj.use = ['libdcpomatic'] obj.source = '%s.cc' % t obj.target = t if not bld.env.DISABLE_GUI: - for t in ['dvdomatic', 'dvdomatic_batch', 'servomatic_gui']: + for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']: obj = bld(features = 'cxx cxxprogram') obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS' if bld.env.STATIC: obj.uselib += ' GTK' obj.includes = ['..'] - obj.use = ['libdvdomatic', 'libdvdomatic-wx'] + obj.use = ['libdcpomatic', 'libdcpomatic-wx'] obj.source = '%s.cc' % t if bld.env.TARGET_WINDOWS: - obj.source += ' ../../platform/windows/dvdomatic.rc' + obj.source += ' ../../platform/windows/dcpomatic.rc' obj.target = t - i18n.po_to_mo(os.path.join('src', 'tools'), 'dvdomatic', bld) + i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic', bld) def pot(bld): - i18n.pot(os.path.join('src', 'tools'), 'dvdomatic.cc', 'dvdomatic') + i18n.pot(os.path.join('src', 'tools'), 'dcpomatic.cc', 'dcpomatic') def pot_merge(bld): - i18n.pot_merge(os.path.join('src', 'tools'), 'dvdomatic') + i18n.pot_merge(os.path.join('src', 'tools'), 'dcpomatic') diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc index cbac3ed43..ca19326a1 100644 --- a/src/wx/about_dialog.cc +++ b/src/wx/about_dialog.cc @@ -27,7 +27,7 @@ using std::vector; AboutDialog::AboutDialog (wxWindow* parent) - : wxDialog (parent, wxID_ANY, _("About DVD-o-matic")) + : wxDialog (parent, wxID_ANY, _("About DCP-o-matic")) { wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); @@ -38,15 +38,15 @@ AboutDialog::AboutDialog (wxWindow* parent) wxFont version_font (*wxNORMAL_FONT); version_font.SetWeight (wxFONTWEIGHT_BOLD); - wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DVD-o-matic")); + wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DCP-o-matic")); t->SetFont (title_font); sizer->Add (t, wxSizerFlags().Centre().Border()); wxString s; - if (strcmp (dvdomatic_git_commit, "release") == 0) { - t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dvdomatic_version))); + if (strcmp (dcpomatic_git_commit, "release") == 0) { + t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dcpomatic_version))); } else { - t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dvdomatic_version, dvdomatic_git_commit))); + t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dcpomatic_version, dcpomatic_git_commit))); } t->SetFont (version_font); sizer->Add (t, wxSizerFlags().Centre().Border()); @@ -62,8 +62,8 @@ AboutDialog::AboutDialog (wxWindow* parent) wxHyperlinkCtrl* h = new wxHyperlinkCtrl ( this, wxID_ANY, - wxT ("www.carlh.net/software/dvdomatic"), - wxT ("http://www.carlh.net/software/dvdomatic") + wxT ("dcpomatic.com"), + wxT ("http://dcpomatic.com") ); sizer->Add (h, wxSizerFlags().Centre().Border()); @@ -117,10 +117,6 @@ AboutDialog::AboutDialog (wxWindow* parent) sizer->Add (_notebook, wxSizerFlags().Centre().Border().Expand()); -#if 0 - info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic")); -#endif - SetSizerAndFit (sizer); } diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc index 21e4e5940..22e09cc7a 100644 --- a/src/wx/audio_dialog.cc +++ b/src/wx/audio_dialog.cc @@ -18,10 +18,10 @@ */ #include <boost/filesystem.hpp> +#include "lib/audio_analysis.h" +#include "lib/film.h" #include "audio_dialog.h" #include "audio_plot.h" -#include "audio_analysis.h" -#include "film.h" #include "wx_util.h" using boost::shared_ptr; @@ -43,7 +43,6 @@ AudioDialog::AudioDialog (wxWindow* parent) wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels")); side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16); } - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i))); @@ -84,62 +83,39 @@ AudioDialog::AudioDialog (wxWindow* parent) } void -AudioDialog::set_film (boost::shared_ptr<Film> f) +AudioDialog::set_content (shared_ptr<AudioContent> c) { - _film_changed_connection.disconnect (); - _film_audio_analysis_succeeded_connection.disconnect (); + _content_changed_connection.disconnect (); - _film = f; + _content = c; try_to_load_analysis (); - setup_channels (); - _plot->set_gain (_film->audio_gain ()); + _plot->set_gain (_content->audio_gain ()); - _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1)); - _film_audio_analysis_succeeded_connection = _film->AudioAnalysisSucceeded.connect (bind (&AudioDialog::try_to_load_analysis, this)); + _content_changed_connection = _content->Changed.connect (bind (&AudioDialog::content_changed, this, _2)); - SetTitle (wxString::Format (_("DVD-o-matic audio - %s"), std_to_wx(_film->name()).data())); + SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->file().filename().string()).data())); } void -AudioDialog::setup_channels () -{ - if (!_film->audio_stream()) { - return; - } - - AudioMapping m (_film); - - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - if (m.dcp_to_source(static_cast<libdcp::Channel>(i))) { - _channel_checkbox[i]->Show (); - } else { - _channel_checkbox[i]->Hide (); - } - } -} - -void AudioDialog::try_to_load_analysis () { shared_ptr<AudioAnalysis> a; - if (boost::filesystem::exists (_film->audio_analysis_path())) { - a.reset (new AudioAnalysis (_film->audio_analysis_path ())); + if (boost::filesystem::exists (_content->audio_analysis_path())) { + a.reset (new AudioAnalysis (_content->audio_analysis_path ())); } else { if (IsShown ()) { - _film->analyse_audio (); + _content->analyse_audio (bind (&AudioDialog::try_to_load_analysis, this)); } } _plot->set_analysis (a); - AudioMapping m (_film); - optional<libdcp::Channel> c = m.source_to_dcp (0); - if (c) { - _channel_checkbox[c.get()]->SetValue (true); - _plot->set_channel_visible (0, true); + if (_channel_checkbox[0]) { + _channel_checkbox[0]->SetValue (true); } + _plot->set_channel_visible (0, true); for (int i = 0; i < AudioPoint::COUNT; ++i) { _type_checkbox[i]->SetValue (true); @@ -157,27 +133,14 @@ AudioDialog::channel_clicked (wxCommandEvent& ev) assert (c < MAX_AUDIO_CHANNELS); - AudioMapping m (_film); - optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c)); - if (s) { - _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ()); - } + _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ()); } void -AudioDialog::film_changed (Film::Property p) +AudioDialog::content_changed (int p) { - switch (p) { - case Film::AUDIO_GAIN: - _plot->set_gain (_film->audio_gain ()); - break; - case Film::CONTENT_AUDIO_STREAM: - case Film::EXTERNAL_AUDIO: - case Film::USE_CONTENT_AUDIO: - setup_channels (); - break; - default: - break; + if (p == AudioContentProperty::AUDIO_GAIN) { + _plot->set_gain (_content->audio_gain ()); } } diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h index 514faeea0..c69baf282 100644 --- a/src/wx/audio_dialog.h +++ b/src/wx/audio_dialog.h @@ -31,21 +31,19 @@ class AudioDialog : public wxDialog public: AudioDialog (wxWindow *); - void set_film (boost::shared_ptr<Film>); + void set_content (boost::shared_ptr<AudioContent>); private: - void film_changed (Film::Property); + void content_changed (int); void channel_clicked (wxCommandEvent &); void type_clicked (wxCommandEvent &); void smoothing_changed (wxScrollEvent &); void try_to_load_analysis (); - void setup_channels (); - boost::shared_ptr<Film> _film; + boost::shared_ptr<AudioContent> _content; AudioPlot* _plot; wxCheckBox* _channel_checkbox[MAX_AUDIO_CHANNELS]; wxCheckBox* _type_checkbox[AudioPoint::COUNT]; wxSlider* _smoothing; - boost::signals2::scoped_connection _film_changed_connection; - boost::signals2::scoped_connection _film_audio_analysis_succeeded_connection; + boost::signals2::scoped_connection _content_changed_connection; }; diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc new file mode 100644 index 000000000..3ea0cdd1d --- /dev/null +++ b/src/wx/audio_mapping_view.cc @@ -0,0 +1,174 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <wx/wx.h> +#include <wx/renderer.h> +#include <wx/grid.h> +#include <libdcp/types.h> +#include "lib/audio_mapping.h" +#include "audio_mapping_view.h" +#include "wx_util.h" + +using std::cout; +using std::list; +using boost::shared_ptr; + +/* This could go away with wxWidgets 2.9, which has an API call + to find these values. +*/ + +#ifdef __WXMSW__ +#define CHECKBOX_WIDTH 16 +#define CHECKBOX_HEIGHT 16 +#endif + +#ifdef __WXGTK__ +#define CHECKBOX_WIDTH 20 +#define CHECKBOX_HEIGHT 20 +#endif + + +class NoSelectionStringRenderer : public wxGridCellStringRenderer +{ +public: + void Draw (wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool) + { + wxGridCellStringRenderer::Draw (grid, attr, dc, rect, row, col, false); + } +}; + +class CheckBoxRenderer : public wxGridCellRenderer +{ +public: + + void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool) + { +#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9 + dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxPENSTYLE_SOLID)); +#else + dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxSOLID)); +#endif + dc.DrawRectangle (rect); + + wxRendererNative::Get().DrawCheckBox ( + &grid, + dc, rect, + grid.GetCellValue (row, col) == wxT("1") ? static_cast<int>(wxCONTROL_CHECKED) : 0 + ); + } + + wxSize GetBestSize (wxGrid &, wxGridCellAttr &, wxDC &, int, int) + { + return wxSize (CHECKBOX_WIDTH + 4, CHECKBOX_HEIGHT + 4); + } + + wxGridCellRenderer* Clone () const + { + return new CheckBoxRenderer; + } +}; + + +AudioMappingView::AudioMappingView (wxWindow* parent) + : wxPanel (parent, wxID_ANY) +{ + _grid = new wxGrid (this, wxID_ANY); + + _grid->CreateGrid (0, 7); +#if wxMINOR_VERSION == 9 + _grid->HideRowLabels (); +#else + _grid->SetRowLabelSize (0); +#endif + _grid->DisableDragRowSize (); + _grid->DisableDragColSize (); + _grid->EnableEditing (false); + _grid->SetCellHighlightPenWidth (0); + _grid->SetDefaultRenderer (new NoSelectionStringRenderer); + + _grid->SetColLabelValue (0, _("Content channel")); + _grid->SetColLabelValue (1, _("L")); + _grid->SetColLabelValue (2, _("R")); + _grid->SetColLabelValue (3, _("C")); + _grid->SetColLabelValue (4, _("Lfe")); + _grid->SetColLabelValue (5, _("Ls")); + _grid->SetColLabelValue (6, _("Rs")); + + _grid->AutoSize (); + + _sizer = new wxBoxSizer (wxVERTICAL); + _sizer->Add (_grid, 1, wxEXPAND | wxALL); + SetSizerAndFit (_sizer); + + Connect (wxID_ANY, wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler (AudioMappingView::left_click), 0, this); +} + +void +AudioMappingView::left_click (wxGridEvent& ev) +{ + if (ev.GetCol() == 0) { + return; + } + + if (_grid->GetCellValue (ev.GetRow(), ev.GetCol()) == wxT("1")) { + _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("0")); + } else { + _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("1")); + } + + AudioMapping mapping; + for (int i = 0; i < _grid->GetNumberRows(); ++i) { + for (int j = 1; j < _grid->GetNumberCols(); ++j) { + if (_grid->GetCellValue (i, j) == wxT ("1")) { + mapping.add (i, static_cast<libdcp::Channel> (j - 1)); + } + } + } + + Changed (mapping); +} + +void +AudioMappingView::set (AudioMapping map) +{ + if (_grid->GetNumberRows ()) { + _grid->DeleteRows (0, _grid->GetNumberRows ()); + } + + list<int> content_channels = map.content_channels (); + _grid->InsertRows (0, content_channels.size ()); + + for (size_t r = 0; r < content_channels.size(); ++r) { + for (int c = 1; c < 7; ++c) { + _grid->SetCellRenderer (r, c, new CheckBoxRenderer); + } + } + + int n = 0; + for (list<int>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) { + _grid->SetCellValue (n, 0, wxString::Format (wxT("%d"), *i + 1)); + + list<libdcp::Channel> const d = map.content_to_dcp (*i); + for (list<libdcp::Channel>::const_iterator j = d.begin(); j != d.end(); ++j) { + _grid->SetCellValue (n, static_cast<int> (*j) + 1, wxT("1")); + } + ++n; + } +} + diff --git a/src/lib/trimmer.h b/src/wx/audio_mapping_view.h index 45b3f149a..e0709c8b7 100644 --- a/src/lib/trimmer.h +++ b/src/wx/audio_mapping_view.h @@ -17,23 +17,23 @@ */ -#include "processor.h" +#include <boost/signals2.hpp> +#include <wx/wx.h> +#include <wx/grid.h> +#include "lib/audio_mapping.h" -class Trimmer : public AudioVideoProcessor +class AudioMappingView : public wxPanel { public: - Trimmer (boost::shared_ptr<Log>, int, int, int, int, float, int); + AudioMappingView (wxWindow *); - void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s); - void process_audio (boost::shared_ptr<const AudioBuffers>); + void set (AudioMapping); + + boost::signals2::signal<void (AudioMapping)> Changed; private: - friend class trimmer_test; - - int _video_start; - int _video_end; - int _video_in; - int64_t _audio_start; - int64_t _audio_end; - int64_t _audio_in; + void left_click (wxGridEvent &); + + wxGrid* _grid; + wxSizer* _sizer; }; diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc index 3fec1d3fe..e2e2cdf76 100644 --- a/src/wx/audio_plot.cc +++ b/src/wx/audio_plot.cc @@ -21,7 +21,6 @@ #include <boost/bind.hpp> #include <wx/graphics.h> #include "audio_plot.h" -#include "lib/decoder_factory.h" #include "lib/audio_decoder.h" #include "lib/audio_analysis.h" #include "wx/wx_util.h" @@ -38,7 +37,7 @@ int const AudioPlot::_minimum = -70; int const AudioPlot::max_smoothing = 128; AudioPlot::AudioPlot (wxWindow* parent) - : wxPanel (parent) + : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) , _gain (0) , _smoothing (max_smoothing / 2) { @@ -192,6 +191,10 @@ AudioPlot::y_for_linear (float p) const void AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const { + if (_analysis->points (channel) == 0) { + return; + } + path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::PEAK])); float peak = 0; @@ -212,6 +215,10 @@ AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const void AudioPlot::plot_rms (wxGraphicsPath& path, int channel) const { + if (_analysis->points (channel) == 0) { + return; + } + path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::RMS])); list<float> smoothing; diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index e622c331b..e66be174d 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -28,7 +28,7 @@ #include <wx/notebook.h> #include "lib/config.h" #include "lib/server.h" -#include "lib/format.h" +#include "lib/ratio.h" #include "lib/scaler.h" #include "lib/filter.h" #include "lib/dcp_content_type.h" @@ -57,8 +57,6 @@ ConfigDialog::ConfigDialog (wxWindow* parent) _notebook->AddPage (_metadata_panel, _("Metadata"), false); make_tms_panel (); _notebook->AddPage (_tms_panel, _("TMS"), false); - make_ab_panel (); - _notebook->AddPage (_ab_panel, _("A/B mode"), false); wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); overall_sizer->Add (s, 1, wxEXPAND | wxALL, 6); @@ -80,7 +78,7 @@ ConfigDialog::make_misc_panel () wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); _misc_panel->SetSizer (s); - wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); s->Add (table, 1, wxALL | wxEXPAND, 8); @@ -108,8 +106,13 @@ ConfigDialog::make_misc_panel () table->Add (_num_local_encoding_threads, 1); table->AddSpacer (0); + add_label_to_sizer (table, _misc_panel, _("Default duration of still images"), true); + _default_still_length = new wxSpinCtrl (_misc_panel); + table->Add (_default_still_length, 1, wxEXPAND); + add_label_to_sizer (table, _misc_panel, _("s"), false); + add_label_to_sizer (table, _misc_panel, _("Default directory for new films"), true); -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER +#ifdef DCPOMATIC_USE_OWN_DIR_PICKER _default_directory = new DirPickerCtrl (_misc_panel); #else _default_directory = new wxDirPickerCtrl (_misc_panel, wxDD_DIR_MUST_EXIST); @@ -122,9 +125,9 @@ ConfigDialog::make_misc_panel () table->Add (_default_dci_metadata_button); table->AddSpacer (1); - add_label_to_sizer (table, _misc_panel, _("Default format"), true); - _default_format = new wxChoice (_misc_panel, wxID_ANY); - table->Add (_default_format); + add_label_to_sizer (table, _misc_panel, _("Default container"), true); + _default_container = new wxChoice (_misc_panel, wxID_ANY); + table->Add (_default_container); table->AddSpacer (1); add_label_to_sizer (table, _misc_panel, _("Default content type"), true); @@ -157,22 +160,26 @@ ConfigDialog::make_misc_panel () _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ()); _num_local_encoding_threads->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::num_local_encoding_threads_changed), 0, this); + _default_still_length->SetRange (1, 3600); + _default_still_length->SetValue (config->default_still_length ()); + _default_still_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::default_still_length_changed), 0, this); + _default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())))); _default_directory->Connect (wxID_ANY, wxEVT_COMMAND_DIRPICKER_CHANGED, wxCommandEventHandler (ConfigDialog::default_directory_changed), 0, this); _default_dci_metadata_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_default_dci_metadata_clicked), 0, this); - vector<Format const *> fmt = Format::all (); + vector<Ratio const *> ratio = Ratio::all (); int n = 0; - for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) { - _default_format->Append (std_to_wx ((*i)->name ())); - if (*i == config->default_format ()) { - _default_format->SetSelection (n); + for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) { + _default_container->Append (std_to_wx ((*i)->nickname ())); + if (*i == config->default_container ()) { + _default_container->SetSelection (n); } ++n; } - _default_format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_format_changed), 0, this); + _default_container->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_container_changed), 0, this); vector<DCPContentType const *> const ct = DCPContentType::all (); n = 0; @@ -194,7 +201,7 @@ ConfigDialog::make_tms_panel () wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); _tms_panel->SetSizer (s); - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); s->Add (table, 1, wxALL | wxEXPAND, 8); @@ -233,7 +240,7 @@ ConfigDialog::make_metadata_panel () wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); _metadata_panel->SetSizer (s); - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); s->Add (table, 1, wxALL | wxEXPAND, 8); @@ -254,55 +261,13 @@ ConfigDialog::make_metadata_panel () } void -ConfigDialog::make_ab_panel () -{ - _ab_panel = new wxPanel (_notebook); - wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); - _ab_panel->SetSizer (s); - - wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); - table->AddGrowableCol (1, 1); - s->Add (table, 1, wxALL, 8); - - add_label_to_sizer (table, _ab_panel, _("Reference scaler"), true); - _reference_scaler = new wxChoice (_ab_panel, wxID_ANY); - vector<Scaler const *> const sc = Scaler::all (); - for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { - _reference_scaler->Append (std_to_wx ((*i)->name ())); - } - - table->Add (_reference_scaler, 1, wxEXPAND); - table->AddSpacer (0); - - { - add_label_to_sizer (table, _ab_panel, _("Reference filters"), true); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _reference_filters = new wxStaticText (_ab_panel, wxID_ANY, wxT ("")); - s->Add (_reference_filters, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 6); - _reference_filters_button = new wxButton (_ab_panel, wxID_ANY, _("Edit...")); - s->Add (_reference_filters_button, 0); - table->Add (s, 1, wxEXPAND); - table->AddSpacer (0); - } - - Config* config = Config::instance (); - - _reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ())); - _reference_scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::reference_scaler_changed), 0, this); - - pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ()); - _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second)); - _reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this); -} - -void ConfigDialog::make_servers_panel () { _servers_panel = new wxPanel (_notebook); wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); _servers_panel->SetSizer (s); - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (0, 1); s->Add (table, 1, wxALL | wxEXPAND, 8); @@ -475,32 +440,6 @@ ConfigDialog::server_selection_changed (wxListEvent &) } void -ConfigDialog::reference_scaler_changed (wxCommandEvent &) -{ - int const n = _reference_scaler->GetSelection (); - if (n >= 0) { - Config::instance()->set_reference_scaler (Scaler::from_index (n)); - } -} - -void -ConfigDialog::edit_reference_filters_clicked (wxCommandEvent &) -{ - FilterDialog* d = new FilterDialog (this, Config::instance()->reference_filters ()); - d->ActiveChanged.connect (boost::bind (&ConfigDialog::reference_filters_changed, this, _1)); - d->ShowModal (); - d->Destroy (); -} - -void -ConfigDialog::reference_filters_changed (vector<Filter const *> f) -{ - Config::instance()->set_reference_filters (f); - pair<string, string> p = Filter::ffmpeg_strings (Config::instance()->reference_filters ()); - _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second)); -} - -void ConfigDialog::edit_default_dci_metadata_clicked (wxCommandEvent &) { DCIMetadataDialog* d = new DCIMetadataDialog (this, Config::instance()->default_dci_metadata ()); @@ -527,10 +466,16 @@ ConfigDialog::setup_language_sensitivity () } void -ConfigDialog::default_format_changed (wxCommandEvent &) +ConfigDialog::default_still_length_changed (wxCommandEvent &) +{ + Config::instance()->set_default_still_length (_default_still_length->GetValue ()); +} + +void +ConfigDialog::default_container_changed (wxCommandEvent &) { - vector<Format const *> fmt = Format::all (); - Config::instance()->set_default_format (fmt[_default_format->GetSelection()]); + vector<Ratio const *> ratio = Ratio::all (); + Config::instance()->set_default_container (ratio[_default_container->GetSelection()]); } void diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h index a97788942..459d64dd7 100644 --- a/src/wx/config_dialog.h +++ b/src/wx/config_dialog.h @@ -48,16 +48,14 @@ private: void tms_user_changed (wxCommandEvent &); void tms_password_changed (wxCommandEvent &); void num_local_encoding_threads_changed (wxCommandEvent &); + void default_still_length_changed (wxCommandEvent &); void default_directory_changed (wxCommandEvent &); void edit_default_dci_metadata_clicked (wxCommandEvent &); - void reference_scaler_changed (wxCommandEvent &); - void edit_reference_filters_clicked (wxCommandEvent &); - void reference_filters_changed (std::vector<Filter const *>); void add_server_clicked (wxCommandEvent &); void edit_server_clicked (wxCommandEvent &); void remove_server_clicked (wxCommandEvent &); void server_selection_changed (wxListEvent &); - void default_format_changed (wxCommandEvent &); + void default_container_changed (wxCommandEvent &); void default_dcp_content_type_changed (wxCommandEvent &); void issuer_changed (wxCommandEvent &); void creator_changed (wxCommandEvent &); @@ -68,33 +66,29 @@ private: void make_misc_panel (); void make_tms_panel (); void make_metadata_panel (); - void make_ab_panel (); void make_servers_panel (); wxNotebook* _notebook; wxPanel* _misc_panel; wxPanel* _tms_panel; - wxPanel* _ab_panel; wxPanel* _servers_panel; wxPanel* _metadata_panel; wxCheckBox* _set_language; wxChoice* _language; - wxChoice* _default_format; + wxChoice* _default_container; wxChoice* _default_dcp_content_type; wxTextCtrl* _tms_ip; wxTextCtrl* _tms_path; wxTextCtrl* _tms_user; wxTextCtrl* _tms_password; wxSpinCtrl* _num_local_encoding_threads; -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER + wxSpinCtrl* _default_still_length; +#ifdef DCPOMATIC_USE_OWN_DIR_PICKER DirPickerCtrl* _default_directory; #else wxDirPickerCtrl* _default_directory; #endif wxButton* _default_dci_metadata_button; - wxChoice* _reference_scaler; - wxStaticText* _reference_filters; - wxButton* _reference_filters_button; wxListCtrl* _servers; wxButton* _add_server; wxButton* _edit_server; diff --git a/src/wx/dci_metadata_dialog.cc b/src/wx/dci_metadata_dialog.cc index 9c124ed74..df3a24f62 100644 --- a/src/wx/dci_metadata_dialog.cc +++ b/src/wx/dci_metadata_dialog.cc @@ -27,7 +27,7 @@ using boost::shared_ptr; DCIMetadataDialog::DCIMetadataDialog (wxWindow* parent, DCIMetadata dm) : wxDialog (parent, wxID_ANY, _("DCI name"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"), true); diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index a54b412b3..2d8c752a1 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -25,19 +25,20 @@ #include <iomanip> #include <wx/wx.h> #include <wx/notebook.h> +#include <wx/listctrl.h> #include <boost/thread.hpp> #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> -#include "lib/format.h" #include "lib/film.h" #include "lib/transcode_job.h" #include "lib/exceptions.h" -#include "lib/ab_transcode_job.h" #include "lib/job_manager.h" #include "lib/filter.h" +#include "lib/ratio.h" #include "lib/config.h" -#include "lib/ffmpeg_decoder.h" -#include "lib/log.h" +#include "lib/imagemagick_content.h" +#include "lib/sndfile_content.h" +#include "lib/dcp_content_type.h" #include "filter_dialog.h" #include "wx_util.h" #include "film_editor.h" @@ -46,6 +47,10 @@ #include "dci_metadata_dialog.h" #include "scaler.h" #include "audio_dialog.h" +#include "imagemagick_content_dialog.h" +#include "timeline_dialog.h" +#include "audio_mapping_view.h" +#include "timecode.h" using std::string; using std::cout; @@ -55,60 +60,60 @@ using std::fixed; using std::setprecision; using std::list; using std::vector; +using std::max; using boost::shared_ptr; +using boost::weak_ptr; using boost::dynamic_pointer_cast; +using boost::lexical_cast; /** @param f Film to edit */ FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent) : wxPanel (parent) - , _film (f) , _generally_sensitive (true) , _audio_dialog (0) + , _timeline_dialog (0) { wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); - SetSizer (s); - _notebook = new wxNotebook (this, wxID_ANY); - s->Add (_notebook, 1); - make_film_panel (); - _notebook->AddPage (_film_panel, _("Film"), true); - make_video_panel (); - _notebook->AddPage (_video_panel, _("Video"), false); - make_audio_panel (); - _notebook->AddPage (_audio_panel, _("Audio"), false); - make_subtitle_panel (); - _notebook->AddPage (_subtitle_panel, _("Subtitles"), false); + _main_notebook = new wxNotebook (this, wxID_ANY); + s->Add (_main_notebook, 1); + + make_content_panel (); + _main_notebook->AddPage (_content_panel, _("Content"), true); + make_dcp_panel (); + _main_notebook->AddPage (_dcp_panel, _("DCP"), false); + + setup_ratios (); - set_film (_film); + set_film (f); connect_to_widgets (); JobManager::instance()->ActiveJobsChanged.connect ( bind (&FilmEditor::active_jobs_changed, this, _1) ); - setup_visibility (); - setup_formats (); + SetSizerAndFit (s); } void -FilmEditor::make_film_panel () +FilmEditor::make_dcp_panel () { - _film_panel = new wxPanel (_notebook); - _film_sizer = new wxBoxSizer (wxVERTICAL); - _film_panel->SetSizer (_film_sizer); + _dcp_panel = new wxPanel (_main_notebook); + _dcp_sizer = new wxBoxSizer (wxVERTICAL); + _dcp_panel->SetSizer (_dcp_sizer); - wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); - _film_sizer->Add (grid, 0, wxALL, 8); + wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8); int r = 0; - add_label_to_grid_bag_sizer (grid, _film_panel, _("Name"), true, wxGBPosition (r, 0)); - _name = new wxTextCtrl (_film_panel, wxID_ANY); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0)); + _name = new wxTextCtrl (_dcp_panel, wxID_ANY); grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT); ++r; - add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Name"), true, wxGBPosition (r, 0)); - _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, wxGBPosition (r, 0)); + _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT ("")); grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); ++r; @@ -116,94 +121,58 @@ FilmEditor::make_film_panel () #ifdef __WXOSX__ flags |= wxALIGN_RIGHT; #endif - - _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name")); + + _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name")); grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, flags); - _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details...")); + _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details...")); grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan); ++r; - add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), true, wxGBPosition (r, 0)); - _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*")); - grid->Add (_content, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND); - ++r; - - _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header")); - video_control (_trust_content_header); - grid->Add (_trust_content_header, wxGBPosition (r, 0), wxGBSpan(1, 2)); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0)); + _container = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND); ++r; - add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), true, wxGBPosition (r, 0)); - _dcp_content_type = new wxChoice (_film_panel, wxID_ANY); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, wxGBPosition (r, 0)); + _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY); grid->Add (_dcp_content_type, wxGBPosition (r, 1)); ++r; - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Frame Rate"), true, wxGBPosition (r, 0))); - _source_frame_rate = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - grid->Add (video_control (_source_frame_rate), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - ++r; - { - add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0)); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _dcp_frame_rate = new wxChoice (_film_panel, wxID_ANY); + _dcp_frame_rate = new wxChoice (_dcp_panel, wxID_ANY); s->Add (_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL); - _best_dcp_frame_rate = new wxButton (_film_panel, wxID_ANY, _("Use best")); - s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 6); + _best_dcp_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best")); + s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); grid->Add (s, wxGBPosition (r, 1)); } ++r; - _frame_rate_description = new wxStaticText (_film_panel, wxID_ANY, wxT ("\n \n "), wxDefaultPosition, wxDefaultSize); - grid->Add (video_control (_frame_rate_description), wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6); - wxFont font = _frame_rate_description->GetFont(); - font.SetStyle(wxFONTSTYLE_ITALIC); - font.SetPointSize(font.GetPointSize() - 1); - _frame_rate_description->SetFont(font); - ++r; - - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), true, wxGBPosition (r, 0))); - _length = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - ++r; - - { - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), true, wxGBPosition (r, 0))); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - video_control (add_label_to_sizer (s, _film_panel, _("Start"), true)); - _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_trim_start)); - video_control (add_label_to_sizer (s, _film_panel, _("End"), true)); - _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_trim_end)); - + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0)); + wxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY); + s->Add (_j2k_bandwidth, 1); + add_label_to_sizer (s, _dcp_panel, _("MBps"), false); grid->Add (s, wxGBPosition (r, 1)); } ++r; - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), true, wxGBPosition (r, 0))); - _trim_type = new wxChoice (_film_panel, wxID_ANY); - grid->Add (video_control (_trim_type), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), true, wxGBPosition (r, 0)); + _scaler = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); ++r; - _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B")); - video_control (_dcp_ab); - grid->Add (_dcp_ab, wxGBPosition (r, 0)); - ++r; + vector<Scaler const *> const sc = Scaler::all (); + for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { + _scaler->Append (std_to_wx ((*i)->name())); + } - /* STILL-only stuff */ - { - still_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Duration"), true, wxGBPosition (r, 0))); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _still_duration = new wxSpinCtrl (_film_panel); - still_control (_still_duration); - s->Add (_still_duration, 1, wxEXPAND); - /// TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time - still_control (add_label_to_sizer (s, _film_panel, _("s"), false)); - grid->Add (s, wxGBPosition (r, 1)); + vector<Ratio const *> const ratio = Ratio::all (); + for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) { + _container->Append (std_to_wx ((*i)->nickname ())); } - ++r; vector<DCPContentType const *> const ct = DCPContentType::all (); for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) { @@ -215,73 +184,64 @@ FilmEditor::make_film_panel () _dcp_frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i))); } - _trim_type->Append (_("encode all frames and play the subset")); - _trim_type->Append (_("encode only the subset")); + _j2k_bandwidth->SetRange (50, 250); } void FilmEditor::connect_to_widgets () { - _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this); - _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this); - _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this); - _format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_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); - _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this); - _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this); - _scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this); - _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this); - _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this); - _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this); - _pad_with_silence->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this); - _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this); - _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this); - _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this); - _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this); - _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this); - _trim_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::trim_type_changed), 0, this); - _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this); - _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this); - _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this); - _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this); - _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this); - _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this); - _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this); - _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this); + _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this); + _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this); + _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this); + _container->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::container_changed), 0, this); + _ratio->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ratio_changed), 0, this); + _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this); + _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this); + _content_add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this); + _content_remove->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this); + _content_timeline->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_timeline_clicked), 0, this); + _loop_content->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this); + _loop_count->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::loop_count_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); + _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this); + _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this); + _scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this); + _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this); + _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this); + _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this); +// _pad_with_silence->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this); +// _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this); + _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this); + _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this); + _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this); + _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this); + _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this); + _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this); _audio_gain_calculate_button->Connect ( wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this ); - _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this); - _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this); - _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this); - _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this); - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - _external_audio[i]->Connect ( - wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this - ); - } + _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this); + _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this); + _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this); + _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this); + _audio_mapping->Changed.connect (boost::bind (&FilmEditor::audio_mapping_changed, this, _1)); + _start->Changed.connect (boost::bind (&FilmEditor::start_changed, this)); + _length->Changed.connect (boost::bind (&FilmEditor::length_changed, this)); } void FilmEditor::make_video_panel () { - _video_panel = new wxPanel (_notebook); - _video_sizer = new wxBoxSizer (wxVERTICAL); - _video_panel->SetSizer (_video_sizer); + _video_panel = new wxPanel (_content_notebook); + wxBoxSizer* video_sizer = new wxBoxSizer (wxVERTICAL); + _video_panel->SetSizer (video_sizer); - wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); - _video_sizer->Add (grid, 0, wxALL, 8); + wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + video_sizer->Add (grid, 0, wxALL, 8); int r = 0; - add_label_to_grid_bag_sizer (grid, _video_panel, _("Format"), true, wxGBPosition (r, 0)); - _format = new wxChoice (_video_panel, wxID_ANY); - grid->Add (_format, wxGBPosition (r, 1)); - ++r; - add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), true, wxGBPosition (r, 0)); _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); grid->Add (_left_crop, wxGBPosition (r, 1)); @@ -302,6 +262,11 @@ FilmEditor::make_video_panel () grid->Add (_bottom_crop, wxGBPosition (r, 1)); ++r; + add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), true, wxGBPosition (r, 0)); + _ratio = new wxChoice (_video_panel, wxID_ANY); + grid->Add (_ratio, wxGBPosition (r, 1)); + ++r; + _scaling_description = new wxStaticText (_video_panel, wxID_ANY, wxT ("\n \n \n \n"), wxDefaultPosition, wxDefaultSize); grid->Add (_scaling_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6); wxFont font = _scaling_description->GetFont(); @@ -312,28 +277,16 @@ FilmEditor::make_video_panel () /* VIDEO-only stuff */ { - video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0))); + add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0)); wxSizer* s = new wxBoxSizer (wxHORIZONTAL); _filters = new wxStaticText (_video_panel, wxID_ANY, _("None")); - video_control (_filters); s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6); _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit...")); - video_control (_filters_button); s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL); grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); } ++r; - video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), true, wxGBPosition (r, 0))); - _scaler = new wxChoice (_video_panel, wxID_ANY); - grid->Add (video_control (_scaler), wxGBPosition (r, 1)); - ++r; - - vector<Scaler const *> const sc = Scaler::all (); - for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { - _scaler->Append (std_to_wx ((*i)->name())); - } - add_label_to_grid_bag_sizer (grid, _video_panel, _("Colour look-up table"), true, wxGBPosition (r, 0)); _colour_lut = new wxChoice (_video_panel, wxID_ANY); for (int i = 0; i < 2; ++i) { @@ -343,62 +296,117 @@ FilmEditor::make_video_panel () grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND); ++r; - { - add_label_to_grid_bag_sizer (grid, _video_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0)); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY); - s->Add (_j2k_bandwidth, 1); - add_label_to_sizer (s, _video_panel, _("MBps"), false); - grid->Add (s, wxGBPosition (r, 1)); - } - ++r; - _left_crop->SetRange (0, 1024); _top_crop->SetRange (0, 1024); _right_crop->SetRange (0, 1024); _bottom_crop->SetRange (0, 1024); - _still_duration->SetRange (1, 60 * 60); - _trim_start->SetRange (0, 24 * 60 * 60); - _trim_end->SetRange (0, 24 * 60 * 60); - _j2k_bandwidth->SetRange (50, 250); +} + +void +FilmEditor::make_content_panel () +{ + _content_panel = new wxPanel (_main_notebook); + _content_sizer = new wxBoxSizer (wxVERTICAL); + _content_panel->SetSizer (_content_sizer); + + { + wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); + + _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL); + s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6); + + _content->InsertColumn (0, wxT("")); + _content->SetColumnWidth (0, 512); + + wxBoxSizer* b = new wxBoxSizer (wxVERTICAL); + _content_add = new wxButton (_content_panel, wxID_ANY, _("Add...")); + b->Add (_content_add, 1, wxEXPAND | wxLEFT | wxRIGHT); + _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove")); + b->Add (_content_remove, 1, wxEXPAND | wxLEFT | wxRIGHT); + _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline...")); + b->Add (_content_timeline, 1, wxEXPAND | wxLEFT | wxRIGHT); + + s->Add (b, 0, wxALL, 4); + + _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6); + } + + wxBoxSizer* h = new wxBoxSizer (wxHORIZONTAL); + _loop_content = new wxCheckBox (_content_panel, wxID_ANY, _("Loop everything")); + h->Add (_loop_content, 0, wxALL, 6); + _loop_count = new wxSpinCtrl (_content_panel, wxID_ANY); + h->Add (_loop_count, 0, wxALL, 6); + add_label_to_sizer (h, _content_panel, _("times"), false); + _content_sizer->Add (h, 0, wxALL, 6); + + _content_notebook = new wxNotebook (_content_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_LEFT); + _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6); + + make_video_panel (); + _content_notebook->AddPage (_video_panel, _("Video"), false); + make_audio_panel (); + _content_notebook->AddPage (_audio_panel, _("Audio"), false); + make_subtitle_panel (); + _content_notebook->AddPage (_subtitle_panel, _("Subtitles"), false); + make_timing_panel (); + _content_notebook->AddPage (_timing_panel, _("Timing"), false); + + _loop_count->SetRange (2, 1024); } void FilmEditor::make_audio_panel () { - _audio_panel = new wxPanel (_notebook); - _audio_sizer = new wxBoxSizer (wxVERTICAL); - _audio_panel->SetSizer (_audio_sizer); + _audio_panel = new wxPanel (_content_notebook); + wxBoxSizer* audio_sizer = new wxBoxSizer (wxVERTICAL); + _audio_panel->SetSizer (audio_sizer); - wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); - _audio_sizer->Add (grid, 0, wxALL, 8); + wxFlexGridSizer* grid = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + audio_sizer->Add (grid, 0, wxALL, 8); _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio...")); grid->Add (_show_audio, 1); grid->AddSpacer (0); + grid->AddSpacer (0); + add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true); { - video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_gain = new wxSpinCtrl (_audio_panel); - s->Add (video_control (_audio_gain), 1); - video_control (add_label_to_sizer (s, _audio_panel, _("dB"), false)); - _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate...")); - video_control (_audio_gain_calculate_button); - s->Add (_audio_gain_calculate_button, 1, wxEXPAND); - grid->Add (s); + s->Add (_audio_gain, 1); + add_label_to_sizer (s, _audio_panel, _("dB"), false); + grid->Add (s, 1); } + + _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate...")); + grid->Add (_audio_gain_calculate_button); + add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), false); { - video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), true)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_delay = new wxSpinCtrl (_audio_panel); - s->Add (video_control (_audio_delay), 1); + s->Add (_audio_delay, 1); /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time - video_control (add_label_to_sizer (s, _audio_panel, _("ms"), false)); + add_label_to_sizer (s, _audio_panel, _("ms"), false); grid->Add (s); } + grid->AddSpacer (0); + + add_label_to_sizer (grid, _audio_panel, _("Audio Stream"), true); + _audio_stream = new wxChoice (_audio_panel, wxID_ANY); + grid->Add (_audio_stream, 1); + _audio_description = new wxStaticText (_audio_panel, wxID_ANY, wxT ("")); + grid->AddSpacer (0); + + grid->Add (_audio_description, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); + grid->AddSpacer (0); + grid->AddSpacer (0); + + _audio_mapping = new AudioMappingView (_audio_panel); + audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6); + +#if 0 { _pad_with_silence = new wxCheckBox (_audio_panel, wxID_ANY, _("Pad with silence to")); grid->Add (_pad_with_silence); @@ -419,139 +427,117 @@ FilmEditor::make_audio_panel () s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); grid->Add (s); } - - _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio")); - grid->Add (_use_external_audio); - grid->AddSpacer (0); - - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - add_label_to_sizer (grid, _audio_panel, std_to_wx (audio_channel_name (i)), true); - _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav")); - grid->Add (_external_audio[i], 1, wxEXPAND); - } +#endif _audio_gain->SetRange (-60, 60); _audio_delay->SetRange (-1000, 1000); - _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS); +// _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS); } void FilmEditor::make_subtitle_panel () { - _subtitle_panel = new wxPanel (_notebook); - _subtitle_sizer = new wxBoxSizer (wxVERTICAL); - _subtitle_panel->SetSizer (_subtitle_sizer); - wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); - _subtitle_sizer->Add (grid, 0, wxALL, 8); + _subtitle_panel = new wxPanel (_content_notebook); + wxBoxSizer* subtitle_sizer = new wxBoxSizer (wxVERTICAL); + _subtitle_panel->SetSizer (subtitle_sizer); + wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + subtitle_sizer->Add (grid, 0, wxALL, 8); _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles")); - video_control (_with_subtitles); grid->Add (_with_subtitles, 1); + grid->AddSpacer (0); - _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY); - grid->Add (video_control (_subtitle_stream)); - { - video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true)); + add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _subtitle_offset = new wxSpinCtrl (_subtitle_panel); s->Add (_subtitle_offset); - video_control (add_label_to_sizer (s, _subtitle_panel, _("pixels"), false)); + add_label_to_sizer (s, _subtitle_panel, _("pixels"), false); grid->Add (s); } { - video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true)); + add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _subtitle_scale = new wxSpinCtrl (_subtitle_panel); - s->Add (video_control (_subtitle_scale)); - video_control (add_label_to_sizer (s, _subtitle_panel, _("%"), false)); + s->Add (_subtitle_scale); + add_label_to_sizer (s, _subtitle_panel, _("%"), false); grid->Add (s); } + add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"), true); + _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY); + grid->Add (_subtitle_stream, 1, wxEXPAND | wxALL, 6); + grid->AddSpacer (0); + _subtitle_offset->SetRange (-1024, 1024); _subtitle_scale->SetRange (1, 1000); } +void +FilmEditor::make_timing_panel () +{ + _timing_panel = new wxPanel (_content_notebook); + wxBoxSizer* timing_sizer = new wxBoxSizer (wxVERTICAL); + _timing_panel->SetSizer (timing_sizer); + wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4); + timing_sizer->Add (grid, 0, wxALL, 8); + + add_label_to_sizer (grid, _timing_panel, _("Start time"), true); + _start = new Timecode (_timing_panel); + grid->Add (_start); + add_label_to_sizer (grid, _timing_panel, _("Length"), true); + _length = new Timecode (_timing_panel); + grid->Add (_length); +} + + /** Called when the left crop widget has been changed */ void FilmEditor::left_crop_changed (wxCommandEvent &) { - if (!_film) { + shared_ptr<VideoContent> c = selected_video_content (); + if (!c) { return; } - _film->set_left_crop (_left_crop->GetValue ()); + c->set_left_crop (_left_crop->GetValue ()); } /** Called when the right crop widget has been changed */ void FilmEditor::right_crop_changed (wxCommandEvent &) { - if (!_film) { + shared_ptr<VideoContent> c = selected_video_content (); + if (!c) { return; } - _film->set_right_crop (_right_crop->GetValue ()); + c->set_right_crop (_right_crop->GetValue ()); } /** Called when the top crop widget has been changed */ void FilmEditor::top_crop_changed (wxCommandEvent &) { - if (!_film) { + shared_ptr<VideoContent> c = selected_video_content (); + if (!c) { return; } - _film->set_top_crop (_top_crop->GetValue ()); + c->set_top_crop (_top_crop->GetValue ()); } /** Called when the bottom crop value has been changed */ void FilmEditor::bottom_crop_changed (wxCommandEvent &) { - if (!_film) { - return; - } - - _film->set_bottom_crop (_bottom_crop->GetValue ()); -} - -/** Called when the content filename has been changed */ -void -FilmEditor::content_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - try { - _film->set_content (wx_to_std (_content->GetPath ())); - } catch (std::exception& e) { - _content->SetPath (std_to_wx (_film->directory ())); - error_dialog (this, wxString::Format (_("Could not set content: %s"), std_to_wx (e.what()).data())); - } -} - -void -FilmEditor::trust_content_header_changed (wxCommandEvent &) -{ - if (!_film) { + shared_ptr<VideoContent> c = selected_video_content (); + if (!c) { 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 &) -{ - if (!_film) { - return; - } - - _film->set_dcp_ab (_dcp_ab->GetValue ()); + c->set_bottom_crop (_bottom_crop->GetValue ()); } /** Called when the name widget has been changed */ @@ -612,7 +598,7 @@ FilmEditor::dcp_frame_rate_changed (wxCommandEvent &) return; } - _film->set_dcp_frame_rate ( + _film->set_dcp_video_frame_rate ( boost::lexical_cast<int> ( wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ())) ) @@ -639,118 +625,30 @@ FilmEditor::film_changed (Film::Property p) case Film::NONE: break; case Film::CONTENT: - checked_set (_content, _film->content ()); - setup_visibility (); - setup_formats (); - setup_subtitle_control_sensitivity (); - setup_streams (); - setup_show_audio_sensitivity (); - setup_frame_rate_description (); - setup_minimum_audio_channels (); - break; - case Film::TRUST_CONTENT_HEADER: - checked_set (_trust_content_header, _film->trust_content_header ()); - break; - case Film::SUBTITLE_STREAMS: + setup_content (); setup_subtitle_control_sensitivity (); - setup_streams (); - break; - case Film::CONTENT_AUDIO_STREAMS: - setup_streams (); setup_show_audio_sensitivity (); - setup_frame_rate_description (); setup_minimum_audio_channels (); break; - case Film::FORMAT: - { - int n = 0; - vector<Format const *>::iterator i = _formats.begin (); - while (i != _formats.end() && *i != _film->format ()) { - ++i; - ++n; - } - if (i == _formats.end()) { - checked_set (_format, -1); - } else { - checked_set (_format, n); - } - setup_dcp_name (); - setup_scaling_description (); - break; - } - case Film::CROP: - checked_set (_left_crop, _film->crop().left); - checked_set (_right_crop, _film->crop().right); - checked_set (_top_crop, _film->crop().top); - checked_set (_bottom_crop, _film->crop().bottom); - setup_scaling_description (); + case Film::LOOP: + checked_set (_loop_content, _film->loop() > 1); + checked_set (_loop_count, _film->loop()); + setup_loop_sensitivity (); break; - case Film::FILTERS: - { - pair<string, string> p = Filter::ffmpeg_strings (_film->filters ()); - if (p.first.empty () && p.second.empty ()) { - _filters->SetLabel (_("None")); - } else { - string const b = p.first + " " + p.second; - _filters->SetLabel (std_to_wx (b)); - } - _film_sizer->Layout (); + case Film::CONTAINER: + setup_container (); break; - } case Film::NAME: checked_set (_name, _film->name()); setup_dcp_name (); break; - case Film::SOURCE_FRAME_RATE: - s << fixed << setprecision(2) << _film->source_frame_rate(); - _source_frame_rate->SetLabel (std_to_wx (s.str ())); - setup_frame_rate_description (); - break; - case Film::SIZE: - setup_scaling_description (); - break; - case Film::LENGTH: - if (_film->source_frame_rate() > 0 && _film->length()) { - s << _film->length().get() << " " - << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->length().get() / _film->source_frame_rate()); - } else if (_film->length()) { - s << _film->length().get() << " " - << wx_to_std (_("frames")); - } - _length->SetLabel (std_to_wx (s.str ())); - if (_film->length()) { - _trim_start->SetRange (0, _film->length().get()); - _trim_end->SetRange (0, _film->length().get()); - } - break; case Film::DCP_CONTENT_TYPE: checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ())); setup_dcp_name (); break; - case Film::DCP_AB: - checked_set (_dcp_ab, _film->dcp_ab ()); - break; case Film::SCALER: checked_set (_scaler, Scaler::as_index (_film->scaler ())); break; - case Film::TRIM_START: - checked_set (_trim_start, _film->trim_start()); - break; - case Film::TRIM_END: - checked_set (_trim_end, _film->trim_end()); - break; - case Film::TRIM_TYPE: - checked_set (_trim_type, _film->trim_type() == Film::CPL ? 0 : 1); - break; - case Film::AUDIO_GAIN: - checked_set (_audio_gain, _film->audio_gain ()); - break; - case Film::AUDIO_DELAY: - checked_set (_audio_delay, _film->audio_delay ()); - break; - case Film::STILL_DURATION: - checked_set (_still_duration, _film->still_duration ()); - break; case Film::WITH_SUBTITLES: checked_set (_with_subtitles, _film->with_subtitles ()); setup_subtitle_control_sensitivity (); @@ -775,109 +673,177 @@ FilmEditor::film_changed (Film::Property p) case Film::DCI_METADATA: setup_dcp_name (); break; - case Film::CONTENT_AUDIO_STREAM: - if (_film->content_audio_stream()) { - checked_set (_audio_stream, _film->content_audio_stream()->to_string()); - } - setup_dcp_name (); - setup_audio_details (); - setup_audio_control_sensitivity (); - setup_show_audio_sensitivity (); - setup_frame_rate_description (); - setup_minimum_audio_channels (); - break; - case Film::USE_CONTENT_AUDIO: - _film->log()->log (String::compose ("Film::USE_CONTENT_AUDIO changed; setting GUI using %1", _film->use_content_audio ())); - checked_set (_use_content_audio, _film->use_content_audio()); - checked_set (_use_external_audio, !_film->use_content_audio()); - setup_dcp_name (); - setup_audio_details (); - setup_audio_control_sensitivity (); - setup_show_audio_sensitivity (); - setup_frame_rate_description (); - setup_minimum_audio_channels (); - break; - case Film::SUBTITLE_STREAM: - if (_film->subtitle_stream()) { - checked_set (_subtitle_stream, _film->subtitle_stream()->to_string()); - } - break; - case Film::EXTERNAL_AUDIO: + case Film::DCP_VIDEO_FRAME_RATE: { - vector<string> a = _film->external_audio (); - for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) { - checked_set (_external_audio[i], a[i]); - } - setup_audio_details (); - setup_show_audio_sensitivity (); - setup_frame_rate_description (); - setup_minimum_audio_channels (); - break; - } - case Film::DCP_FRAME_RATE: + bool done = false; for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) { - if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) { - if (_dcp_frame_rate->GetSelection() != int(i)) { - _dcp_frame_rate->SetSelection (i); - break; - } + if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_video_frame_rate())) { + checked_set (_dcp_frame_rate, i); + done = true; + break; } } - if (_film->source_frame_rate()) { - _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->source_frame_rate ()) != _film->dcp_frame_rate ()); - } else { - _best_dcp_frame_rate->Disable (); + if (!done) { + checked_set (_dcp_frame_rate, -1); } - setup_frame_rate_description (); + _best_dcp_frame_rate->Enable (_film->best_dcp_video_frame_rate () != _film->dcp_video_frame_rate ()); + break; + } case Film::MINIMUM_AUDIO_CHANNELS: - checked_set (_minimum_audio_channels, _film->minimum_audio_channels ()); +// checked_set (_minimum_audio_channels, _film->minimum_audio_channels ()); setup_minimum_audio_channels (); break; } } void -FilmEditor::setup_frame_rate_description () +FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property) { - wxString d; - int lines = 0; - - if (_film->source_frame_rate()) { - d << std_to_wx (FrameRateConversion (_film->source_frame_rate(), _film->dcp_frame_rate()).description); - ++lines; -#ifdef HAVE_SWRESAMPLE - if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate ()) { - d << wxString::Format ( - _("Audio will be resampled from %dHz to %dHz\n"), - _film->audio_stream()->sample_rate(), - _film->target_audio_sample_rate() - ); - ++lines; - } -#endif + if (!_film) { + /* We call this method ourselves (as well as using it as a signal handler) + so _film can be 0. + */ + return; } - for (int i = lines; i < 2; ++i) { - d << wxT ("\n "); + shared_ptr<Content> content = weak_content.lock (); + shared_ptr<VideoContent> video_content; + shared_ptr<AudioContent> audio_content; + shared_ptr<FFmpegContent> ffmpeg_content; + if (content) { + video_content = dynamic_pointer_cast<VideoContent> (content); + audio_content = dynamic_pointer_cast<AudioContent> (content); + ffmpeg_content = dynamic_pointer_cast<FFmpegContent> (content); } - _frame_rate_description->SetLabel (d); + /* We can't use case {} here */ + + if (property == ContentProperty::START) { + if (content) { + _start->set (content->start (), _film->dcp_video_frame_rate ()); + } else { + _start->set (0, 24); + } + } else if (property == ContentProperty::LENGTH) { + if (content) { + _length->set (content->length (), _film->dcp_video_frame_rate ()); + } else { + _length->set (0, 24); + } + } else if (property == VideoContentProperty::VIDEO_CROP) { + checked_set (_left_crop, video_content ? video_content->crop().left : 0); + checked_set (_right_crop, video_content ? video_content->crop().right : 0); + checked_set (_top_crop, video_content ? video_content->crop().top : 0); + checked_set (_bottom_crop, video_content ? video_content->crop().bottom : 0); + setup_scaling_description (); + } else if (property == VideoContentProperty::VIDEO_RATIO) { + if (video_content) { + int n = 0; + vector<Ratio const *> ratios = Ratio::all (); + vector<Ratio const *>::iterator i = ratios.begin (); + while (i != ratios.end() && *i != video_content->ratio()) { + ++i; + ++n; + } + + if (i == ratios.end()) { + checked_set (_ratio, -1); + } else { + checked_set (_ratio, n); + } + } else { + checked_set (_ratio, -1); + } + } else if (property == AudioContentProperty::AUDIO_GAIN) { + checked_set (_audio_gain, audio_content ? audio_content->audio_gain() : 0); + } else if (property == AudioContentProperty::AUDIO_DELAY) { + checked_set (_audio_delay, audio_content ? audio_content->audio_delay() : 0); + } else if (property == AudioContentProperty::AUDIO_MAPPING) { + _audio_mapping->set (audio_content ? audio_content->audio_mapping () : AudioMapping ()); + } else if (property == FFmpegContentProperty::SUBTITLE_STREAMS) { + _subtitle_stream->Clear (); + if (ffmpeg_content) { + vector<shared_ptr<FFmpegSubtitleStream> > s = ffmpeg_content->subtitle_streams (); + if (s.empty ()) { + _subtitle_stream->Enable (false); + } + for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) { + _subtitle_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id)))); + } + + if (ffmpeg_content->subtitle_stream()) { + checked_set (_subtitle_stream, lexical_cast<string> (ffmpeg_content->subtitle_stream()->id)); + } else { + _subtitle_stream->SetSelection (wxNOT_FOUND); + } + } + setup_subtitle_control_sensitivity (); + } else if (property == FFmpegContentProperty::AUDIO_STREAMS) { + _audio_stream->Clear (); + if (ffmpeg_content) { + vector<shared_ptr<FFmpegAudioStream> > a = ffmpeg_content->audio_streams (); + for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) { + _audio_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id)))); + } + + if (ffmpeg_content->audio_stream()) { + checked_set (_audio_stream, lexical_cast<string> (ffmpeg_content->audio_stream()->id)); + } + } + setup_show_audio_sensitivity (); + } else if (property == FFmpegContentProperty::AUDIO_STREAM) { + setup_dcp_name (); + setup_show_audio_sensitivity (); + } else if (property == FFmpegContentProperty::FILTERS) { + if (ffmpeg_content) { + pair<string, string> p = Filter::ffmpeg_strings (ffmpeg_content->filters ()); + if (p.first.empty () && p.second.empty ()) { + _filters->SetLabel (_("None")); + } else { + string const b = p.first + " " + p.second; + _filters->SetLabel (std_to_wx (b)); + } + _dcp_sizer->Layout (); + } + } } -/** Called when the format widget has been changed */ void -FilmEditor::format_changed (wxCommandEvent &) +FilmEditor::setup_container () +{ + int n = 0; + vector<Ratio const *> ratios = Ratio::all (); + vector<Ratio const *>::iterator i = ratios.begin (); + while (i != ratios.end() && *i != _film->container ()) { + ++i; + ++n; + } + + if (i == ratios.end()) { + checked_set (_container, -1); + } else { + checked_set (_container, n); + } + + setup_dcp_name (); + setup_scaling_description (); +} + +/** Called when the container widget has been changed */ +void +FilmEditor::container_changed (wxCommandEvent &) { if (!_film) { return; } - int const n = _format->GetSelection (); + int const n = _container->GetSelection (); if (n >= 0) { - assert (n < int (_formats.size())); - _film->set_format (_formats[n]); + vector<Ratio const *> ratios = Ratio::all (); + assert (n < int (ratios.size())); + _film->set_container (ratios[n]); } } @@ -899,12 +865,17 @@ FilmEditor::dcp_content_type_changed (wxCommandEvent &) void FilmEditor::set_film (shared_ptr<Film> f) { - _film = f; + set_things_sensitive (f != 0); - set_things_sensitive (_film != 0); + if (_film == f) { + return; + } + + _film = f; if (_film) { _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1)); + _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2)); } if (_film) { @@ -913,41 +884,23 @@ FilmEditor::set_film (shared_ptr<Film> f) FileChanged (""); } - if (_audio_dialog) { - _audio_dialog->set_film (_film); - } - film_changed (Film::NAME); film_changed (Film::USE_DCI_NAME); film_changed (Film::CONTENT); - film_changed (Film::TRUST_CONTENT_HEADER); + film_changed (Film::LOOP); film_changed (Film::DCP_CONTENT_TYPE); - film_changed (Film::FORMAT); - film_changed (Film::CROP); - film_changed (Film::FILTERS); + film_changed (Film::CONTAINER); film_changed (Film::SCALER); - film_changed (Film::TRIM_START); - film_changed (Film::TRIM_END); - film_changed (Film::TRIM_TYPE); - film_changed (Film::DCP_AB); - film_changed (Film::CONTENT_AUDIO_STREAM); - film_changed (Film::EXTERNAL_AUDIO); - film_changed (Film::USE_CONTENT_AUDIO); - film_changed (Film::AUDIO_GAIN); - film_changed (Film::AUDIO_DELAY); - film_changed (Film::STILL_DURATION); film_changed (Film::WITH_SUBTITLES); film_changed (Film::SUBTITLE_OFFSET); film_changed (Film::SUBTITLE_SCALE); film_changed (Film::COLOUR_LUT); film_changed (Film::J2K_BANDWIDTH); film_changed (Film::DCI_METADATA); - film_changed (Film::SIZE); - film_changed (Film::LENGTH); - film_changed (Film::CONTENT_AUDIO_STREAMS); - film_changed (Film::SUBTITLE_STREAMS); - film_changed (Film::SOURCE_FRAME_RATE); - film_changed (Film::DCP_FRAME_RATE); + film_changed (Film::DCP_VIDEO_FRAME_RATE); + + wxListEvent ev; + content_selection_changed (ev); } /** Updates the sensitivity of lots of widgets to a given value. @@ -961,41 +914,48 @@ FilmEditor::set_things_sensitive (bool s) _name->Enable (s); _use_dci_name->Enable (s); _edit_dci_button->Enable (s); - _format->Enable (s); + _ratio->Enable (s); _content->Enable (s); - _trust_content_header->Enable (s); _left_crop->Enable (s); _right_crop->Enable (s); _top_crop->Enable (s); _bottom_crop->Enable (s); _filters_button->Enable (s); _scaler->Enable (s); - _audio_stream->Enable (s); _dcp_content_type->Enable (s); + _best_dcp_frame_rate->Enable (s); _dcp_frame_rate->Enable (s); - _trim_start->Enable (s); - _trim_end->Enable (s); - _trim_type->Enable (s); - _dcp_ab->Enable (s); _colour_lut->Enable (s); _j2k_bandwidth->Enable (s); _audio_gain->Enable (s); _audio_gain_calculate_button->Enable (s); _show_audio->Enable (s); _audio_delay->Enable (s); - _still_duration->Enable (s); + _container->Enable (s); + _loop_content->Enable (s); + _loop_count->Enable (s); setup_subtitle_control_sensitivity (); - setup_audio_control_sensitivity (); setup_show_audio_sensitivity (); + setup_content_sensitivity (); } /** Called when the `Edit filters' button has been clicked */ void FilmEditor::edit_filters_clicked (wxCommandEvent &) { - FilterDialog* d = new FilterDialog (this, _film->filters()); - d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1)); + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c); + if (!fc) { + return; + } + + FilterDialog* d = new FilterDialog (this, fc->filters()); + d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1)); d->ShowModal (); d->Destroy (); } @@ -1017,106 +977,40 @@ FilmEditor::scaler_changed (wxCommandEvent &) void FilmEditor::audio_gain_changed (wxCommandEvent &) { - if (!_film) { + shared_ptr<AudioContent> ac = selected_audio_content (); + if (!ac) { return; } - _film->set_audio_gain (_audio_gain->GetValue ()); + ac->set_audio_gain (_audio_gain->GetValue ()); } void FilmEditor::audio_delay_changed (wxCommandEvent &) { - if (!_film) { + shared_ptr<AudioContent> ac = selected_audio_content (); + if (!ac) { return; } - _film->set_audio_delay (_audio_delay->GetValue ()); -} - -wxControl * -FilmEditor::video_control (wxControl* c) -{ - _video_controls.push_back (c); - return c; -} - -wxControl * -FilmEditor::still_control (wxControl* c) -{ - _still_controls.push_back (c); - return c; + ac->set_audio_delay (_audio_delay->GetValue ()); } void -FilmEditor::setup_visibility () +FilmEditor::setup_main_notebook_size () { - ContentType c = VIDEO; - - if (_film) { - c = _film->content_type (); - } - - for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) { - (*i)->Show (c == VIDEO); - } - - for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) { - (*i)->Show (c == STILL); - } + _main_notebook->InvalidateBestSize (); - setup_notebook_size (); -} + _content_sizer->Layout (); + _content_sizer->SetSizeHints (_content_panel); + _dcp_sizer->Layout (); + _dcp_sizer->SetSizeHints (_dcp_panel); -void -FilmEditor::setup_notebook_size () -{ - _notebook->InvalidateBestSize (); - - _film_sizer->Layout (); - _film_sizer->SetSizeHints (_film_panel); - _video_sizer->Layout (); - _video_sizer->SetSizeHints (_video_panel); - _audio_sizer->Layout (); - _audio_sizer->SetSizeHints (_audio_panel); - _subtitle_sizer->Layout (); - _subtitle_sizer->SetSizeHints (_subtitle_panel); - - _notebook->Fit (); + _main_notebook->Fit (); Fit (); } void -FilmEditor::still_duration_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - _film->set_still_duration (_still_duration->GetValue ()); -} - -void -FilmEditor::trim_start_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - _film->set_trim_start (_trim_start->GetValue ()); -} - -void -FilmEditor::trim_end_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - _film->set_trim_end (_trim_end->GetValue ()); -} - -void FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &) { GainCalculatorDialog* d = new GainCalculatorDialog (this); @@ -1144,29 +1038,16 @@ FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &) } void -FilmEditor::setup_formats () +FilmEditor::setup_ratios () { - ContentType c = VIDEO; + _ratios = Ratio::all (); - if (_film) { - c = _film->content_type (); + _ratio->Clear (); + for (vector<Ratio const *>::iterator i = _ratios.begin(); i != _ratios.end(); ++i) { + _ratio->Append (std_to_wx ((*i)->nickname ())); } - - _formats.clear (); - vector<Format const *> fmt = Format::all (); - for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) { - if (c == VIDEO || (c == STILL && dynamic_cast<VariableFormat const *> (*i))) { - _formats.push_back (*i); - } - } - - _format->Clear (); - for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) { - _format->Append (std_to_wx ((*i)->name ())); - } - - _film_sizer->Layout (); + _dcp_sizer->Layout (); } void @@ -1184,7 +1065,7 @@ FilmEditor::setup_subtitle_control_sensitivity () { bool h = false; if (_generally_sensitive && _film) { - h = !_film->subtitle_streams().empty(); + h = _film->has_subtitles (); } _with_subtitles->Enable (h); @@ -1194,30 +1075,11 @@ FilmEditor::setup_subtitle_control_sensitivity () j = _film->with_subtitles (); } - _subtitle_stream->Enable (j); _subtitle_offset->Enable (j); _subtitle_scale->Enable (j); } void -FilmEditor::setup_audio_control_sensitivity () -{ - _use_content_audio->Enable (_generally_sensitive && _film && !_film->content_audio_streams().empty()); - _use_external_audio->Enable (_generally_sensitive); - - bool const source = _generally_sensitive && _use_content_audio->GetValue(); - bool const external = _generally_sensitive && _use_external_audio->GetValue(); - - _audio_stream->Enable (source); - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - _external_audio[i]->Enable (external); - } - - _pad_with_silence->Enable (_generally_sensitive && _film && _film->audio_stream() && _film->audio_stream()->channels() < MAX_AUDIO_CHANNELS); - _minimum_audio_channels->Enable (_generally_sensitive && _pad_with_silence->GetValue ()); -} - -void FilmEditor::use_dci_name_toggled (wxCommandEvent &) { if (!_film) { @@ -1241,143 +1103,202 @@ FilmEditor::edit_dci_button_clicked (wxCommandEvent &) } void -FilmEditor::setup_streams () +FilmEditor::active_jobs_changed (bool a) { - _audio_stream->Clear (); - vector<shared_ptr<AudioStream> > a = _film->content_audio_streams (); - for (vector<shared_ptr<AudioStream> >::iterator i = a.begin(); i != a.end(); ++i) { - shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (*i); - assert (ffa); - _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ()))); - } - - if (_film->use_content_audio() && _film->audio_stream()) { - checked_set (_audio_stream, _film->audio_stream()->to_string()); - } + set_things_sensitive (!a); +} - _subtitle_stream->Clear (); - vector<shared_ptr<SubtitleStream> > s = _film->subtitle_streams (); - for (vector<shared_ptr<SubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) { - _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ()))); - } - if (_film->subtitle_stream()) { - checked_set (_subtitle_stream, _film->subtitle_stream()->to_string()); +void +FilmEditor::setup_dcp_name () +{ + string s = _film->dcp_name (true); + if (s.length() > 28) { + _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("...")); + _dcp_name->SetToolTip (std_to_wx (s)); } else { - _subtitle_stream->SetSelection (wxNOT_FOUND); + _dcp_name->SetLabel (std_to_wx (s)); } } void -FilmEditor::audio_stream_changed (wxCommandEvent &) +FilmEditor::show_audio_clicked (wxCommandEvent &) { - if (!_film) { + if (_audio_dialog) { + _audio_dialog->Destroy (); + _audio_dialog = 0; + } + + shared_ptr<Content> c = selected_content (); + if (!c) { return; } - _film->set_content_audio_stream ( - audio_stream_factory ( - string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())), - Film::state_version - ) - ); + shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c); + if (!ac) { + return; + } + + _audio_dialog = new AudioDialog (this); + _audio_dialog->Show (); + _audio_dialog->set_content (ac); } void -FilmEditor::subtitle_stream_changed (wxCommandEvent &) +FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &) { if (!_film) { return; } + + _film->set_dcp_video_frame_rate (_film->best_dcp_video_frame_rate ()); +} - _film->set_subtitle_stream ( - subtitle_stream_factory ( - string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())), - Film::state_version - ) - ); +void +FilmEditor::setup_show_audio_sensitivity () +{ + _show_audio->Enable (_film); } void -FilmEditor::setup_audio_details () +FilmEditor::setup_content () { - if (!_film->content_audio_stream()) { - _audio->SetLabel (wxT ("")); - } else { - wxString s; - if (_film->audio_stream()->channels() == 1) { - s << _("1 channel"); - } else { - s << _film->audio_stream()->channels () << wxT (" ") << _("channels"); + string selected_summary; + int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s != -1) { + selected_summary = wx_to_std (_content->GetItemText (s)); + } + + _content->DeleteAllItems (); + + Playlist::ContentList content = _film->content (); + for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) { + int const t = _content->GetItemCount (); + _content->InsertItem (t, std_to_wx ((*i)->summary ())); + if ((*i)->summary() == selected_summary) { + _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } - s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz"); - _audio->SetLabel (s); } - setup_notebook_size (); + if (selected_summary.empty () && !content.empty ()) { + /* Select the item of content if none was selected before */ + _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } } void -FilmEditor::active_jobs_changed (bool a) +FilmEditor::content_add_clicked (wxCommandEvent &) { - set_things_sensitive (!a); + wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE); + int const r = d->ShowModal (); + d->Destroy (); + + if (r != wxID_OK) { + return; + } + + wxArrayString paths; + d->GetPaths (paths); + + for (unsigned int i = 0; i < paths.GetCount(); ++i) { + boost::filesystem::path p (wx_to_std (paths[i])); + + shared_ptr<Content> c; + + if (ImageMagickContent::valid_file (p)) { + c.reset (new ImageMagickContent (_film, p)); + } else if (SndfileContent::valid_file (p)) { + c.reset (new SndfileContent (_film, p)); + } else { + c.reset (new FFmpegContent (_film, p)); + } + + _film->examine_and_add_content (c); + } } void -FilmEditor::use_audio_changed (wxCommandEvent &) +FilmEditor::content_remove_clicked (wxCommandEvent &) { - _film->set_use_content_audio (_use_content_audio->GetValue()); + shared_ptr<Content> c = selected_content (); + if (c) { + _film->remove_content (c); + } } void -FilmEditor::external_audio_changed (wxCommandEvent &) +FilmEditor::content_selection_changed (wxListEvent &) { - vector<string> a; - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - a.push_back (wx_to_std (_external_audio[i]->GetPath())); + setup_content_sensitivity (); + shared_ptr<Content> s = selected_content (); + + if (_audio_dialog && s && dynamic_pointer_cast<AudioContent> (s)) { + _audio_dialog->set_content (dynamic_pointer_cast<AudioContent> (s)); } - - _film->set_external_audio (a); + + film_content_changed (s, ContentProperty::START); + film_content_changed (s, ContentProperty::LENGTH); + film_content_changed (s, VideoContentProperty::VIDEO_CROP); + film_content_changed (s, VideoContentProperty::VIDEO_RATIO); + film_content_changed (s, AudioContentProperty::AUDIO_GAIN); + film_content_changed (s, AudioContentProperty::AUDIO_DELAY); + film_content_changed (s, AudioContentProperty::AUDIO_MAPPING); + film_content_changed (s, FFmpegContentProperty::AUDIO_STREAM); + film_content_changed (s, FFmpegContentProperty::AUDIO_STREAMS); + film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAM); } void -FilmEditor::setup_dcp_name () +FilmEditor::setup_content_sensitivity () { - string s = _film->dcp_name (true); - if (s.length() > 28) { - _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("...")); - _dcp_name->SetToolTip (std_to_wx (s)); - } else { - _dcp_name->SetLabel (std_to_wx (s)); - } + _content_add->Enable (_generally_sensitive); + + shared_ptr<Content> selection = selected_content (); + + _content_remove->Enable (selection && _generally_sensitive); + _content_timeline->Enable (_generally_sensitive); + + _video_panel->Enable (selection && dynamic_pointer_cast<VideoContent> (selection) && _generally_sensitive); + _audio_panel->Enable (selection && dynamic_pointer_cast<AudioContent> (selection) && _generally_sensitive); + _subtitle_panel->Enable (selection && dynamic_pointer_cast<FFmpegContent> (selection) && _generally_sensitive); + _timing_panel->Enable (selection && _generally_sensitive); } -void -FilmEditor::show_audio_clicked (wxCommandEvent &) +shared_ptr<Content> +FilmEditor::selected_content () { - if (_audio_dialog) { - _audio_dialog->Destroy (); - _audio_dialog = 0; + int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s == -1) { + return shared_ptr<Content> (); + } + + Playlist::ContentList c = _film->content (); + if (s < 0 || size_t (s) >= c.size ()) { + return shared_ptr<Content> (); } - _audio_dialog = new AudioDialog (this); - _audio_dialog->Show (); - _audio_dialog->set_film (_film); + return c[s]; } -void -FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &) +shared_ptr<VideoContent> +FilmEditor::selected_video_content () { - if (!_film) { - return; + shared_ptr<Content> c = selected_content (); + if (!c) { + return shared_ptr<VideoContent> (); } - - _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ())); + + return dynamic_pointer_cast<VideoContent> (c); } -void -FilmEditor::setup_show_audio_sensitivity () +shared_ptr<AudioContent> +FilmEditor::selected_audio_content () { - _show_audio->Enable (_film && _film->has_audio ()); + shared_ptr<Content> c = selected_content (); + if (!c) { + return shared_ptr<AudioContent> (); + } + + return dynamic_pointer_cast<AudioContent> (c); } void @@ -1385,13 +1306,15 @@ FilmEditor::setup_scaling_description () { wxString d; +#if 0 +XXX int lines = 0; - if (_film->size().width && _film->size().height) { + if (_film->video_size().width && _film->video_size().height) { d << wxString::Format ( _("Original video is %dx%d (%.2f:1)\n"), - _film->size().width, _film->size().height, - float (_film->size().width) / _film->size().height + _film->video_size().width, _film->video_size().height, + float (_film->video_size().width) / _film->video_size().height ); ++lines; } @@ -1433,18 +1356,194 @@ FilmEditor::setup_scaling_description () d << wxT ("\n "); } +#endif _scaling_description->SetLabel (d); } void -FilmEditor::trim_type_changed (wxCommandEvent &) +FilmEditor::loop_content_toggled (wxCommandEvent &) +{ + if (_loop_content->GetValue ()) { + _film->set_loop (_loop_count->GetValue ()); + } else { + _film->set_loop (1); + } + + setup_loop_sensitivity (); +} + +void +FilmEditor::loop_count_changed (wxCommandEvent &) { - _film->set_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE); + _film->set_loop (_loop_count->GetValue ()); +} + +void +FilmEditor::setup_loop_sensitivity () +{ + _loop_count->Enable (_loop_content->GetValue ()); +} + +void +FilmEditor::content_timeline_clicked (wxCommandEvent &) +{ + if (_timeline_dialog) { + _timeline_dialog->Destroy (); + _timeline_dialog = 0; + } + + _timeline_dialog = new TimelineDialog (this, _film); + _timeline_dialog->Show (); +} + +void +FilmEditor::audio_stream_changed (wxCommandEvent &) +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c); + if (!fc) { + return; + } + + vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams (); + vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin (); + string const s = string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())); + while (i != a.end() && lexical_cast<string> ((*i)->id) != s) { + ++i; + } + + if (i != a.end ()) { + fc->set_audio_stream (*i); + } + + if (!fc->audio_stream ()) { + _audio_description->SetLabel (wxT ("")); + } else { + wxString s; + if (fc->audio_channels() == 1) { + s << _("1 channel"); + } else { + s << fc->audio_channels() << wxT (" ") << _("channels"); + } + s << wxT (", ") << fc->content_audio_frame_rate() << _("Hz"); + _audio_description->SetLabel (s); + } +} + + + +void +FilmEditor::subtitle_stream_changed (wxCommandEvent &) +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c); + if (!fc) { + return; + } + + vector<shared_ptr<FFmpegSubtitleStream> > a = fc->subtitle_streams (); + vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin (); + string const s = string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())); + while (i != a.end() && lexical_cast<string> ((*i)->id) != s) { + ++i; + } + + if (i != a.end ()) { + fc->set_subtitle_stream (*i); + } +} + +void +FilmEditor::audio_mapping_changed (AudioMapping m) +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c); + if (!ac) { + return; + } + + ac->set_audio_mapping (m); +} + +void +FilmEditor::start_changed () +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + c->set_start (_start->get (_film->dcp_video_frame_rate ())); +} + +void +FilmEditor::length_changed () +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<ImageMagickContent> ic = dynamic_pointer_cast<ImageMagickContent> (c); + if (ic) { + ic->set_video_length (_length->get(_film->dcp_video_frame_rate()) * ic->video_frame_rate() / TIME_HZ); + } +} + +void +FilmEditor::set_selection (weak_ptr<Content> wc) +{ + Playlist::ContentList content = _film->content (); + for (size_t i = 0; i < content.size(); ++i) { + if (content[i] == wc.lock ()) { + _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } else { + _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); + } + } +} + +void +FilmEditor::ratio_changed (wxCommandEvent &) +{ + if (!_film) { + return; + } + + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c); + if (!vc) { + return; + } + + int const n = _ratio->GetSelection (); + if (n >= 0) { + vector<Ratio const *> ratios = Ratio::all (); + assert (n < int (ratios.size())); + vc->set_ratio (ratios[n]); + } } void FilmEditor::setup_minimum_audio_channels () { +#if 0 if (!_film || !_film->audio_stream ()) { _pad_with_silence->SetValue (false); return; @@ -1454,12 +1553,13 @@ FilmEditor::setup_minimum_audio_channels () AudioMapping m (_film); _minimum_audio_channels->SetRange (m.minimum_dcp_channels() + 1, MAX_AUDIO_CHANNELS); +#endif } void FilmEditor::pad_with_silence_toggled (wxCommandEvent &) { - setup_audio_control_sensitivity (); + } void @@ -1469,5 +1569,5 @@ FilmEditor::minimum_audio_channels_changed (wxCommandEvent &) return; } - _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ()); +// _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ()); } diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index c2d064ca2..705eb16af 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -16,7 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - + /** @file src/film_editor.h * @brief A wx widget to edit a film's metadata, and perform various functions. */ @@ -29,8 +29,14 @@ #include "lib/film.h" class wxNotebook; +class wxListCtrl; +class wxListEvent; class Film; class AudioDialog; +class TimelineDialog; +class AudioMappingView; +class Ratio; +class Timecode; /** @class FilmEditor * @brief A wx widget to edit a film's metadata, and perform various functions. @@ -41,15 +47,17 @@ public: FilmEditor (boost::shared_ptr<Film>, wxWindow *); void set_film (boost::shared_ptr<Film>); - void setup_visibility (); + void set_selection (boost::weak_ptr<Content>); boost::signals2::signal<void (std::string)> FileChanged; private: - void make_film_panel (); + void make_dcp_panel (); + void make_content_panel (); void make_video_panel (); void make_audio_panel (); void make_subtitle_panel (); + void make_timing_panel (); void connect_to_widgets (); /* Handle changes to the view */ @@ -60,14 +68,13 @@ private: void right_crop_changed (wxCommandEvent &); 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 trim_start_changed (wxCommandEvent &); - void trim_end_changed (wxCommandEvent &); - void trim_type_changed (wxCommandEvent &); + void trust_content_headers_changed (wxCommandEvent &); + void content_selection_changed (wxListEvent &); + void content_add_clicked (wxCommandEvent &); + void content_remove_clicked (wxCommandEvent &); + void imagemagick_video_length_changed (wxCommandEvent &); + void container_changed (wxCommandEvent &); void dcp_content_type_changed (wxCommandEvent &); - void dcp_ab_toggled (wxCommandEvent &); void scaler_changed (wxCommandEvent &); void audio_gain_changed (wxCommandEvent &); void audio_gain_calculate_button_clicked (wxCommandEvent &); @@ -78,121 +85,103 @@ private: void subtitle_scale_changed (wxCommandEvent &); void colour_lut_changed (wxCommandEvent &); void j2k_bandwidth_changed (wxCommandEvent &); - void still_duration_changed (wxCommandEvent &); - void audio_stream_changed (wxCommandEvent &); - void subtitle_stream_changed (wxCommandEvent &); - void use_audio_changed (wxCommandEvent &); - void external_audio_changed (wxCommandEvent &); void dcp_frame_rate_changed (wxCommandEvent &); void best_dcp_frame_rate_clicked (wxCommandEvent &); + void edit_filters_clicked (wxCommandEvent &); + void loop_content_toggled (wxCommandEvent &); + void loop_count_changed (wxCommandEvent &); + void content_timeline_clicked (wxCommandEvent &); + void audio_stream_changed (wxCommandEvent &); + void subtitle_stream_changed (wxCommandEvent &); + void audio_mapping_changed (AudioMapping); + void start_changed (); + void length_changed (); + void ratio_changed (wxCommandEvent &); void pad_with_silence_toggled (wxCommandEvent &); void minimum_audio_channels_changed (wxCommandEvent &); /* Handle changes to the model */ void film_changed (Film::Property); - - /* Button clicks */ - void edit_filters_clicked (wxCommandEvent &); + void film_content_changed (boost::weak_ptr<Content>, int); void set_things_sensitive (bool); - void setup_formats (); + void setup_ratios (); void setup_subtitle_control_sensitivity (); - void setup_audio_control_sensitivity (); - void setup_streams (); - void setup_audio_details (); void setup_dcp_name (); void setup_show_audio_sensitivity (); void setup_scaling_description (); - void setup_notebook_size (); - void setup_frame_rate_description (); + void setup_main_notebook_size (); + void setup_content (); + void setup_container (); + void setup_content_sensitivity (); + void setup_loop_sensitivity (); void setup_minimum_audio_channels (); - wxControl* video_control (wxControl *); - wxControl* still_control (wxControl *); - void active_jobs_changed (bool); - - wxNotebook* _notebook; - wxPanel* _film_panel; - wxSizer* _film_sizer; + boost::shared_ptr<Content> selected_content (); + boost::shared_ptr<VideoContent> selected_video_content (); + boost::shared_ptr<AudioContent> selected_audio_content (); + + wxNotebook* _main_notebook; + wxNotebook* _content_notebook; + wxPanel* _dcp_panel; + wxSizer* _dcp_sizer; + wxPanel* _content_panel; + wxSizer* _content_sizer; wxPanel* _video_panel; - wxSizer* _video_sizer; wxPanel* _audio_panel; - wxSizer* _audio_sizer; wxPanel* _subtitle_panel; - wxSizer* _subtitle_sizer; + wxPanel* _timing_panel; /** The film we are editing */ boost::shared_ptr<Film> _film; - /** The Film's name */ wxTextCtrl* _name; wxStaticText* _dcp_name; wxCheckBox* _use_dci_name; + wxChoice* _container; + wxListCtrl* _content; + wxButton* _content_add; + wxButton* _content_remove; + wxButton* _content_earlier; + wxButton* _content_later; + wxButton* _content_timeline; + wxCheckBox* _loop_content; + wxSpinCtrl* _loop_count; wxButton* _edit_dci_button; - /** The Film's format */ - wxChoice* _format; + wxChoice* _ratio; + wxStaticText* _ratio_description; wxStaticText* _scaling_description; - /** The Film's content file */ - wxFilePickerCtrl* _content; - wxCheckBox* _trust_content_header; - /** The Film's left crop */ wxSpinCtrl* _left_crop; - /** The Film's right crop */ wxSpinCtrl* _right_crop; - /** The Film's top crop */ wxSpinCtrl* _top_crop; - /** The Film's bottom crop */ wxSpinCtrl* _bottom_crop; - /** Currently-applied filters */ wxStaticText* _filters; - /** Button to open the filters dialogue */ wxButton* _filters_button; - /** The Film's scaler */ wxChoice* _scaler; - wxRadioButton* _use_content_audio; - wxChoice* _audio_stream; - wxRadioButton* _use_external_audio; - wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS]; - /** The Film's audio gain */ wxSpinCtrl* _audio_gain; - /** A button to open the gain calculation dialogue */ wxButton* _audio_gain_calculate_button; wxButton* _show_audio; - /** The Film's audio delay */ wxSpinCtrl* _audio_delay; wxCheckBox* _with_subtitles; - wxChoice* _subtitle_stream; wxSpinCtrl* _subtitle_offset; wxSpinCtrl* _subtitle_scale; wxChoice* _colour_lut; wxSpinCtrl* _j2k_bandwidth; - /** The Film's DCP content type */ wxChoice* _dcp_content_type; - /** The Film's source frame rate */ - wxStaticText* _source_frame_rate; wxChoice* _dcp_frame_rate; wxButton* _best_dcp_frame_rate; wxCheckBox* _pad_with_silence; wxSpinCtrl* _minimum_audio_channels; - wxStaticText* _frame_rate_description; - /** The Film's length */ - wxStaticText* _length; - /** The Film's audio details */ - wxStaticText* _audio; - /** The Film's duration for still sources */ - wxSpinCtrl* _still_duration; - - wxSpinCtrl* _trim_start; - wxSpinCtrl* _trim_end; - wxChoice* _trim_type; - /** Selector to generate an A/B comparison DCP */ - wxCheckBox* _dcp_ab; - - std::list<wxControl*> _video_controls; - std::list<wxControl*> _still_controls; + wxChoice* _audio_stream; + wxStaticText* _audio_description; + wxChoice* _subtitle_stream; + AudioMappingView* _audio_mapping; + Timecode* _start; + Timecode* _length; - std::vector<Format const *> _formats; + std::vector<Ratio const *> _ratios; bool _generally_sensitive; AudioDialog* _audio_dialog; + TimelineDialog* _timeline_dialog; }; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 6845031cf..8ef64d509 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -25,26 +25,31 @@ #include <iomanip> #include <wx/tglbtn.h> #include "lib/film.h" -#include "lib/format.h" +#include "lib/ratio.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 "lib/filter.h" +#include "lib/player.h" +#include "lib/video_content.h" +#include "lib/ffmpeg_content.h" +#include "lib/imagemagick_content.h" #include "film_viewer.h" #include "wx_util.h" #include "video_decoder.h" using std::string; using std::pair; +using std::min; using std::max; using std::cout; using std::list; using boost::shared_ptr; +using boost::dynamic_pointer_cast; +using boost::weak_ptr; using libdcp::Size; FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) @@ -56,7 +61,6 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) , _frame (new wxStaticText (this, wxID_ANY, wxT(""))) , _timecode (new wxStaticText (this, wxID_ANY, wxT(""))) , _play_button (new wxToggleButton (this, wxID_ANY, _("Play"))) - , _display_frame_x (0) , _got_frame (false) { #ifndef __WXOSX__ @@ -111,49 +115,27 @@ void FilmViewer::film_changed (Film::Property p) { switch (p) { - case Film::FORMAT: + case Film::CONTAINER: calculate_sizes (); update_from_raw (); break; case Film::CONTENT: { - DecodeOptions o; - o.decode_audio = false; - o.decode_subtitles = true; - o.video_sync = false; - - try { - _decoders = decoder_factory (_film, o); - } catch (StringError& e) { - error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data())); - return; - } - - if (_decoders.video == 0) { - break; - } - _decoders.video->set_subtitle_stream (_film->subtitle_stream()); - _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4)); - _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this)); calculate_sizes (); - get_frame (); - _panel->Refresh (); - _slider->Show (_film->content_type() == VIDEO); - _play_button->Show (_film->content_type() == VIDEO); - _v_sizer->Layout (); + wxScrollEvent ev; + slider_moved (ev); break; } case Film::WITH_SUBTITLES: case Film::SUBTITLE_OFFSET: case Film::SUBTITLE_SCALE: - case Film::SCALER: - case Film::FILTERS: - update_from_raw (); + update_from_decoder (); + raw_to_display (); + _panel->Refresh (); + _panel->Update (); break; - case Film::SUBTITLE_STREAM: - if (_decoders.video) { - _decoders.video->set_subtitle_stream (_film->subtitle_stream ()); - } + case Film::SCALER: + update_from_decoder (); break; default: break; @@ -166,7 +148,7 @@ FilmViewer::set_film (shared_ptr<Film> f) if (_film == f) { return; } - + _film = f; _raw_frame.reset (); @@ -178,23 +160,28 @@ FilmViewer::set_film (shared_ptr<Film> f) return; } + _player = f->player (); + _player->disable_audio (); + _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3)); + _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1)); + _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2)); film_changed (Film::CONTENT); - film_changed (Film::FORMAT); + film_changed (Film::CONTAINER); film_changed (Film::WITH_SUBTITLES); film_changed (Film::SUBTITLE_OFFSET); film_changed (Film::SUBTITLE_SCALE); - film_changed (Film::SUBTITLE_STREAM); } void -FilmViewer::decoder_changed () +FilmViewer::update_from_decoder () { - if (_decoders.video == 0 || _decoders.video->seek_to_last ()) { + if (!_player) { return; } + _player->seek (_player->video_position() - _film->video_frames_to_time (1)); get_frame (); _panel->Refresh (); _panel->Update (); @@ -203,14 +190,14 @@ FilmViewer::decoder_changed () void FilmViewer::timer (wxTimerEvent &) { - if (!_film || !_decoders.video) { + if (!_player) { return; } get_frame (); if (_film->length()) { - int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate()); + int const new_slider_position = 4096 * _player->video_position() / _film->length(); if (new_slider_position != _slider->GetValue()) { _slider->SetValue (new_slider_position); } @@ -231,22 +218,9 @@ FilmViewer::paint_panel (wxPaintEvent &) return; } - if (_display_frame_x) { - dc.SetPen(*wxBLACK_PEN); - dc.SetBrush(*wxBLACK_BRUSH); - dc.DrawRectangle (0, 0, _display_frame_x, _film_size.height); - dc.DrawRectangle (_display_frame_x + _film_size.width, 0, _display_frame_x, _film_size.height); - } - - wxImage frame (_film_size.width, _film_size.height, _display_frame->data()[0], true); + wxImage frame (_out_size.width, _out_size.height, _display_frame->data()[0], true); wxBitmap frame_bitmap (frame); - dc.DrawBitmap (frame_bitmap, _display_frame_x, 0); - - 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); - } + dc.DrawBitmap (frame_bitmap, 0, 0); if (_out_size.width < _panel_size.width) { wxPen p (GetBackgroundColour ()); @@ -269,12 +243,10 @@ FilmViewer::paint_panel (wxPaintEvent &) void FilmViewer::slider_moved (wxScrollEvent &) { - if (!_film || !_film->length() || !_decoders.video) { - return; - } + cout << "slider " << _slider->GetValue() << " " << _film->length() << "\n"; - if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) { - return; + if (_film && _player) { + _player->seek (_slider->GetValue() * _film->length() / 4096); } get_frame (); @@ -311,49 +283,21 @@ FilmViewer::raw_to_display () return; } - boost::shared_ptr<const Image> input = _raw_frame; - - pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); - if (!s.second.empty ()) { - input = input->post_process (s.second, true); - } - /* Get a compacted image as we have to feed it to wxWidgets */ - _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false); - - if (_raw_sub) { - - /* Our output is already cropped by the decoder, so we need to account for that - when working out the scale that we are applying. - */ - - Size const cropped_size = _film->cropped_size (_film->size ()); - - dvdomatic::Rect tx = subtitle_transformed_area ( - float (_film_size.width) / cropped_size.width, - float (_film_size.height) / cropped_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(); - _display_sub_position.x += _display_frame_x; - } else { - _display_sub.reset (); - } + _display_frame.reset (new SimpleImage (_raw_frame, false)); } void FilmViewer::calculate_sizes () { - if (!_film) { + if (!_film || !_player) { return; } - Format const * format = _film->format (); + Ratio const * container = _film->container (); float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height; - float const film_ratio = format ? format->container_ratio () : 1.78; + float const film_ratio = container ? container->ratio () : 1.78; if (panel_ratio < film_ratio) { /* panel is less widscreen than the film; clamp width */ @@ -365,21 +309,12 @@ FilmViewer::calculate_sizes () _out_size.width = _out_size.height * film_ratio; } - /* Work out how much padding there is in terms of our display; this will be the x position - of our _display_frame. - */ - _display_frame_x = 0; - if (format) { - _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width; - } - - _film_size = _out_size; - _film_size.width -= _display_frame_x * 2; - /* Catch silly values */ - if (_out_size.width < 64) { - _out_size.width = 64; - } + _out_size.width = max (64, _out_size.width); + _out_size.height = max (64, _out_size.height); + + _player->set_video_container_size (_out_size); + update_from_decoder (); } void @@ -391,32 +326,31 @@ FilmViewer::play_clicked (wxCommandEvent &) void FilmViewer::check_play_state () { - if (!_film) { + if (!_film || _film->dcp_video_frame_rate() == 0) { return; } if (_play_button->GetValue()) { - _timer.Start (1000 / _film->source_frame_rate()); + _timer.Start (1000 / _film->dcp_video_frame_rate()); } else { _timer.Stop (); } } void -FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, double t) +FilmViewer::process_video (shared_ptr<const Image> image, bool, Time t) { _raw_frame = image; - _raw_sub = sub; raw_to_display (); _got_frame = true; - double const fps = _decoders.video->frames_per_second (); + double const fps = _film->dcp_video_frame_rate (); /* Count frame number from 1 ... not sure if this is the best idea */ - _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps)) + 1)); + _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1)); - double w = t; + double w = static_cast<double>(t) / TIME_HZ; int const h = (w / 3600); w -= h * 3600; int const m = (w / 60); @@ -427,13 +361,16 @@ FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subti _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f)); } +/** Get a new _raw_frame from the decoder and then do + * raw_to_display (). + */ void FilmViewer::get_frame () { /* Clear our raw frame in case we don't get a new one */ _raw_frame.reset (); - if (_decoders.video == 0) { + if (!_player) { _display_frame.reset (); return; } @@ -441,7 +378,7 @@ FilmViewer::get_frame () try { _got_frame = false; while (!_got_frame) { - if (_decoders.video->pass ()) { + if (_player->pass ()) { /* We didn't get a frame before the decoder gave up, so clear our display frame. */ @@ -477,13 +414,25 @@ FilmViewer::active_jobs_changed (bool a) } void +FilmViewer::film_content_changed (weak_ptr<Content>, int p) +{ + if (p == ContentProperty::LENGTH) { + /* Force an update to our frame */ + wxScrollEvent ev; + slider_moved (ev); + } else if (p == VideoContentProperty::VIDEO_CROP || p == VideoContentProperty::VIDEO_RATIO) { + update_from_decoder (); + } +} + +void FilmViewer::back_clicked (wxCommandEvent &) { - if (!_decoders.video) { + if (!_player) { return; } - _decoders.video->seek_back (); + _player->seek_back (); get_frame (); _panel->Refresh (); _panel->Update (); @@ -492,11 +441,10 @@ FilmViewer::back_clicked (wxCommandEvent &) void FilmViewer::forward_clicked (wxCommandEvent &) { - if (!_decoders.video) { + if (!_player) { return; } - _decoders.video->seek_forward (); get_frame (); _panel->Refresh (); _panel->Update (); diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index ed5874fbc..6c18c7c5b 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -23,16 +23,31 @@ #include <wx/wx.h> #include "lib/film.h" -#include "lib/decoder_factory.h" class wxToggleButton; class FFmpegPlayer; class Image; class RGBPlusAlphaImage; -class Subtitle; /** @class FilmViewer * @brief A wx widget to view a preview of a Film. + * + * The film takes the following path through the viewer: + * + * 1. get_frame() asks our _player to decode some data. If it does, process_video() + * will be called. + * + * 2. process_video() takes the image from the decoder (_raw_frame) and calls raw_to_display(). + * + * 3. raw_to_display() copies _raw_frame to _display_frame, processing it and scaling it. + * + * 4. calling _panel->Refresh() and _panel->Update() results in paint_panel() being called; + * this creates frame_bitmap from _display_frame and blits it to the display. + * + * update_from_decoder() asks the player to re-emit its current frame on the next pass(), and then + * starts from step #1. + * + * update_from_raw() starts at step #3, then calls _panel->Refresh and _panel->Update. */ class FilmViewer : public wxPanel { @@ -43,16 +58,17 @@ public: private: void film_changed (Film::Property); + void film_content_changed (boost::weak_ptr<Content>, int); void paint_panel (wxPaintEvent &); void panel_sized (wxSizeEvent &); void slider_moved (wxScrollEvent &); void play_clicked (wxCommandEvent &); void timer (wxTimerEvent &); - void process_video (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double); + void process_video (boost::shared_ptr<const Image>, bool, Time); void calculate_sizes (); void check_play_state (); void update_from_raw (); - void decoder_changed (); + void update_from_decoder (); void raw_to_display (); void get_frame (); void active_jobs_changed (bool); @@ -60,6 +76,7 @@ private: void forward_clicked (wxCommandEvent &); boost::shared_ptr<Film> _film; + boost::shared_ptr<Player> _player; wxSizer* _v_sizer; wxPanel* _panel; @@ -71,22 +88,12 @@ private: wxToggleButton* _play_button; wxTimer _timer; - Decoders _decoders; boost::shared_ptr<const Image> _raw_frame; - boost::shared_ptr<Subtitle> _raw_sub; boost::shared_ptr<const Image> _display_frame; - /* The x offset at which we display the actual film content; this corresponds - to the film's padding converted to our coordinates. - */ - int _display_frame_x; - boost::shared_ptr<RGBPlusAlphaImage> _display_sub; - Position _display_sub_position; bool _got_frame; /** Size of our output (including padding if we have any) */ libdcp::Size _out_size; - /** Size that we will make our film (equal to _out_size unless we have padding) */ - libdcp::Size _film_size; /** Size of the panel that we have available */ libdcp::Size _panel_size; }; diff --git a/src/wx/gain_calculator_dialog.cc b/src/wx/gain_calculator_dialog.cc index 7499cbf8b..17ebbb983 100644 --- a/src/wx/gain_calculator_dialog.cc +++ b/src/wx/gain_calculator_dialog.cc @@ -26,7 +26,7 @@ using namespace boost; GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent) : wxDialog (parent, wxID_ANY, _("Gain Calculator")) { - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); add_label_to_sizer (table, this, _("I want to play this back at fader"), true); diff --git a/src/wx/imagemagick_content_dialog.cc b/src/wx/imagemagick_content_dialog.cc new file mode 100644 index 000000000..6aa756260 --- /dev/null +++ b/src/wx/imagemagick_content_dialog.cc @@ -0,0 +1,70 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <wx/spinctrl.h> +#include "lib/imagemagick_content.h" +#include "imagemagick_content_dialog.h" +#include "wx_util.h" + +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +ImageMagickContentDialog::ImageMagickContentDialog (wxWindow* parent, shared_ptr<ImageMagickContent> content) + : wxDialog (parent, wxID_ANY, _("Image")) + , _content (content) +{ + wxFlexGridSizer* grid = new wxFlexGridSizer (3, 6, 6); + grid->AddGrowableCol (1, 1); + + { + add_label_to_sizer (grid, this, _("Duration"), true); + wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _video_length = new wxSpinCtrl (this); + s->Add (_video_length); + /// TRANSLATORS: this is an abbreviation for seconds, the unit of time + add_label_to_sizer (s, this, _("s"), false); + grid->Add (s); + } + + wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); + overall_sizer->Add (grid, 1, wxEXPAND | wxALL, 6); + + wxSizer* buttons = CreateSeparatedButtonSizer (wxOK); + if (buttons) { + overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + SetSizer (overall_sizer); + overall_sizer->Layout (); + overall_sizer->SetSizeHints (this); + + checked_set (_video_length, content->video_length () / 24); + _video_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ImageMagickContentDialog::video_length_changed), 0, this); +} + +void +ImageMagickContentDialog::video_length_changed (wxCommandEvent &) +{ + shared_ptr<ImageMagickContent> c = _content.lock (); + if (!c) { + return; + } + + c->set_video_length (_video_length->GetValue() * 24); +} diff --git a/src/lib/audio_sink.h b/src/wx/imagemagick_content_dialog.h index 69b3a4b75..23722f183 100644 --- a/src/lib/audio_sink.h +++ b/src/wx/imagemagick_content_dialog.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013 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 @@ -17,21 +17,20 @@ */ -#ifndef DVDOMATIC_AUDIO_SINK_H -#define DVDOMATIC_AUDIO_SINK_H +#include <wx/wx.h> -class AudioSink -{ -public: - /** Call with some audio data */ - virtual void process_audio (boost::shared_ptr<const AudioBuffers>) = 0; -}; +class wxSpinCtrl; +class ImageMagickContent; +class Region; -class TimedAudioSink +class ImageMagickContentDialog : public wxDialog { public: - /** Call with some audio data */ - virtual void process_audio (boost::shared_ptr<const AudioBuffers>, double t) = 0; -}; + ImageMagickContentDialog (wxWindow *, boost::shared_ptr<ImageMagickContent>); + +private: + void video_length_changed (wxCommandEvent &); -#endif + boost::weak_ptr<ImageMagickContent> _content; + wxSpinCtrl* _video_length; +}; diff --git a/src/wx/job_manager_view.cc b/src/wx/job_manager_view.cc index cfe09aec8..b5c66bd3e 100644 --- a/src/wx/job_manager_view.cc +++ b/src/wx/job_manager_view.cc @@ -86,6 +86,7 @@ JobManagerView::update () JobRecord r; int n = 1; r.finalised = false; + r.scroll_nudged = false; r.gauge = new wxGauge (_panel, wxID_ANY, 100); _table->Insert (index + n, r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT); ++n; @@ -113,6 +114,7 @@ JobManagerView::update () ++n; _job_records[*i] = r; + } string const st = (*i)->status (); @@ -126,8 +128,25 @@ JobManagerView::update () checked_set (_job_records[*i].message, wx_to_std (_("Running"))); _job_records[*i].gauge->Pulse (); } + } + if (!_job_records[*i].scroll_nudged && ((*i)->running () || (*i)->finished())) { + int x, y; + _job_records[*i].gauge->GetPosition (&x, &y); + int px, py; + GetScrollPixelsPerUnit (&px, &py); + int vx, vy; + GetViewStart (&vx, &vy); + int sx, sy; + GetClientSize (&sx, &sy); + + if (y > (vy * py + sy / 2)) { + Scroll (-1, y / py); + _job_records[*i].scroll_nudged = true; + } + } + if ((*i)->finished() && !_job_records[*i].finalised) { checked_set (_job_records[*i].message, st); if (!(*i)->finished_cancelled()) { @@ -155,7 +174,7 @@ JobManagerView::details_clicked (wxCommandEvent& ev) { wxObject* o = ev.GetEventObject (); - for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) { + for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) { if (i->second.details == o) { string s = i->first->error_summary(); s[0] = toupper (s[0]); @@ -169,7 +188,7 @@ JobManagerView::cancel_clicked (wxCommandEvent& ev) { wxObject* o = ev.GetEventObject (); - for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) { + for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) { if (i->second.cancel == o) { i->first->cancel (); } diff --git a/src/wx/job_manager_view.h b/src/wx/job_manager_view.h index fc29eadb4..3d1ad30c0 100644 --- a/src/wx/job_manager_view.h +++ b/src/wx/job_manager_view.h @@ -57,6 +57,7 @@ private: wxButton* pause; wxButton* details; bool finalised; + bool scroll_nudged; }; std::map<boost::shared_ptr<Job>, JobRecord> _job_records; diff --git a/src/wx/new_film_dialog.cc b/src/wx/new_film_dialog.cc index 289926b8e..d4b78d5bf 100644 --- a/src/wx/new_film_dialog.cc +++ b/src/wx/new_film_dialog.cc @@ -22,7 +22,7 @@ #include "lib/config.h" #include "new_film_dialog.h" #include "wx_util.h" -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER +#ifdef DCPOMATIC_USE_OWN_DIR_PICKER #include "dir_picker_ctrl.h" #endif @@ -37,7 +37,7 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent) wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); SetSizer (overall_sizer); - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6); @@ -47,7 +47,7 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent) add_label_to_sizer (table, this, _("Create in folder"), true); -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER +#ifdef DCPOMATIC_USE_OWN_DIR_PICKER _folder = new DirPickerCtrl (this); #else _folder = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST); diff --git a/src/wx/new_film_dialog.h b/src/wx/new_film_dialog.h index 220bba732..f8f3aa08d 100644 --- a/src/wx/new_film_dialog.h +++ b/src/wx/new_film_dialog.h @@ -33,7 +33,7 @@ public: private: wxTextCtrl* _name; -#ifdef DVDOMATIC_USE_OWN_DIR_PICKER +#ifdef DCPOMATIC_USE_OWN_DIR_PICKER DirPickerCtrl* _folder; #else wxDirPickerCtrl* _folder; diff --git a/src/wx/po/es_ES.po b/src/wx/po/es_ES.po index 8bc806755..34646c2b7 100644 --- a/src/wx/po/es_ES.po +++ b/src/wx/po/es_ES.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: libdvdomatic-wx\n" +"Project-Id-Version: libdcpomatic-wx\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-04-02 19:08-0500\n" @@ -21,10 +21,6 @@ msgstr "" msgid "%" msgstr "%" -#: src/wx/config_dialog.cc:98 -msgid "(restart DVD-o-matic to see language changes)" -msgstr "" - #: src/wx/film_editor.cc:1276 msgid "1 channel" msgstr "1 canal" @@ -149,9 +145,13 @@ msgstr "Velocidad DCP" msgid "DCP Name" msgstr "Nombre DCP" -#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +#: src/wx/wx_util.cc:61 +msgid "DCP-o-matic" +msgstr "DCP-o-matic" + +#: src/wx/config_dialog.cc:44 +msgid "DCP-o-matic Preferences" +msgstr "Preferencias DCP-o-matic" #: src/wx/config_dialog.cc:46 #, fuzzy @@ -160,8 +160,8 @@ msgstr "Preferencias DVD-o-matic" #: src/wx/audio_dialog.cc:101 #, fuzzy, c-format -msgid "DVD-o-matic audio - %s" -msgstr "Audio DVD-o-matic - %1" +msgid "DCP-o-matic audio - %s" +msgstr "Audio DCP-o-matic - %1" #: src/wx/config_dialog.cc:120 msgid "Default DCI name details" diff --git a/src/wx/po/fr_FR.po b/src/wx/po/fr_FR.po index 5c0a7b63c..3f150bb0e 100644 --- a/src/wx/po/fr_FR.po +++ b/src/wx/po/fr_FR.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVD-o-matic FRENCH\n" +"Project-Id-Version: DCP-o-matic FRENCH\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-05-10 14:19+0100\n" @@ -148,9 +148,13 @@ msgstr "Cadence image du DCP" msgid "DCP Name" msgstr "Nom du DCP" -#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +#: src/wx/wx_util.cc:61 +msgid "DCP-o-matic" +msgstr "DCP-o-matic" + +#: src/wx/config_dialog.cc:44 +msgid "DCP-o-matic Preferences" +msgstr "Préférences DCP-o-matic" #: src/wx/config_dialog.cc:46 #, fuzzy @@ -159,8 +163,8 @@ msgstr "Préférences de DCP-o-matic" #: src/wx/audio_dialog.cc:101 #, c-format -msgid "DVD-o-matic audio - %s" -msgstr "Son DVD-o-matic - %s" +msgid "DCP-o-matic audio - %s" +msgstr "Son DCP-o-matic - %s" #: src/wx/config_dialog.cc:120 msgid "Default DCI name details" diff --git a/src/wx/po/it_IT.po b/src/wx/po/it_IT.po index 518bd7dde..92161172a 100644 --- a/src/wx/po/it_IT.po +++ b/src/wx/po/it_IT.po @@ -21,10 +21,9 @@ msgstr "" msgid "%" msgstr "%" -#: src/wx/config_dialog.cc:98 -#, fuzzy -msgid "(restart DVD-o-matic to see language changes)" -msgstr "(riavviare DVD-o-matic per vedere i cambiamenti di lingua)" +#: src/wx/config_dialog.cc:61 +msgid "(restart DCP-o-matic to see language changes)" +msgstr "(riavviare DCP-o-matic per vedere i cambiamenti di lingua)" #: src/wx/film_editor.cc:1276 msgid "1 channel" @@ -150,9 +149,13 @@ msgstr "Frequenza fotogrammi del DCP" msgid "DCP Name" msgstr "Nome del DCP" -#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +#: src/wx/wx_util.cc:61 +msgid "DCP-o-matic" +msgstr "DCP-o-matic" + +#: src/wx/config_dialog.cc:44 +msgid "DCP-o-matic Preferences" +msgstr "Preferenze DCP-o-matic" #: src/wx/config_dialog.cc:46 #, fuzzy @@ -161,8 +164,8 @@ msgstr "Preferenze DVD-o-matic" #: src/wx/audio_dialog.cc:101 #, c-format -msgid "DVD-o-matic audio - %s" -msgstr "Audio DVD-o-matic - %s" +msgid "DCP-o-matic audio - %s" +msgstr "Audio DCP-o-matic - %s" #: src/wx/config_dialog.cc:120 msgid "Default DCI name details" diff --git a/src/wx/po/sv_SE.po b/src/wx/po/sv_SE.po index 6eda1cf4b..02df467ca 100644 --- a/src/wx/po/sv_SE.po +++ b/src/wx/po/sv_SE.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: DVD-o-matic\n" +"Project-Id-Version: DCP-o-matic\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-06-04 23:59+0100\n" "PO-Revision-Date: 2013-04-09 10:13+0100\n" @@ -21,10 +21,9 @@ msgstr "" msgid "%" msgstr "%" -#: src/wx/config_dialog.cc:98 -#, fuzzy -msgid "(restart DVD-o-matic to see language changes)" -msgstr "(starta om DVD-o-matic för att se språkändringar)" +#: src/wx/config_dialog.cc:61 +msgid "(restart DCP-o-matic to see language changes)" +msgstr "(starta om DCP-o-matic för att se språkändringar)" #: src/wx/film_editor.cc:1276 msgid "1 channel" @@ -150,9 +149,13 @@ msgstr "DCP bildhastighet" msgid "DCP Name" msgstr "DCP Namn" -#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +#: src/wx/wx_util.cc:61 +msgid "DCP-o-matic" +msgstr "DCP-o-matic" + +#: src/wx/config_dialog.cc:44 +msgid "DCP-o-matic Preferences" +msgstr "DCP-o-matic Inställningar" #: src/wx/config_dialog.cc:46 #, fuzzy @@ -161,8 +164,8 @@ msgstr "DVD-o-matic Inställningar" #: src/wx/audio_dialog.cc:101 #, c-format -msgid "DVD-o-matic audio - %s" -msgstr "DVD-o-matic audio - %s" +msgid "DCP-o-matic audio - %s" +msgstr "DCP-o-matic audio - %s" #: src/wx/config_dialog.cc:120 msgid "Default DCI name details" diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index aa97623cd..d525fe38b 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -36,7 +36,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) : wxDialog (parent, wxID_ANY, _("Film Properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) , _film (film) { - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); add_label_to_sizer (table, this, _("Frames"), true); _frames = new wxStaticText (this, wxID_ANY, wxT ("")); @@ -50,18 +50,11 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)); table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL); - if (_film->length()) { - _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get()))); - FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate()); - int const dcp_length = _film->length().get() * frc.factor(); - double const disk = ((double) _film->j2k_bandwidth() / 8) * dcp_length / (_film->dcp_frame_rate() * 1073741824.0f); - stringstream s; - s << fixed << setprecision (1) << disk << wx_to_std (_("Gb")); - _disk->SetLabel (std_to_wx (s.str ())); - } else { - _frames->SetLabel (_("unknown")); - _disk->SetLabel (_("unknown")); - } + _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length())))); + double const disk = ((double) _film->j2k_bandwidth() / 8) * _film->length() / (TIME_HZ * 1073741824.0f); + stringstream s; + s << fixed << setprecision (1) << disk << wx_to_std (_("Gb")); + _disk->SetLabel (std_to_wx (s.str ())); wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); overall_sizer->Add (table, 0, wxALL, 6); @@ -87,7 +80,7 @@ PropertiesDialog::frames_already_encoded () const if (_film->length()) { /* XXX: encoded_frames() should check which frames have been encoded */ - u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)"; + u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)"; } return u.str (); } diff --git a/src/wx/server_dialog.cc b/src/wx/server_dialog.cc index 30e3c0f83..33cb392bf 100644 --- a/src/wx/server_dialog.cc +++ b/src/wx/server_dialog.cc @@ -30,7 +30,7 @@ ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server) _server = new ServerDescription (wx_to_std (N_("localhost")), 1); } - wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP); + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (1, 1); add_label_to_sizer (table, this, _("Host name or IP address"), true); diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc new file mode 100644 index 000000000..6ce1c1cb8 --- /dev/null +++ b/src/wx/timecode.cc @@ -0,0 +1,115 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/lexical_cast.hpp> +#include "timecode.h" +#include "wx_util.h" + +using std::string; +using std::cout; +using boost::lexical_cast; + +Timecode::Timecode (wxWindow* parent) + : wxPanel (parent) + , _in_set (false) +{ + wxClientDC dc (parent); + wxSize size = dc.GetTextExtent (wxT ("9999")); + size.SetHeight (-1); + + wxTextValidator validator (wxFILTER_INCLUDE_CHAR_LIST); + wxArrayString list; + + wxString n (wxT ("0123456789")); + for (size_t i = 0; i < n.Length(); ++i) { + list.Add (n[i]); + } + + validator.SetIncludes (list); + + wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL); + _hours = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator); + _hours->SetMaxLength (2); + sizer->Add (_hours); + add_label_to_sizer (sizer, this, wxT (":"), false); + _minutes = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size); + _minutes->SetMaxLength (2); + sizer->Add (_minutes); + add_label_to_sizer (sizer, this, wxT (":"), false); + _seconds = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size); + _seconds->SetMaxLength (2); + sizer->Add (_seconds); + add_label_to_sizer (sizer, this, wxT ("."), false); + _frames = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size); + _frames->SetMaxLength (2); + sizer->Add (_frames); + + _hours->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this); + _minutes->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this); + _seconds->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this); + _frames->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this); + + SetSizerAndFit (sizer); +} + +void +Timecode::set (Time t, int fps) +{ + _in_set = true; + + int const h = t / (3600 * TIME_HZ); + t -= h * 3600 * TIME_HZ; + int const m = t / (60 * TIME_HZ); + t -= m * 60 * TIME_HZ; + int const s = t / TIME_HZ; + t -= s * TIME_HZ; + int const f = t * fps / TIME_HZ; + + _hours->SetValue (wxString::Format (wxT ("%d"), h)); + _minutes->SetValue (wxString::Format (wxT ("%d"), m)); + _seconds->SetValue (wxString::Format (wxT ("%d"), s)); + _frames->SetValue (wxString::Format (wxT ("%d"), f)); + + _in_set = false; +} + +Time +Timecode::get (int fps) const +{ + Time t = 0; + string const h = wx_to_std (_hours->GetValue ()); + t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ; + string const m = wx_to_std (_minutes->GetValue()); + t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ; + string const s = wx_to_std (_seconds->GetValue()); + t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ; + string const f = wx_to_std (_frames->GetValue()); + t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps; + return t; +} + +void +Timecode::changed (wxCommandEvent &) +{ + if (_in_set) { + return; + } + + Changed (); +} diff --git a/src/lib/delay_line.h b/src/wx/timecode.h index 781dce88a..9b6fe6654 100644 --- a/src/lib/delay_line.h +++ b/src/wx/timecode.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013 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 @@ -17,18 +17,27 @@ */ -#include <boost/shared_ptr.hpp> -#include "processor.h" +#include <boost/signals2.hpp> +#include <wx/wx.h> +#include "lib/types.h" -/** A delay line */ -class DelayLine : public TimedAudioVideoProcessor +class Timecode : public wxPanel { public: - DelayLine (boost::shared_ptr<Log> log, double); - - void process_video (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double); - void process_audio (boost::shared_ptr<const AudioBuffers>, double); + Timecode (wxWindow *); + + void set (Time, int); + Time get (int) const; + + boost::signals2::signal<void ()> Changed; private: - double _seconds; + void changed (wxCommandEvent &); + + wxTextCtrl* _hours; + wxTextCtrl* _minutes; + wxTextCtrl* _seconds; + wxTextCtrl* _frames; + + bool _in_set; }; diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc new file mode 100644 index 000000000..113e883fc --- /dev/null +++ b/src/wx/timeline.cc @@ -0,0 +1,561 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <list> +#include <wx/graphics.h> +#include <boost/weak_ptr.hpp> +#include "film.h" +#include "film_editor.h" +#include "timeline.h" +#include "wx_util.h" +#include "lib/playlist.h" + +using std::list; +using std::cout; +using std::max; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; +using boost::bind; + +class View +{ +public: + View (Timeline& t) + : _timeline (t) + { + + } + + void paint (wxGraphicsContext* g) + { + _last_paint_bbox = bbox (); + do_paint (g); + } + + void force_redraw () + { + _timeline.force_redraw (_last_paint_bbox); + _timeline.force_redraw (bbox ()); + } + + virtual dcpomatic::Rect bbox () const = 0; + +protected: + virtual void do_paint (wxGraphicsContext *) = 0; + + int time_x (Time t) const + { + return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit(); + } + + Timeline& _timeline; + +private: + dcpomatic::Rect _last_paint_bbox; +}; + +class ContentView : public View +{ +public: + ContentView (Timeline& tl, shared_ptr<Content> c, int t) + : View (tl) + , _content (c) + , _track (t) + , _selected (false) + { + _content_connection = c->Changed.connect (bind (&ContentView::content_changed, this, _2)); + } + + dcpomatic::Rect bbox () const + { + shared_ptr<const Film> film = _timeline.film (); + shared_ptr<const Content> content = _content.lock (); + if (!film || !content) { + return dcpomatic::Rect (); + } + + return dcpomatic::Rect ( + time_x (content->start ()) - 8, + y_pos (_track) - 8, + content->length () * _timeline.pixels_per_time_unit() + 16, + _timeline.track_height() + 16 + ); + } + + void set_selected (bool s) { + _selected = s; + force_redraw (); + } + + bool selected () const { + return _selected; + } + + weak_ptr<Content> content () const { + return _content; + } + + void set_track (int t) { + _track = t; + } + + int track () const { + return _track; + } + + virtual wxString type () const = 0; + virtual wxColour colour () const = 0; + +private: + + void do_paint (wxGraphicsContext* gc) + { + shared_ptr<const Film> film = _timeline.film (); + shared_ptr<const Content> content = _content.lock (); + if (!film || !content) { + return; + } + + Time const start = content->start (); + Time const len = content->length (); + + wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2); + + gc->SetPen (*wxBLACK_PEN); + +#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9 + gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID)); + if (_selected) { + gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxBRUSHSTYLE_SOLID)); + } else { + gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxBRUSHSTYLE_SOLID)); + } +#else + gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxSOLID)); + if (_selected) { + gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxSOLID)); + } else { + gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxSOLID)); + } +#endif + + wxGraphicsPath path = gc->CreatePath (); + path.MoveToPoint (time_x (start), y_pos (_track) + 4); + path.AddLineToPoint (time_x (start + len), y_pos (_track) + 4); + path.AddLineToPoint (time_x (start + len), y_pos (_track + 1) - 4); + path.AddLineToPoint (time_x (start), y_pos (_track + 1) - 4); + path.AddLineToPoint (time_x (start), y_pos (_track) + 4); + gc->StrokePath (path); + gc->FillPath (path); + + wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (content->file().filename().string()).data(), type().data()); + wxDouble name_width; + wxDouble name_height; + wxDouble name_descent; + wxDouble name_leading; + gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading); + + gc->Clip (wxRegion (time_x (start), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height())); + gc->DrawText (name, time_x (start) + 12, y_pos (_track + 1) - name_height - 4); + gc->ResetClip (); + } + + int y_pos (int t) const + { + return _timeline.tracks_position().y + t * _timeline.track_height(); + } + + void content_changed (int p) + { + if (p == ContentProperty::START || p == ContentProperty::LENGTH) { + force_redraw (); + } + } + + boost::weak_ptr<Content> _content; + int _track; + bool _selected; + + boost::signals2::scoped_connection _content_connection; +}; + +class AudioContentView : public ContentView +{ +public: + AudioContentView (Timeline& tl, shared_ptr<Content> c, int t) + : ContentView (tl, c, t) + {} + +private: + wxString type () const + { + return _("audio"); + } + + wxColour colour () const + { + return wxColour (149, 121, 232, 255); + } +}; + +class VideoContentView : public ContentView +{ +public: + VideoContentView (Timeline& tl, shared_ptr<Content> c, int t) + : ContentView (tl, c, t) + {} + +private: + + wxString type () const + { + return _("video"); + } + + wxColour colour () const + { + return wxColour (242, 92, 120, 255); + } +}; + +class TimeAxisView : public View +{ +public: + TimeAxisView (Timeline& tl, int y) + : View (tl) + , _y (y) + {} + + dcpomatic::Rect bbox () const + { + return dcpomatic::Rect (0, _y - 4, _timeline.width(), 24); + } + + void set_y (int y) + { + _y = y; + force_redraw (); + } + +private: + + void do_paint (wxGraphicsContext* gc) + { +#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9 + gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID)); +#else + gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxSOLID)); +#endif + + int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ())); + if (mark_interval > 5) { + mark_interval -= mark_interval % 5; + } + if (mark_interval > 10) { + mark_interval -= mark_interval % 10; + } + if (mark_interval > 60) { + mark_interval -= mark_interval % 60; + } + if (mark_interval > 3600) { + mark_interval -= mark_interval % 3600; + } + + if (mark_interval < 1) { + mark_interval = 1; + } + + wxGraphicsPath path = gc->CreatePath (); + path.MoveToPoint (_timeline.x_offset(), _y); + path.AddLineToPoint (_timeline.width(), _y); + gc->StrokePath (path); + + Time t = 0; + while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) { + wxGraphicsPath path = gc->CreatePath (); + path.MoveToPoint (time_x (t), _y - 4); + path.AddLineToPoint (time_x (t), _y + 4); + gc->StrokePath (path); + + int tc = t / TIME_HZ; + int const h = tc / 3600; + tc -= h * 3600; + int const m = tc / 60; + tc -= m * 60; + int const s = tc; + + wxString str = wxString::Format (wxT ("%02d:%02d:%02d"), h, m, s); + wxDouble str_width; + wxDouble str_height; + wxDouble str_descent; + wxDouble str_leading; + gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading); + + int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit(); + if ((tx + str_width) < _timeline.width()) { + gc->DrawText (str, time_x (t), _y + 16); + } + + t += mark_interval * TIME_HZ; + } + } + +private: + int _y; +}; + +Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film) + : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) + , _film_editor (ed) + , _film (film) + , _time_axis_view (new TimeAxisView (*this, 32)) + , _tracks (0) + , _pixels_per_time_unit (0) + , _left_down (false) + , _down_view_start (0) + , _first_move (false) +{ + SetDoubleBuffered (true); + + setup_pixels_per_time_unit (); + + Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this); + Connect (wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler (Timeline::left_down), 0, this); + Connect (wxID_ANY, wxEVT_LEFT_UP, wxMouseEventHandler (Timeline::left_up), 0, this); + Connect (wxID_ANY, wxEVT_MOTION, wxMouseEventHandler (Timeline::mouse_moved), 0, this); + Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Timeline::resized), 0, this); + + playlist_changed (); + + SetMinSize (wxSize (640, tracks() * track_height() + 96)); + + _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this)); + + _views.push_back (_time_axis_view); +} + +void +Timeline::paint (wxPaintEvent &) +{ + wxPaintDC dc (this); + + wxGraphicsContext* gc = wxGraphicsContext::Create (dc); + if (!gc) { + return; + } + + gc->SetFont (gc->CreateFont (*wxNORMAL_FONT)); + + for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) { + (*i)->paint (gc); + } + + delete gc; +} + +void +Timeline::playlist_changed () +{ + shared_ptr<const Film> fl = _film.lock (); + if (!fl) { + return; + } + + _views.clear (); + + Playlist::ContentList content = fl->playlist()->content (); + + for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) { + if (dynamic_pointer_cast<VideoContent> (*i)) { + _views.push_back (shared_ptr<View> (new VideoContentView (*this, *i, 0))); + } + if (dynamic_pointer_cast<AudioContent> (*i)) { + _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i, 0))); + } + } + + assign_tracks (); + Refresh (); +} + +void +Timeline::assign_tracks () +{ + for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) { + shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i); + if (cv) { + cv->set_track (0); + _tracks = 1; + } + } + + for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) { + shared_ptr<AudioContentView> acv = dynamic_pointer_cast<AudioContentView> (*i); + if (!acv) { + continue; + } + + shared_ptr<Content> acv_content = acv->content().lock (); + assert (acv_content); + + int t = 1; + while (1) { + list<shared_ptr<View> >::iterator j = _views.begin(); + while (j != _views.end()) { + shared_ptr<AudioContentView> test = dynamic_pointer_cast<AudioContentView> (*j); + if (!test) { + ++j; + continue; + } + + shared_ptr<Content> test_content = test->content().lock (); + assert (test_content); + + if (test && test->track() == t) { + if ((acv_content->start() <= test_content->start() && test_content->start() <= acv_content->end()) || + (acv_content->start() <= test_content->end() && test_content->end() <= acv_content->end())) { + /* we have an overlap on track `t' */ + ++t; + break; + } + } + + ++j; + } + + if (j == _views.end ()) { + /* no overlap on `t' */ + break; + } + } + + acv->set_track (t); + _tracks = max (_tracks, t + 1); + } + + _time_axis_view->set_y (tracks() * track_height() + 32); +} + +int +Timeline::tracks () const +{ + return _tracks; +} + +void +Timeline::setup_pixels_per_time_unit () +{ + shared_ptr<const Film> film = _film.lock (); + if (!film) { + return; + } + + _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length(); +} + +void +Timeline::left_down (wxMouseEvent& ev) +{ + list<shared_ptr<View> >::iterator i = _views.begin(); + Position const p (ev.GetX(), ev.GetY()); + while (i != _views.end() && !(*i)->bbox().contains (p)) { + ++i; + } + + _down_view.reset (); + + if (i != _views.end ()) { + shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i); + if (cv) { + _down_view = cv; + shared_ptr<Content> c = cv->content().lock(); + assert (c); + _down_view_start = c->start (); + } + } + + for (list<shared_ptr<View> >::iterator j = _views.begin(); j != _views.end(); ++j) { + shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*j); + if (cv) { + cv->set_selected (i == j); + if (i == j) { + _film_editor->set_selection (cv->content ()); + } + } + } + + _left_down = true; + _down_point = ev.GetPosition (); + _first_move = false; +} + +void +Timeline::left_up (wxMouseEvent &) +{ + _left_down = false; +} + +void +Timeline::mouse_moved (wxMouseEvent& ev) +{ + if (!_left_down) { + return; + } + + wxPoint const p = ev.GetPosition(); + + if (!_first_move) { + int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2)); + if (dist < 8) { + return; + } + _first_move = true; + } + + Time const time_diff = (p.x - _down_point.x) / _pixels_per_time_unit; + if (_down_view) { + shared_ptr<Content> c = _down_view->content().lock(); + if (c) { + c->set_start (max (static_cast<Time> (0), _down_view_start + time_diff)); + + shared_ptr<Film> film = _film.lock (); + assert (film); + film->set_sequence_video (false); + } + } +} + +void +Timeline::force_redraw (dcpomatic::Rect const & r) +{ + RefreshRect (wxRect (r.x, r.y, r.width, r.height), false); +} + +shared_ptr<const Film> +Timeline::film () const +{ + return _film.lock (); +} + +void +Timeline::resized (wxSizeEvent &) +{ + setup_pixels_per_time_unit (); +} diff --git a/src/wx/timeline.h b/src/wx/timeline.h new file mode 100644 index 000000000..5c25a6426 --- /dev/null +++ b/src/wx/timeline.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/signals2.hpp> +#include <wx/wx.h> +#include "util.h" + +class Film; +class View; +class ContentView; +class FilmEditor; +class TimeAxisView; + +class Timeline : public wxPanel +{ +public: + Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>); + + boost::shared_ptr<const Film> film () const; + + void force_redraw (dcpomatic::Rect const &); + + int x_offset () const { + return 8; + } + + int width () const { + return GetSize().GetWidth (); + } + + int track_height () const { + return 48; + } + + double pixels_per_time_unit () const { + return _pixels_per_time_unit; + } + + Position tracks_position () const { + return Position (8, 8); + } + + int tracks () const; + +private: + void paint (wxPaintEvent &); + void left_down (wxMouseEvent &); + void mouse_moved (wxMouseEvent &); + void left_up (wxMouseEvent &); + void playlist_changed (); + void setup_pixels_per_time_unit (); + void resized (wxSizeEvent &); + void assign_tracks (); + + FilmEditor* _film_editor; + boost::weak_ptr<Film> _film; + std::list<boost::shared_ptr<View> > _views; + boost::shared_ptr<TimeAxisView> _time_axis_view; + int _tracks; + double _pixels_per_time_unit; + bool _left_down; + wxPoint _down_point; + boost::shared_ptr<ContentView> _down_view; + Time _down_view_start; + bool _first_move; + + boost::signals2::scoped_connection _playlist_connection; +}; diff --git a/src/lib/video_source.cc b/src/wx/timeline_dialog.cc index 539243402..35d5eec21 100644 --- a/src/lib/video_source.cc +++ b/src/wx/timeline_dialog.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013 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 @@ -17,28 +17,26 @@ */ -#include "video_source.h" -#include "video_sink.h" +#include <list> +#include <wx/graphics.h> +#include "film_editor.h" +#include "timeline_dialog.h" +#include "wx_util.h" +#include "playlist.h" +using std::list; +using std::cout; using boost::shared_ptr; -using boost::bind; -void -VideoSource::connect_video (shared_ptr<VideoSink> s) +TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film) + : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) + , _timeline (this, ed, film) { - Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3)); -} - -void -TimedVideoSource::connect_video (shared_ptr<TimedVideoSink> s) -{ - Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4)); -} + wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); + + sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12); -void -TimedVideoSource::connect_video (shared_ptr<VideoSink> s) -{ - Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3)); + SetSizer (sizer); + sizer->Layout (); + sizer->SetSizeHints (this); } - - diff --git a/src/lib/gain.h b/src/wx/timeline_dialog.h index 61fef5e85..17ca22c49 100644 --- a/src/lib/gain.h +++ b/src/wx/timeline_dialog.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013 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 @@ -17,15 +17,18 @@ */ -#include "processor.h" +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <wx/wx.h> +#include "timeline.h" -class Gain : public AudioProcessor +class Playlist; + +class TimelineDialog : public wxDialog { public: - Gain (boost::shared_ptr<Log> log, float gain); - - void process_audio (boost::shared_ptr<const AudioBuffers>); + TimelineDialog (FilmEditor *, boost::shared_ptr<Film>); private: - float _gain; + Timeline _timeline; }; diff --git a/src/wx/wscript b/src/wx/wscript index 345c02b08..1205fb21b 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -6,6 +6,7 @@ import i18n sources = """ about_dialog.cc audio_dialog.cc + audio_mapping_view.cc audio_plot.cc config_dialog.cc dci_metadata_dialog.cc @@ -15,11 +16,15 @@ sources = """ filter_dialog.cc filter_view.cc gain_calculator_dialog.cc + imagemagick_content_dialog.cc job_manager_view.cc job_wrapper.cc new_film_dialog.cc properties_dialog.cc server_dialog.cc + timecode.cc + timeline.cc + timeline_dialog.cc wx_util.cc wx_ui_signaller.cc """ @@ -47,20 +52,20 @@ def build(bld): else: obj = bld(features = 'cxx cxxshlib') - obj.name = 'libdvdomatic-wx' + obj.name = 'libdcpomatic-wx' obj.includes = [ '..' ] obj.export_includes = ['.'] obj.uselib = 'WXWIDGETS' if bld.env.TARGET_LINUX: obj.uselib += ' GTK' - obj.use = 'libdvdomatic' + obj.use = 'libdcpomatic' obj.source = sources - obj.target = 'dvdomatic-wx' + obj.target = 'dcpomatic-wx' - i18n.po_to_mo(os.path.join('src', 'wx'), 'libdvdomatic-wx', bld) + i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld) def pot(bld): - i18n.pot(os.path.join('src', 'wx'), sources, 'libdvdomatic-wx') + i18n.pot(os.path.join('src', 'wx'), sources, 'libdcpomatic-wx') def pot_merge(bld): - i18n.pot_merge(os.path.join('src', 'wx'), 'libdvdomatic-wx') + i18n.pot_merge(os.path.join('src', 'wx'), 'libdcpomatic-wx') diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index b9e78932a..c5887e17d 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -76,7 +76,7 @@ add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool le void error_dialog (wxWindow* parent, wxString m) { - wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxOK); + wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK); d->ShowModal (); d->Destroy (); } @@ -84,7 +84,7 @@ error_dialog (wxWindow* parent, wxString m) bool confirm_dialog (wxWindow* parent, wxString m) { - wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxYES_NO | wxICON_QUESTION); + wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION); int const r = d->ShowModal (); d->Destroy (); return r == wxID_YES; @@ -231,7 +231,7 @@ checked_set (wxRadioButton* widget, bool value) } void -dvdomatic_setup_i18n () +dcpomatic_setup_i18n () { int language = wxLANGUAGE_DEFAULT; @@ -247,12 +247,12 @@ dvdomatic_setup_i18n () if (wxLocale::IsAvailable (language)) { locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT); -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string())); #endif - locale->AddCatalog (wxT ("libdvdomatic-wx")); - locale->AddCatalog (wxT ("dvdomatic")); + locale->AddCatalog (wxT ("libdcpomatic-wx")); + locale->AddCatalog (wxT ("dcpomatic")); if (!locale->IsOk()) { delete locale; @@ -262,6 +262,6 @@ dvdomatic_setup_i18n () } if (locale) { - dvdomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ())); + dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ())); } } diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h index 18a9f251c..de6a09c35 100644 --- a/src/wx/wx_util.h +++ b/src/wx/wx_util.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_WX_UTIL_H -#define DVDOMATIC_WX_UTIL_H +#ifndef DCPOMATIC_WX_UTIL_H +#define DCPOMATIC_WX_UTIL_H #include <wx/wx.h> #include <wx/gbsizer.h> @@ -32,8 +32,8 @@ class wxFilePickerCtrl; class wxSpinCtrl; class wxGridBagSizer; -#define DVDOMATIC_SIZER_X_GAP 8 -#define DVDOMATIC_SIZER_Y_GAP 8 +#define DCPOMATIC_SIZER_X_GAP 8 +#define DCPOMATIC_SIZER_Y_GAP 8 /** @file src/wx/wx_util.h * @brief Some utility functions and classes. @@ -45,7 +45,7 @@ extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, bool l extern wxStaticText* add_label_to_grid_bag_sizer (wxGridBagSizer *, wxWindow *, wxString, bool, wxGBPosition, wxGBSpan span = wxDefaultSpan); extern std::string wx_to_std (wxString); extern wxString std_to_wx (std::string); -extern void dvdomatic_setup_i18n (); +extern void dcpomatic_setup_i18n (); /** @class ThreadedStaticText * @@ -83,7 +83,7 @@ extern void checked_set (wxStaticText* widget, std::string value); Use our own dir picker as this is the least bad option I can think of. */ #if defined(__WXMSW__) || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION == 24 && GTK_MICRO_VERSION == 17) -#define DVDOMATIC_USE_OWN_DIR_PICKER +#define DCPOMATIC_USE_OWN_DIR_PICKER #endif #endif |
