diff options
Diffstat (limited to 'src')
126 files changed, 4531 insertions, 2999 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc index 4ffdd9af6..2bdff47de 100644 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@ -32,11 +32,9 @@ using std::string; using boost::shared_ptr; /** @param f Film to compare. - * @param o Decode options. */ -ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o) +ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f) : Job (f) - , _decode_opt (o) { _film_b.reset (new Film (*_film)); _film_b->set_scaler (Config::instance()->reference_scaler ()); @@ -54,7 +52,7 @@ ABTranscodeJob::run () { try { /* _film_b is the one with reference filters */ - ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film))); + ABTranscoder w (_film_b, _film, shared_from_this ()); w.go (); set_progress (1); set_state (FINISHED_OK); diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h index 8e3cbe2d8..cd82d4247 100644 --- a/src/lib/ab_transcode_job.h +++ b/src/lib/ab_transcode_job.h @@ -23,7 +23,6 @@ #include <boost/shared_ptr.hpp> #include "job.h" -#include "options.h" class Film; @@ -38,16 +37,13 @@ class ABTranscodeJob : public Job { public: ABTranscodeJob ( - boost::shared_ptr<Film> f, - DecodeOptions o + boost::shared_ptr<Film> f ); 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 index d8f13dae4..2e0d41e7d 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -21,13 +21,10 @@ #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 "player.h" #include "matcher.h" #include "delay_line.h" #include "gain.h" @@ -50,58 +47,38 @@ using boost::dynamic_pointer_cast; * @param e Encoder to use. */ -ABTranscoder::ABTranscoder ( - shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e) +ABTranscoder::ABTranscoder (shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<Job> j) : _film_a (a) , _film_b (b) + , _player_a (_film_a->player ()) + , _player_b (_film_b->player ()) , _job (j) - , _encoder (e) + , _encoder (new Encoder (_film_a)) , _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) { - _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)); + _matcher.reset (new Matcher (_film_a->log(), _film_a->audio_frame_rate(), _film_a->video_frame_rate())); + _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() * _film_a->audio_frame_rate() / 1000)); _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain())); - int const sr = st ? st->sample_rate() : 0; + _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4)); + _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4)); + 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() + _film_a->log(), trim_start, trim_end, _film_a->content_length(), + _film_a->audio_frame_rate(), _film_a->video_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); - } + _delay_line->connect_video (_matcher); + _matcher->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); - } + _player_a->connect_audio (_delay_line); + _delay_line->connect_audio (_matcher); + _matcher->connect_audio (_gain); _gain->connect_audio (_trimmer); _trimmer->connect_audio (_encoder); } @@ -111,32 +88,25 @@ ABTranscoder::go () { _encoder->process_begin (); - bool done[3] = { false, false, false }; + bool done[2] = { 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; - } - + done[0] = _player_a->pass (); + done[1] = _player_b->pass (); + if (_job) { - _da.video->set_progress (_job); + _player_a->set_progress (_job); } - - if (done[0] && done[1] && done[2]) { + + if (done[0] && done[1]) { break; } } _delay_line->process_end (); - if (_matcher) { - _matcher->process_end (); - } + _matcher->process_end (); _gain->process_end (); + _trimmer->process_end (); _encoder->process_end (); } diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h index 4f1b14e48..1fef66b88 100644 --- a/src/lib/ab_transcoder.h +++ b/src/lib/ab_transcoder.h @@ -25,20 +25,17 @@ #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 Player; class Trimmer; /** @class ABTranscoder @@ -51,9 +48,7 @@ public: ABTranscoder ( boost::shared_ptr<Film> a, boost::shared_ptr<Film> b, - DecodeOptions o, - Job* j, - boost::shared_ptr<Encoder> e + boost::shared_ptr<Job> j ); void go (); @@ -61,10 +56,10 @@ public: private: boost::shared_ptr<Film> _film_a; boost::shared_ptr<Film> _film_b; - Job* _job; + boost::shared_ptr<Player> _player_a; + boost::shared_ptr<Player> _player_b; + boost::shared_ptr<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; diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 88cd65fee..f3c55b208 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" @@ -52,29 +50,18 @@ AnalyseAudioJob::name () const void AnalyseAudioJob::run () { - if (!_film->audio_stream () || !_film->length()) { - set_progress (1); - set_state (FINISHED_ERROR); - return; - } - - DecodeOptions options; - options.decode_video = false; - - Decoders decoders = decoder_factory (_film, options); - assert (decoders.audio); + shared_ptr<Player> player = _film->player (); + 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)); - 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->audio_length() / _num_points); - _current.resize (_film->audio_stream()->channels ()); - _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels())); + _current.resize (MAX_AUDIO_CHANNELS); + _analysis.reset (new AudioAnalysis (MAX_AUDIO_CHANNELS)); - while (!decoders.audio->pass()) { - set_progress (float (_done) / total_audio_frames); + while (!player->pass()) { + set_progress (float (_done) / _film->audio_length ()); } _analysis->write (_film->audio_analysis_path ()); diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h index 6e0e2b78a..ec6905105 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_AUDIO_ANALYSIS_H -#define DVDOMATIC_AUDIO_ANALYSIS_H +#ifndef DCPOMATIC_AUDIO_ANALYSIS_H +#define DCPOMATIC_AUDIO_ANALYSIS_H #include <iostream> #include <vector> diff --git a/src/lib/decoder_factory.h b/src/lib/audio_content.cc index 8076b01c7..9968f4725 100644 --- a/src/lib/decoder_factory.h +++ b/src/lib/audio_content.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,33 +17,29 @@ */ -#ifndef DVDOMATIC_DECODER_FACTORY_H -#define DVDOMATIC_DECODER_FACTORY_H +#include <libcxml/cxml.h> +#include "audio_content.h" -/** @file src/decoder_factory.h - * @brief A method to create appropriate decoders for some content. - */ +using boost::shared_ptr; -#include "options.h" +int const AudioContentProperty::AUDIO_CHANNELS = 200; +int const AudioContentProperty::AUDIO_LENGTH = 201; +int const AudioContentProperty::AUDIO_FRAME_RATE = 202; -class Film; -class VideoDecoder; -class AudioDecoder; +AudioContent::AudioContent (boost::filesystem::path f) + : Content (f) +{ -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; -}; +AudioContent::AudioContent (shared_ptr<const cxml::Node> node) + : Content (node) +{ -extern Decoders decoder_factory ( - boost::shared_ptr<Film>, DecodeOptions - ); +} -#endif +AudioContent::AudioContent (AudioContent const & o) + : Content (o) +{ + +} diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h new file mode 100644 index 000000000..2362786d9 --- /dev/null +++ b/src/lib/audio_content.h @@ -0,0 +1,50 @@ +/* + 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 "util.h" + +namespace cxml { + class Node; +} + +class AudioContentProperty +{ +public: + static int const AUDIO_CHANNELS; + static int const AUDIO_LENGTH; + static int const AUDIO_FRAME_RATE; +}; + +class AudioContent : public virtual Content +{ +public: + AudioContent (boost::filesystem::path); + AudioContent (boost::shared_ptr<const cxml::Node>); + AudioContent (AudioContent const &); + + virtual int audio_channels () const = 0; + virtual ContentAudioFrame audio_length () const = 0; + virtual int audio_frame_rate () const = 0; +}; + +#endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index a54c14843..df13a984a 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -18,19 +18,12 @@ */ #include "audio_decoder.h" -#include "stream.h" 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) { } - -void -AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s) -{ - _audio_stream = s; -} diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index cfe94b528..c393e95f1 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -21,38 +21,21 @@ * @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" +class AudioContent; + /** @class AudioDecoder. * @brief Parent class for audio decoders. */ class AudioDecoder : public TimedAudioSource, public virtual Decoder { public: - AudioDecoder (boost::shared_ptr<Film>, DecodeOptions); - - virtual void set_audio_stream (boost::shared_ptr<AudioStream>); - - /** @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; - } - -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; + AudioDecoder (boost::shared_ptr<const Film>); }; #endif diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc new file mode 100644 index 000000000..83c748f1a --- /dev/null +++ b/src/lib/audio_mapping.cc @@ -0,0 +1,131 @@ +/* + 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 <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; + +void +AudioMapping::add (Channel c, libdcp::Channel d) +{ + _content_to_dcp.push_back (make_pair (c, d)); +} + +/* XXX: this is grotty */ +int +AudioMapping::dcp_channels () const +{ + for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + if (((int) i->second) >= 2) { + return 6; + } + } + + return 2; +} + +list<AudioMapping::Channel> +AudioMapping::dcp_to_content (libdcp::Channel d) const +{ + list<AudioMapping::Channel> c; + for (list<pair<Channel, 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<AudioMapping::Channel> +AudioMapping::content_channels () const +{ + list<AudioMapping::Channel> c; + for (list<pair<Channel, 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 (Channel c) const +{ + list<libdcp::Channel> d; + for (list<pair<Channel, 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<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + xmlpp::Node* t = node->add_child ("Map"); + shared_ptr<const AudioContent> c = i->first.content.lock (); + t->add_child ("Content")->add_child_text (c->digest ()); + t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first.index)); + t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second)); + } +} + +void +AudioMapping::set_from_xml (ContentList const & content, 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) { + string const c = (*i)->string_child ("Content"); + ContentList::const_iterator j = content.begin (); + while (j != content.end() && (*j)->digest() != c) { + ++j; + } + + if (j == content.end ()) { + continue; + } + + shared_ptr<const AudioContent> ac = dynamic_pointer_cast<AudioContent> (*j); + assert (ac); + + add (AudioMapping::Channel (ac, (*i)->number_child<int> ("ContentIndex")), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP"))); + } +} + +bool +operator== (AudioMapping::Channel const & a, AudioMapping::Channel const & b) +{ + shared_ptr<const AudioContent> sa = a.content.lock (); + shared_ptr<const AudioContent> sb = b.content.lock (); + return sa == sb && a.index == b.index; +} diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h new file mode 100644 index 000000000..248d2570e --- /dev/null +++ b/src/lib/audio_mapping.h @@ -0,0 +1,62 @@ +/* + 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 <string> +#include <libdcp/types.h> +#include <boost/shared_ptr.hpp> +#include "audio_content.h" + +class AudioMapping +{ +public: + void as_xml (xmlpp::Node *) const; + void set_from_xml (ContentList const &, boost::shared_ptr<const cxml::Node>); + + struct Channel { + Channel (boost::weak_ptr<const AudioContent> c, int i) + : content (c) + , index (i) + {} + + boost::weak_ptr<const AudioContent> content; + int index; + }; + + void add (Channel, libdcp::Channel); + + int dcp_channels () const; + std::list<Channel> dcp_to_content (libdcp::Channel) const; + std::list<std::pair<Channel, libdcp::Channel> > content_to_dcp () const { + return _content_to_dcp; + } + + std::list<Channel> content_channels () const; + std::list<libdcp::Channel> content_to_dcp (Channel) const; + +private: + std::list<std::pair<Channel, libdcp::Channel> > _content_to_dcp; +}; + +extern bool operator== (AudioMapping::Channel const &, AudioMapping::Channel const &); + +#endif diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h index 69b3a4b75..ee39f9ee7 100644 --- a/src/lib/audio_sink.h +++ b/src/lib/audio_sink.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_AUDIO_SINK_H -#define DVDOMATIC_AUDIO_SINK_H +#ifndef DCPOMATIC_AUDIO_SINK_H +#define DCPOMATIC_AUDIO_SINK_H class AudioSink { diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc index d77e89367..32b3deccf 100644 --- a/src/lib/audio_source.cc +++ b/src/lib/audio_source.cc @@ -21,12 +21,22 @@ #include "audio_sink.h" using boost::shared_ptr; +using boost::weak_ptr; using boost::bind; +static void +process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<const AudioBuffers> audio) +{ + shared_ptr<AudioSink> p = sink.lock (); + if (p) { + p->process_audio (audio); + } +} + void AudioSource::connect_audio (shared_ptr<AudioSink> s) { - Audio.connect (bind (&AudioSink::process_audio, s, _1)); + Audio.connect (bind (process_audio_proxy, weak_ptr<AudioSink> (s), _1)); } void diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h index c13f1636b..c7f0a09ed 100644 --- a/src/lib/audio_source.h +++ b/src/lib/audio_source.h @@ -21,8 +21,8 @@ * @brief Parent class for classes which emit audio data. */ -#ifndef DVDOMATIC_AUDIO_SOURCE_H -#define DVDOMATIC_AUDIO_SOURCE_H +#ifndef DCPOMATIC_AUDIO_SOURCE_H +#define DCPOMATIC_AUDIO_SOURCE_H #include <boost/signals2.hpp> diff --git a/src/lib/config.cc b/src/lib/config.cc index 5dce3748d..e6f657a39 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -22,6 +22,7 @@ #include <fstream> #include <glib.h> #include <boost/filesystem.hpp> +#include <libcxml/cxml.h> #include "config.h" #include "server.h" #include "scaler.h" @@ -34,7 +35,9 @@ using std::vector; using std::ifstream; using std::string; using std::ofstream; +using std::list; using boost::shared_ptr; +using boost::optional; Config* Config::_instance = 0; @@ -52,8 +55,51 @@ Config::Config () _allowed_dcp_frame_rates.push_back (48); _allowed_dcp_frame_rates.push_back (50); _allowed_dcp_frame_rates.push_back (60); + + 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"); + c = f.optional_string_child ("ReferenceScaler"); + if (c) { + _reference_scaler = Scaler::from_id (c.get ()); + } + + list<shared_ptr<cxml::Node> > filters = f.node_children ("ReferenceFilter"); + for (list<shared_ptr<cxml::Node> >::iterator i = filters.begin(); i != filters.end(); ++i) { + _reference_filters.push_back (Filter::from_id ((*i)->content ())); + } - 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"); + _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); +} + +void +Config::read_old_metadata () +{ + ifstream f (file(true).c_str ()); string line; while (getline (f, line)) { if (line.empty ()) { @@ -98,17 +144,21 @@ Config::Config () _language = 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 (); - p /= N_(".dvdomatic"); + if (old) { + p /= ".dvdomatic"; + } else { + p /= ".dcpomatic.xml"; + } return p.string (); } @@ -127,35 +177,38 @@ Config::instance () void Config::write () const { - ofstream f (file().c_str ()); - f << N_("num_local_encoding_threads ") << _num_local_encoding_threads << N_("\n") - << N_("default_directory ") << _default_directory << N_("\n") - << N_("server_port ") << _server_port << N_("\n"); + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("Config"); + root->add_child("NumLocalEncodingThreads")->add_child_text (boost::lexical_cast<string> (_num_local_encoding_threads)); + root->add_child("DefaultDirectory")->add_child_text (_default_directory); + root->add_child("ServerPort")->add_child_text (boost::lexical_cast<string> (_server_port)); if (_reference_scaler) { - f << "reference_scaler " << _reference_scaler->id () << "\n"; + root->add_child("ReferenceScaler")->add_child_text (_reference_scaler->id ()); } for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) { - f << N_("reference_filter ") << (*i)->id () << N_("\n"); + root->add_child("ReferenceFilter")->add_child_text ((*i)->id ()); } for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) { - f << N_("server ") << (*i)->as_metadata () << N_("\n"); + (*i)->as_xml (root->add_child ("Server")); } - f << N_("tms_ip ") << _tms_ip << N_("\n"); - f << N_("tms_path ") << _tms_path << N_("\n"); - f << N_("tms_user ") << _tms_user << N_("\n"); - f << N_("tms_password ") << _tms_password << N_("\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()); } - _default_dci_metadata.write (f); + _default_dci_metadata.as_xml (root->add_child ("DCIMetadata")); + + doc.write_to_file_formatted (file (false)); } string diff --git a/src/lib/config.h b/src/lib/config.h index 011ca716f..57f4fb8a9 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> @@ -177,7 +177,8 @@ public: private: Config (); - std::string file () const; + std::string file (bool) const; + void read_old_metadata (); /** number of threads to use for J2K encoding on the local machine */ int _num_local_encoding_threads; diff --git a/src/lib/content.cc b/src/lib/content.cc new file mode 100644 index 000000000..9c3bcd39d --- /dev/null +++ b/src/lib/content.cc @@ -0,0 +1,69 @@ +/* + 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; + +Content::Content (boost::filesystem::path f) + : _file (f) +{ + +} + +Content::Content (shared_ptr<const cxml::Node> node) +{ + _file = node->string_child ("File"); + _digest = node->string_child ("Digest"); +} + +Content::Content (Content const & o) + : boost::enable_shared_from_this<Content> (o) + , _file (o._file) + , _digest (o._digest) +{ + +} + +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); +} + +void +Content::examine (shared_ptr<Film>, shared_ptr<Job>, bool) +{ + 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); +} diff --git a/src/lib/content.h b/src/lib/content.h new file mode 100644 index 000000000..d39fc9e1a --- /dev/null +++ b/src/lib/content.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_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> + +namespace cxml { + class Node; +} + +class Job; +class Film; + +class Content : public boost::enable_shared_from_this<Content> +{ +public: + Content (boost::filesystem::path); + Content (boost::shared_ptr<const cxml::Node>); + Content (Content const &); + + virtual void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool); + 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; + + 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; + } + + boost::signals2::signal<void (boost::weak_ptr<Content>, int)> Changed; + +protected: + void signal_changed (int); + + mutable boost::mutex _mutex; + +private: + boost::filesystem::path _file; + std::string _digest; +}; + +#endif diff --git a/src/lib/cross.cc b/src/lib/cross.cc index 2c66ab53a..f232f1779 100644 --- a/src/lib/cross.cc +++ b/src/lib/cross.cc @@ -18,20 +18,20 @@ */ #include "cross.h" -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX #include <unistd.h> #endif -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS #include "windows.h" #endif 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 } diff --git a/src/lib/cross.h b/src/lib/cross.h index 110660b16..00457c968 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -17,8 +17,8 @@ */ -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS #define WEXITSTATUS(w) (w) #endif -void dvdomatic_sleep (int); +void dcpomatic_sleep (int); 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 d674393a9..da51665d1 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -47,7 +47,6 @@ #include "dcp_video_frame.h" #include "lut.h" #include "config.h" -#include "options.h" #include "exceptions.h" #include "server.h" #include "util.h" @@ -267,7 +266,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 */ diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 52b22fa06..082ad5076 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -22,41 +22,26 @@ */ #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. +/** Seek to a position as a content timestamp in seconds. * @return true on error. */ bool @@ -64,12 +49,3 @@ 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..02ccaa42b 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -21,8 +21,8 @@ * @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> @@ -30,11 +30,9 @@ #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; @@ -53,26 +51,26 @@ class FilterGraph; 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 () {} + virtual bool seek_back () { + return true; + } + virtual bool seek_forward () { + return true; + } boost::signals2::signal<void()> OutputChanged; protected: - /** our Film */ - boost::shared_ptr<Film> _film; - /** our decode options */ - DecodeOptions _opt; + boost::shared_ptr<const Film> _film; private: virtual void film_changed (Film::Property) {} - + 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/encoder.cc b/src/lib/encoder.cc index cff9899ac..c1d1041ae 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -27,7 +27,6 @@ #include <libdcp/picture_asset.h> #include "encoder.h" #include "util.h" -#include "options.h" #include "film.h" #include "log.h" #include "exceptions.h" @@ -38,6 +37,8 @@ #include "format.h" #include "cross.h" #include "writer.h" +#include "player.h" +#include "audio_mapping.h" #include "i18n.h" @@ -48,7 +49,8 @@ using std::vector; using std::list; using std::cout; using std::make_pair; -using namespace boost; +using boost::shared_ptr; +using boost::optional; int const Encoder::_history_size = 25; @@ -77,22 +79,29 @@ Encoder::~Encoder () void Encoder::process_begin () { - if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) { + if (_film->has_audio() && _film->audio_frame_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()); + s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_frame_rate(), _film->target_audio_sample_rate()); _film->log()->log (s.str ()); - /* We will be using planar float data when we call the resampler */ + /* 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, - _film->audio_stream()->channel_layout(), + av_get_default_channel_layout (_film->audio_mapping().dcp_channels ()), AV_SAMPLE_FMT_FLTP, _film->target_audio_sample_rate(), - _film->audio_stream()->channel_layout(), + av_get_default_channel_layout (_film->audio_mapping().dcp_channels ()), AV_SAMPLE_FMT_FLTP, - _film->audio_stream()->sample_rate(), + _film->audio_frame_rate(), 0, 0 ); @@ -126,9 +135,9 @@ void Encoder::process_end () { #if HAVE_SWRESAMPLE - if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) { + if (_film->has_audio() && _swr_context) { - shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256)); + 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); @@ -142,7 +151,7 @@ Encoder::process_end () } out->set_frames (frames); - write_audio (out); + _writer->write (out); } swr_free (&_swr_context); @@ -193,7 +202,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) { @@ -231,9 +240,9 @@ 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, shared_ptr<Subtitle> sub) { - FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate()); + FrameRateConversion frc (_film->video_frame_rate(), _film->dcp_frame_rate()); if (frc.skip && (_video_frames_in % 2)) { ++_video_frames_in; @@ -269,7 +278,7 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ /* 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> ( + _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(), @@ -301,9 +310,9 @@ Encoder::process_audio (shared_ptr<const AudioBuffers> data) 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; + int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_frame_rate()) + 32; - shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames)); + shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_mapping().dcp_channels(), max_resampled_frames)); /* Resample audio */ int const resampled_frames = swr_convert ( @@ -321,7 +330,7 @@ Encoder::process_audio (shared_ptr<const AudioBuffers> data) } #endif - write_audio (data); + _writer->write (data); } void @@ -362,7 +371,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 (); @@ -416,34 +425,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->audio_channels ()); - if (m.dcp_channels() != _film->audio_channels()) { - - /* Remap (currently just for mono -> 5.1) */ - - 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..f95d42661 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. @@ -81,15 +81,13 @@ public: /** Called when a processing run has finished */ virtual 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 (); @@ -106,7 +104,7 @@ private: static int const _history_size; /** Number of video frames received so far */ - SourceFrame _video_frames_in; + ContentVideoFrame _video_frames_in; /** Number of video frames written for the DCP so far */ int _video_frames_out; diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc index 4b30c9431..aad7f265e 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 "film.h" -#include "video_decoder.h" +#include "content.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<Film> f, shared_ptr<Content> c, bool q) : Job (f) + , _content (c) + , _quick (q) { } @@ -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 (_film, shared_from_this (), _quick); 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..dc0d53fff 100644 --- a/src/lib/examine_content_job.h +++ b/src/lib/examine_content_job.h @@ -17,22 +17,23 @@ */ -/** @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<Film>, boost::shared_ptr<Content>, bool); ~ExamineContentJob (); std::string name () const; void run (); + +private: + boost::shared_ptr<Content> _content; + bool _quick; }; 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_content.cc b/src/lib/ffmpeg_content.cc new file mode 100644 index 000000000..719c4cb53 --- /dev/null +++ b/src/lib/ffmpeg_content.cc @@ -0,0 +1,282 @@ +/* + 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_decoder.h" +#include "compose.hpp" +#include "job.h" +#include "util.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; + +FFmpegContent::FFmpegContent (boost::filesystem::path f) + : Content (f) + , VideoContent (f) + , AudioContent (f) +{ + +} + +FFmpegContent::FFmpegContent (shared_ptr<const cxml::Node> node) + : Content (node) + , VideoContent (node) + , AudioContent (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 (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 (FFmpegAudioStream (*i)); + if ((*i)->optional_number_child<int> ("Selected")) { + _audio_stream = _audio_streams.back (); + } + } +} + +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); + + boost::mutex::scoped_lock lm (_mutex); + + for (vector<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.get()) { + t->add_child("Selected")->add_child_text("1"); + } + i->as_xml (t); + } + + for (vector<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.get()) { + t->add_child("Selected")->add_child_text("1"); + } + i->as_xml (t); + } +} + +void +FFmpegContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick) +{ + job->set_progress_unknown (); + + Content::examine (film, job, quick); + + shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false)); + + ContentVideoFrame video_length = 0; + if (quick) { + video_length = decoder->video_length (); + film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ())); + } else { + while (!decoder->pass ()) { + /* keep going */ + } + + video_length = decoder->video_frame (); + film->log()->log (String::compose ("Video length examined as %1 frames", decoder->video_frame ())); + } + + { + boost::mutex::scoped_lock lm (_mutex); + + _video_length = video_length; + + _subtitle_streams = decoder->subtitle_streams (); + if (!_subtitle_streams.empty ()) { + _subtitle_stream = _subtitle_streams.front (); + } + + _audio_streams = decoder->audio_streams (); + if (!_audio_streams.empty ()) { + _audio_stream = _audio_streams.front (); + } + } + + take_from_video_decoder (decoder); + + signal_changed (VideoContentProperty::VIDEO_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 (FFmpegSubtitleStream s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _subtitle_stream = s; + } + + signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); +} + +void +FFmpegContent::set_audio_stream (FFmpegAudioStream s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_stream = s; + } + + signal_changed (FFmpegContentProperty::AUDIO_STREAM); +} + +ContentAudioFrame +FFmpegContent::audio_length () const +{ + if (!_audio_stream) { + return 0; + } + + return video_frames_to_audio_frames (_video_length, audio_frame_rate(), video_frame_rate()); +} + +int +FFmpegContent::audio_channels () const +{ + if (!_audio_stream) { + return 0; + } + + return _audio_stream->channels; +} + +int +FFmpegContent::audio_frame_rate () const +{ + if (!_audio_stream) { + return 0; + } + + return _audio_stream->frame_rate; +} + +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"); +} + +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)); +} + +/** 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)); +} diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h new file mode 100644 index 000000000..8bf4d42a5 --- /dev/null +++ b/src/lib/ffmpeg_content.h @@ -0,0 +1,128 @@ +/* + 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 FFmpegAudioStream +{ +public: + FFmpegAudioStream (std::string n, int i, int f, int c) + : name (n) + , id (i) + , frame_rate (f) + , channels (c) + {} + + FFmpegAudioStream (boost::shared_ptr<const cxml::Node>); + + void as_xml (xmlpp::Node *) const; + + std::string name; + int id; + int frame_rate; + int channels; +}; + +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; +}; + +class FFmpegContent : public VideoContent, public AudioContent +{ +public: + FFmpegContent (boost::filesystem::path); + FFmpegContent (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<Film>, boost::shared_ptr<Job>, bool); + std::string summary () const; + std::string information () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr<Content> clone () const; + + /* AudioContent */ + int audio_channels () const; + ContentAudioFrame audio_length () const; + int audio_frame_rate () const; + + std::vector<FFmpegSubtitleStream> subtitle_streams () const { + boost::mutex::scoped_lock lm (_mutex); + return _subtitle_streams; + } + + boost::optional<FFmpegSubtitleStream> subtitle_stream () const { + boost::mutex::scoped_lock lm (_mutex); + return _subtitle_stream; + } + + std::vector<FFmpegAudioStream> audio_streams () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_streams; + } + + boost::optional<FFmpegAudioStream> audio_stream () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_stream; + } + + void set_subtitle_stream (FFmpegSubtitleStream); + void set_audio_stream (FFmpegAudioStream); + +private: + std::vector<FFmpegSubtitleStream> _subtitle_streams; + boost::optional<FFmpegSubtitleStream> _subtitle_stream; + std::vector<FFmpegAudioStream> _audio_streams; + boost::optional<FFmpegAudioStream> _audio_stream; +}; + +#endif diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 8e09810cb..d5285b73a 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -41,7 +41,6 @@ extern "C" { #include "transcoder.h" #include "job.h" #include "filter.h" -#include "options.h" #include "exceptions.h" #include "image.h" #include "util.h" @@ -62,10 +61,13 @@ using boost::optional; using boost::dynamic_pointer_cast; using libdcp::Size; -FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) - , VideoDecoder (f, o) - , AudioDecoder (f, o) +boost::mutex FFmpegDecoder::_mutex; + +FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles) + : Decoder (f) + , VideoDecoder (f) + , AudioDecoder (f) + , _ffmpeg_content (c) , _format_context (0) , _video_stream (-1) , _frame (0) @@ -75,6 +77,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o) , _audio_codec (0) , _subtitle_codec_context (0) , _subtitle_codec (0) + , _decode_video (video) + , _decode_audio (audio) + , _decode_subtitles (subtitles) { setup_general (); setup_video (); @@ -84,10 +89,12 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o) 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); } @@ -106,15 +113,15 @@ 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_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, audio and subtitle streams and choose the first of each */ + /* Find video, audio and subtitle streams */ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { AVStream* s = _format_context->streams[i]; @@ -131,17 +138,11 @@ FFmpegDecoder::setup_general () } _audio_streams.push_back ( - shared_ptr<AudioStream> ( - new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout) - ) + 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<SubtitleStream> ( - new SubtitleStream (stream_name (s), i) - ) - ); + _subtitle_streams.push_back (FFmpegSubtitleStream (stream_name (s), i)); } } @@ -158,6 +159,8 @@ FFmpegDecoder::setup_general () void FFmpegDecoder::setup_video () { + boost::mutex::scoped_lock lm (_mutex); + _video_codec_context = _format_context->streams[_video_stream]->codec; _video_codec = avcodec_find_decoder (_video_codec_context->codec_id); @@ -173,14 +176,13 @@ FFmpegDecoder::setup_video () void FFmpegDecoder::setup_audio () { - if (!_audio_stream) { + boost::mutex::scoped_lock lm (_mutex); + + if (!_ffmpeg_content->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_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec; _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id); if (_audio_codec == 0) { @@ -195,11 +197,13 @@ FFmpegDecoder::setup_audio () void FFmpegDecoder::setup_subtitle () { - if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) { + boost::mutex::scoped_lock lm (_mutex); + + if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) { return; } - _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec; + _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec; _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); if (_subtitle_codec == 0) { @@ -233,14 +237,14 @@ FFmpegDecoder::pass () /* XXX: should we reset _packet.data and size after each *_decode_* call? */ int frame_finished; - - if (_opt.decode_video) { + + if (_decode_video) { while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { filter_and_emit_video (); } } - - if (_audio_stream && _opt.decode_audio) { + + if (_ffmpeg_content->audio_stream() && _decode_audio) { decode_audio_packet (); } @@ -249,9 +253,7 @@ FFmpegDecoder::pass () avcodec_get_frame_defaults (_frame); - shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); - - if (_packet.stream_index == _video_stream && _opt.decode_video) { + if (_packet.stream_index == _video_stream && _decode_video) { int frame_finished; int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet); @@ -264,9 +266,9 @@ FFmpegDecoder::pass () filter_and_emit_video (); } - } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) { + } 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 && _decode_subtitles) { int got_subtitle; AVSubtitle sub; @@ -298,19 +300,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: @@ -322,7 +321,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; } @@ -333,7 +332,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); } @@ -350,7 +349,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; } @@ -367,7 +366,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; } @@ -378,7 +377,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)); } } @@ -392,7 +391,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) } float -FFmpegDecoder::frames_per_second () const +FFmpegDecoder::video_frame_rate () const { AVStream* s = _format_context->streams[_video_stream]; @@ -483,21 +482,6 @@ FFmpegDecoder::bytes_per_audio_sample () const } void -FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s) -{ - AudioDecoder::set_audio_stream (s); - setup_audio (); -} - -void -FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) -{ - VideoDecoder::set_subtitle_stream (s); - setup_subtitle (); - OutputChanged (); -} - -void FFmpegDecoder::filter_and_emit_video () { boost::mutex::scoped_lock lm (_filter_graphs_mutex); @@ -536,25 +520,23 @@ FFmpegDecoder::seek (double p) } 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); + if (last_content_time() < 2.5) { + return true; + } + + return do_seek (last_content_time() - 2.5 / video_frame_rate(), true, true); } -void +bool FFmpegDecoder::seek_forward () { - do_seek (last_source_time() - 0.5 / frames_per_second(), true, true); + if (last_content_time() >= (video_length() - video_frame_rate())) { + return true; + } + + return do_seek (last_content_time() - 0.5 / video_frame_rate(), true, true); } bool @@ -596,58 +578,6 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate) return r < 0; } -shared_ptr<FFmpegAudioStream> -FFmpegAudioStream::create (string t, optional<int> v) -{ - if (!v) { - /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ - return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v)); - } - - stringstream s (t); - string type; - s >> type; - if (type != N_("ffmpeg")) { - return shared_ptr<FFmpegAudioStream> (); - } - - return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v)); -} - -FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version) -{ - 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")); - } - - for (int i = 0; i < name_index; ++i) { - size_t const s = t.find (' '); - if (s != string::npos) { - t = t.substr (s + 1); - } - } - - _name = t; -} - -string -FFmpegAudioStream::to_string () const -{ - return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name); -} - void FFmpegDecoder::film_changed (Film::Property p) { @@ -658,7 +588,6 @@ FFmpegDecoder::film_changed (Film::Property p) boost::mutex::scoped_lock lm (_filter_graphs_mutex); _filter_graphs.clear (); } - OutputChanged (); break; default: @@ -667,18 +596,15 @@ FFmpegDecoder::film_changed (Film::Property p) } /** @return Length (in video frames) according to our content's header */ -SourceFrame -FFmpegDecoder::length () const +ContentVideoFrame +FFmpegDecoder::video_length () const { - return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second(); + return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate(); } void FFmpegDecoder::decode_audio_packet () { - 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. */ @@ -700,7 +626,7 @@ FFmpegDecoder::decode_audio_packet () 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1 ); - assert (_audio_codec_context->channels == _film->audio_channels()); + assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels()); Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds); } diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 0c89b973d..1e273752a 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -36,6 +36,7 @@ extern "C" { #include "video_decoder.h" #include "audio_decoder.h" #include "film.h" +#include "ffmpeg_content.h" struct AVFilterGraph; struct AVCodecContext; @@ -50,68 +51,46 @@ 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; - } - - 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 FFmpegDecoder * @brief A decoder using FFmpeg to decode content. */ class FFmpegDecoder : public VideoDecoder, public AudioDecoder { public: - FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions); + FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles); ~FFmpegDecoder (); - float frames_per_second () const; + float video_frame_rate () const; libdcp::Size native_size () const; - SourceFrame length () const; + ContentVideoFrame video_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>); + std::vector<FFmpegSubtitleStream> subtitle_streams () const { + return _subtitle_streams; + } + + std::vector<FFmpegAudioStream> audio_streams () const { + return _audio_streams; + } bool seek (double); - bool seek_to_last (); - void seek_forward (); - void seek_back (); + bool seek_forward (); + bool seek_back (); + bool pass (); private: - bool pass (); - bool do_seek (double p, bool, bool); + /* No copy construction */ + FFmpegDecoder (FFmpegDecoder const &); + FFmpegDecoder& operator= (FFmpegDecoder const &); + PixelFormat pixel_format () const; AVSampleFormat audio_sample_format () const; int bytes_per_audio_sample () const; + bool do_seek (double, bool, bool); void filter_and_emit_video (); @@ -129,6 +108,8 @@ private: std::string stream_name (AVStream* s) const; + boost::shared_ptr<const FFmpegContent> _ffmpeg_content; + AVFormatContext* _format_context; int _video_stream; @@ -145,4 +126,17 @@ private: std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; boost::mutex _filter_graphs_mutex; + + std::vector<FFmpegSubtitleStream> _subtitle_streams; + std::vector<FFmpegAudioStream> _audio_streams; + + bool _decode_video; + bool _decode_audio; + bool _decode_subtitles; + + /* 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; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index b0785df34..98c6c0610 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -29,29 +29,31 @@ #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 "i18n.h" @@ -67,6 +69,8 @@ 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::lexical_cast; using boost::to_upper_copy; @@ -86,19 +90,18 @@ int const Film::state_version = 4; */ Film::Film (string d, bool must_exist) - : _use_dci_name (true) - , _trust_content_header (true) + : _playlist (new Playlist) + , _use_dci_name (true) + , _trust_content_headers (true) , _dcp_content_type (0) - , _format (0) + , _format (Format::from_id ("185")) , _scaler (Scaler::from_id ("bicubic")) , _trim_start (0) , _trim_end (0) , _trim_type (CPL) - , _dcp_ab (false) - , _use_content_audio (true) + , _ab (false) , _audio_gain (0) , _audio_delay (0) - , _still_duration (10) , _with_subtitles (false) , _subtitle_offset (0) , _subtitle_scale (1) @@ -106,10 +109,11 @@ Film::Film (string d, bool must_exist) , _j2k_bandwidth (200000000) , _dci_metadata (Config::instance()->default_dci_metadata ()) , _dcp_frame_rate (0) - , _source_frame_rate (0) , _dirty (false) { set_dci_date_today (); + + _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2)); /* Make state.directory a complete path without ..s (where possible) (Code swiped from Adam Bowen on stackoverflow) @@ -139,8 +143,6 @@ Film::Film (string d, bool must_exist) } } - _sndfile_stream = SndfileStream::create (); - if (must_exist) { read_metadata (); } else { @@ -154,11 +156,11 @@ 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) , _directory (o._directory) , _name (o._name) , _use_dci_name (o._use_dci_name) - , _content (o._content) - , _trust_content_header (o._trust_content_header) + , _trust_content_headers (o._trust_content_headers) , _dcp_content_type (o._dcp_content_type) , _format (o._format) , _crop (o._crop) @@ -167,37 +169,26 @@ Film::Film (Film const & o) , _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) + , _ab (o._ab) , _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) - , _dci_date (o._dci_date) , _dcp_frame_rate (o._dcp_frame_rate) - , _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) + , _dci_date (o._dci_date) , _dirty (o._dirty) { + for (ContentList::const_iterator i = o._content.begin(); i != o._content.end(); ++i) { + _content.push_back ((*i)->clone ()); + } -} - -Film::~Film () -{ - + _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2)); + + _playlist->setup (_content); } string @@ -210,7 +201,7 @@ Film::video_state_identifier () const stringstream s; s << format()->id() - << "_" << content_digest() + << "_" << _playlist->video_digest() << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom << "_" << _dcp_frame_rate << "_" << f.first << "_" << f.second @@ -218,7 +209,7 @@ Film::video_state_identifier () const << "_" << j2k_bandwidth() << "_" << boost::lexical_cast<int> (colour_lut()); - if (dcp_ab()) { + if (ab()) { pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; } @@ -282,7 +273,7 @@ Film::audio_analysis_path () const { boost::filesystem::path p; p /= "analysis"; - p /= content_digest(); + p /= _playlist->audio_digest(); return file (p.string ()); } @@ -296,7 +287,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]; @@ -304,18 +295,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())); -#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,19 +332,16 @@ 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))); + if (ab()) { + r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this()))); } else { - r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od))); + r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this()))); } } -/** Start a job to analyse the audio of our content file */ +/** Start a job to analyse the audio in our Playlist */ void Film::analyse_audio () { @@ -366,17 +354,12 @@ Film::analyse_audio () JobManager::instance()->add (_analyse_audio_job); } -/** Start a job to examine our content file */ +/** Start a job to examine a piece of content */ void -Film::examine_content () +Film::examine_content (shared_ptr<Content> c) { - 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); + shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c, trust_content_headers ())); + JobManager::instance()->add (j); } void @@ -391,12 +374,6 @@ Film::analyse_audio_finished () _analyse_audio_job.reset (); } -void -Film::examine_content_finished () -{ - _examine_content_job.reset (); -} - /** Start a job to send our DCP to the configured TMS */ void Film::send_dcp_to_tms () @@ -428,86 +405,67 @@ Film::encoded_frames () const void Film::write_metadata () const { + ContentList the_content = content (); + 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 (boost::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"); + root->add_child("TrustContentHeaders")->add_child_text (_trust_content_headers ? "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; + root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ()); } + if (_format) { - f << "format " << _format->as_metadata () << endl; + root->add_child("Format")->add_child_text (_format->id ()); } - 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"; + root->add_child("TrimType")->add_child_text ("CPL"); 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; - } - 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 << "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; + root->add_child("TrimType")->add_child_text ("Encode"); } + + root->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left)); + root->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right)); + root->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top)); + root->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom)); - 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; + for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { + root->add_child("Filter")->add_child_text ((*i)->id ()); } - - f << "source_frame_rate " << _source_frame_rate << endl; + + root->add_child("Scaler")->add_child_text (_scaler->id ()); + root->add_child("TrimStart")->add_child_text (boost::lexical_cast<string> (_trim_start)); + root->add_child("TrimEnd")->add_child_text (boost::lexical_cast<string> (_trim_end)); + root->add_child("AB")->add_child_text (_ab ? "1" : "0"); + root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain)); + root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay)); + root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); + root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset)); + root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale)); + root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut)); + root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth)); + _dci_metadata.as_xml (root->add_child ("DCIMetadata")); + root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate)); + root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date)); + _audio_mapping.as_xml (root->add_child("AudioMapping")); + + for (ContentList::iterator i = the_content.begin(); i != the_content.end(); ++i) { + (*i)->as_xml (root->add_child ("Content")); + } + + doc.write_to_file_formatted (file ("metadata.xml")); _dirty = false; } @@ -519,166 +477,89 @@ 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"); + _trust_content_headers = f.bool_child ("TrustContentHeaders"); - if (k == "audio_sample_rate") { - audio_sample_rate = atoi (v.c_str()); + { + optional<string> c = f.optional_string_child ("DCPContentType"); + if (c) { + _dcp_content_type = DCPContentType::from_dci_name (c.get ()); } + } - /* 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 ()); + { + optional<string> c = f.optional_string_child ("Format"); + if (c) { + _format = Format::from_id (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 ("TrimType"); + if (!c || c.get() == "CPL") { + _trim_type = CPL; + } else if (c && c.get() == "Encode") { + _trim_type = ENCODE; } } - 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()); - } - } + _crop.left = f.number_child<int> ("LeftCrop"); + _crop.right = f.number_child<int> ("RightCrop"); + _crop.top = f.number_child<int> ("TopCrop"); + _crop.bottom = f.number_child<int> ("BottomCrop"); - /* 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()]; + { + list<shared_ptr<cxml::Node> > c = f.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 ())); } + } - /* 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()]; + _scaler = Scaler::from_id (f.string_child ("Scaler")); + _trim_start = f.number_child<int> ("TrimStart"); + _trim_end = f.number_child<int> ("TrimEnd"); + _ab = f.bool_child ("AB"); + _audio_gain = f.number_child<float> ("AudioGain"); + _audio_delay = f.number_child<int> ("AudioDelay"); + _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_frame_rate = f.number_child<int> ("DCPFrameRate"); + _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); + + list<shared_ptr<cxml::Node> > c = f.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> c; + + if (type == "FFmpeg") { + c.reset (new FFmpegContent (*i)); + } else if (type == "ImageMagick") { + c.reset (new ImageMagickContent (*i)); + } else if (type == "Sndfile") { + c.reset (new SndfileContent (*i)); } + + _content.push_back (c); } - + + /* This must come after we've loaded the content, as we're looking things up in _content */ + _audio_mapping.set_from_xml (_content, f.node_child ("AudioMapping")); + _dirty = false; + + _playlist->setup (_content); } libdcp::Size @@ -725,47 +606,18 @@ 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()) { + if (!has_audio ()) { return 0; } /* Resample to a DCI-approved sample rate */ - double t = dcp_audio_sample_rate (audio_stream()->sample_rate()); + double t = dcp_audio_sample_rate (audio_frame_rate()); - FrameRateConversion frc (source_frame_rate(), dcp_frame_rate()); + FrameRateConversion frc (video_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 @@ -774,18 +626,12 @@ Film::target_audio_sample_rate () const */ if (frc.change_speed) { - t *= source_frame_rate() * frc.factor() / dcp_frame_rate(); + t *= video_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 @@ -832,7 +678,7 @@ Film::dci_name (bool if_created_now) const } } - switch (audio_channels()) { + switch (audio_channels ()) { case 1: d << "_10"; break; @@ -911,110 +757,21 @@ 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; - } - - /* 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 */ - switch (content_type()) { - case STILL: - set_format (Format::from_id ("var-185")); - break; - case VIDEO: - set_format (Format::from_id ("185")); - break; - } - - /* Still image DCPs must use external audio */ - if (content_type() == STILL) { - set_use_content_audio (false); - } -} - -void -Film::set_trust_content_header (bool t) +Film::set_trust_content_headers (bool t) { { boost::mutex::scoped_lock lm (_state_mutex); - _trust_content_header = t; + _trust_content_headers = t; } - signal_changed (TRUST_CONTENT_HEADER); + signal_changed (TRUST_CONTENT_HEADERS); - if (!_trust_content_header && !content().empty()) { + if (!_trust_content_headers && !content().empty()) { /* We just said that we don't trust the content's header */ - examine_content (); + ContentList c = content (); + for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { + examine_content (*i); + } } } @@ -1156,50 +913,13 @@ Film::set_trim_type (TrimType t) } 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) +Film::set_ab (bool a) { { boost::mutex::scoped_lock lm (_state_mutex); - _external_audio = a; + _ab = 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); + signal_changed (AB); } void @@ -1223,26 +943,6 @@ Film::set_audio_delay (int d) } 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) { { @@ -1314,184 +1014,312 @@ Film::set_dcp_frame_rate (int f) } void -Film::set_size (libdcp::Size s) +Film::signal_changed (Property p) { { boost::mutex::scoped_lock lm (_state_mutex); - _size = s; + _dirty = true; + } + + switch (p) { + case Film::CONTENT: + _playlist->setup (content ()); + set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ())); + set_audio_mapping (_playlist->default_audio_mapping ()); + break; + default: + break; + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (Changed), p)); } - signal_changed (SIZE); } void -Film::set_length (SourceFrame l) +Film::set_dci_date_today () { - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = l; - } - signal_changed (LENGTH); + _dci_date = boost::gregorian::day_clock::local_day (); } -void -Film::unset_length () +string +Film::info_path (int f) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = boost::none; + boost::filesystem::path p; + p /= info_dir (); + + stringstream s; + s.width (8); + s << setfill('0') << f << ".md5"; + + p /= s.str(); + + /* info_dir() will already have added any initial bit of the path, + so don't call file() on this. + */ + return p.string (); +} + +string +Film::j2c_path (int f, bool t) const +{ + boost::filesystem::path p; + p /= "j2c"; + p /= video_state_identifier (); + + stringstream s; + s.width (8); + s << setfill('0') << f << ".j2c"; + + if (t) { + s << ".tmp"; } - signal_changed (LENGTH); + + p /= s.str(); + return file (p.string ()); } -void -Film::set_content_digest (string d) +/** Make an educated guess as to whether we have a complete DCP + * or not. + * @return true if we do. + */ + +bool +Film::have_dcp () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_digest = d; + try { + libdcp::DCP dcp (dir (dcp_name())); + dcp.read (); + } catch (...) { + return false; } - _dirty = true; + + return true; +} + +shared_ptr<Player> +Film::player () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return shared_ptr<Player> (new Player (shared_from_this (), _playlist)); } void -Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s) +Film::add_content (shared_ptr<Content> c) { { boost::mutex::scoped_lock lm (_state_mutex); - _content_audio_streams = s; + _content.push_back (c); } - signal_changed (CONTENT_AUDIO_STREAMS); + + signal_changed (CONTENT); + + examine_content (c); } void -Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s) +Film::remove_content (shared_ptr<Content> c) { { boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_streams = s; + ContentList::iterator i = find (_content.begin(), _content.end(), c); + if (i != _content.end ()) { + _content.erase (i); + } } - signal_changed (SUBTITLE_STREAMS); + + signal_changed (CONTENT); } void -Film::set_source_frame_rate (float f) +Film::move_content_earlier (shared_ptr<Content> c) { { boost::mutex::scoped_lock lm (_state_mutex); - _source_frame_rate = f; + ContentList::iterator i = find (_content.begin(), _content.end(), c); + if (i == _content.begin () || i == _content.end()) { + return; + } + + ContentList::iterator j = i; + --j; + + swap (*i, *j); } - signal_changed (SOURCE_FRAME_RATE); + + signal_changed (CONTENT); } - + void -Film::signal_changed (Property p) +Film::move_content_later (shared_ptr<Content> c) { { boost::mutex::scoped_lock lm (_state_mutex); - _dirty = true; - } + ContentList::iterator i = find (_content.begin(), _content.end(), c); + if (i == _content.end()) { + return; + } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Changed), p)); + ContentList::iterator j = i; + ++j; + if (j == _content.end ()) { + return; + } + + swap (*i, *j); } + + signal_changed (CONTENT); + +} + +ContentAudioFrame +Film::audio_length () const +{ + return _playlist->audio_length (); } int Film::audio_channels () const { - shared_ptr<AudioStream> s = audio_stream (); - if (!s) { - return 0; - } + return _playlist->audio_channels (); +} - return s->channels (); +int +Film::audio_frame_rate () const +{ + return _playlist->audio_frame_rate (); } -void -Film::set_dci_date_today () +bool +Film::has_audio () const { - _dci_date = boost::gregorian::day_clock::local_day (); + return _playlist->has_audio (); } -boost::shared_ptr<AudioStream> -Film::audio_stream () const +float +Film::video_frame_rate () const { - if (use_content_audio()) { - return _content_audio_stream; - } + return _playlist->video_frame_rate (); +} - return _sndfile_stream; +libdcp::Size +Film::video_size () const +{ + return _playlist->video_size (); } -string -Film::info_path (int f) const +ContentVideoFrame +Film::video_length () const { - boost::filesystem::path p; - p /= info_dir (); + return _playlist->video_length (); +} - stringstream s; - s.width (8); - s << setfill('0') << f << ".md5"; +ContentVideoFrame +Film::content_length () const +{ + return _playlist->content_length (); +} - p /= s.str(); +/** Unfortunately this is needed as the GUI has FFmpeg-specific controls */ +shared_ptr<FFmpegContent> +Film::ffmpeg () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + + for (ContentList::const_iterator i = _content.begin (); i != _content.end(); ++i) { + shared_ptr<FFmpegContent> f = boost::dynamic_pointer_cast<FFmpegContent> (*i); + if (f) { + return f; + } + } - /* info_dir() will already have added any initial bit of the path, - so don't call file() on this. - */ - return p.string (); + return shared_ptr<FFmpegContent> (); } -string -Film::j2c_path (int f, bool t) const +vector<FFmpegSubtitleStream> +Film::ffmpeg_subtitle_streams () const { - boost::filesystem::path p; - p /= "j2c"; - p /= video_state_identifier (); + shared_ptr<FFmpegContent> f = ffmpeg (); + if (f) { + return f->subtitle_streams (); + } - stringstream s; - s.width (8); - s << setfill('0') << f << ".j2c"; + return vector<FFmpegSubtitleStream> (); +} - if (t) { - s << ".tmp"; +boost::optional<FFmpegSubtitleStream> +Film::ffmpeg_subtitle_stream () const +{ + shared_ptr<FFmpegContent> f = ffmpeg (); + if (f) { + return f->subtitle_stream (); } - p /= s.str(); - return file (p.string ()); + return boost::none; } -/** Make an educated guess as to whether we have a complete DCP - * or not. - * @return true if we do. - */ +vector<FFmpegAudioStream> +Film::ffmpeg_audio_streams () const +{ + shared_ptr<FFmpegContent> f = ffmpeg (); + if (f) { + return f->audio_streams (); + } -bool -Film::have_dcp () const + return vector<FFmpegAudioStream> (); +} + +boost::optional<FFmpegAudioStream> +Film::ffmpeg_audio_stream () const { - try { - libdcp::DCP dcp (dir (dcp_name())); - dcp.read (); - } catch (...) { - return false; + shared_ptr<FFmpegContent> f = ffmpeg (); + if (f) { + return f->audio_stream (); } - return true; + return boost::none; } -bool -Film::has_audio () const +void +Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s) { - if (use_content_audio()) { - return audio_stream(); + shared_ptr<FFmpegContent> f = ffmpeg (); + if (f) { + f->set_subtitle_stream (s); } +} - 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::set_ffmpeg_audio_stream (FFmpegAudioStream s) +{ + shared_ptr<FFmpegContent> f = ffmpeg (); + if (f) { + f->set_audio_stream (s); + } +} + +void +Film::set_audio_mapping (AudioMapping m) +{ + { + boost::mutex::scoped_lock lm (_state_mutex); + _audio_mapping = m; } - return false; + signal_changed (AUDIO_MAPPING); } +void +Film::content_changed (boost::weak_ptr<Content> c, int p) +{ + if (p == VideoContentProperty::VIDEO_FRAME_RATE) { + set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ())); + } else if (p == AudioContentProperty::AUDIO_CHANNELS) { + set_audio_mapping (_playlist->default_audio_mapping ()); + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); + } +} diff --git a/src/lib/film.h b/src/lib/film.h index dd0a83d94..43b5d1a17 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,14 +32,13 @@ #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 "audio_mapping.h" +class DCPContentType; class Format; class Job; class Filter; @@ -47,19 +46,19 @@ class Log; class ExamineContentJob; class AnalyseAudioJob; class ExternalAudioStream; +class Content; +class Player; +class Playlist; /** @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 (Film const &); - ~Film (); std::string info_dir () const; std::string j2c_path (int f, bool t) const; @@ -68,13 +67,12 @@ public: std::string internal_video_mxf_filename () const; std::string audio_analysis_path () const; + void examine_content (boost::shared_ptr<Content>); 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,13 +87,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 (); libdcp::Size cropped_size (libdcp::Size) const; std::string dci_name (bool if_created_now) const; @@ -106,11 +100,30 @@ public: return _dirty; } + bool have_dcp () const; + + boost::shared_ptr<Player> player () const; + + /* Proxies for some Playlist methods */ + + ContentAudioFrame audio_length () const; int audio_channels () const; + int audio_frame_rate () const; + bool has_audio () const; + + float video_frame_rate () const; + libdcp::Size video_size () const; + ContentVideoFrame video_length () const; - void set_dci_date_today (); + ContentVideoFrame content_length () const; - bool have_dcp () const; + std::vector<FFmpegSubtitleStream> ffmpeg_subtitle_streams () const; + boost::optional<FFmpegSubtitleStream> ffmpeg_subtitle_stream () const; + std::vector<FFmpegAudioStream> ffmpeg_audio_streams () const; + boost::optional<FFmpegAudioStream> ffmpeg_audio_stream () const; + + void set_ffmpeg_subtitle_stream (FFmpegSubtitleStream); + void set_ffmpeg_audio_stream (FFmpegAudioStream); enum TrimType { CPL, @@ -124,8 +137,9 @@ public: NONE, NAME, USE_DCI_NAME, + TRUST_CONTENT_HEADERS, + /** The content list has changed (i.e. content has been added, moved around or removed) */ CONTENT, - TRUST_CONTENT_HEADER, DCP_CONTENT_TYPE, FORMAT, CROP, @@ -133,27 +147,18 @@ public: SCALER, TRIM_START, TRIM_END, + AB, 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_FRAME_RATE, + AUDIO_MAPPING }; @@ -174,14 +179,14 @@ public: return _use_dci_name; } - std::string content () const { + bool trust_content_headers () const { boost::mutex::scoped_lock lm (_state_mutex); - return _content; + return _trust_content_headers; } - bool trust_content_header () const { + ContentList content () const { boost::mutex::scoped_lock lm (_state_mutex); - return _trust_content_header; + return _content; } DCPContentType const * dcp_content_type () const { @@ -224,26 +229,11 @@ public: 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 { + bool ab () const { boost::mutex::scoped_lock lm (_state_mutex); - return _external_audio; + return _ab; } - 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; @@ -254,18 +244,6 @@ public: 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; @@ -300,51 +278,22 @@ public: 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 { + AudioMapping audio_mapping () 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; - } - - std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_streams; - } - - float source_frame_rate () const { - boost::mutex::scoped_lock lm (_state_mutex); - if (content_type() == STILL) { - return 24; - } - - return _source_frame_rate; + return _audio_mapping; } - 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 set_trust_content_headers (bool); + void add_content (boost::shared_ptr<Content>); + void remove_content (boost::shared_ptr<Content>); + void move_content_earlier (boost::shared_ptr<Content>); + void move_content_later (boost::shared_ptr<Content>); void set_dcp_content_type (DCPContentType const *); void set_format (Format const *); void set_crop (Crop); @@ -356,15 +305,10 @@ public: void set_scaler (Scaler const *); void set_trim_start (int); void set_trim_end (int); + void set_ab (bool); 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); @@ -372,17 +316,15 @@ public: 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); - - /** Emitted when some property has changed */ + void set_dci_date_today (); + void set_audio_mapping (AudioMapping); + + /** Emitted when some property has of the Film has changed */ mutable boost::signals2::signal<void (Property)> Changed; + /** Emitted when some property of our content has changed */ + mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged; + boost::signals2::signal<void ()> AudioAnalysisSucceeded; /** Current version number of the state file */ @@ -390,20 +332,21 @@ public: 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 read_metadata (); + void content_changed (boost::weak_ptr<Content>, int); + boost::shared_ptr<FFmpegContent> ffmpeg () const; + void setup_default_audio_mapping (); std::string filename_safe_name () const; + /** Log to write to */ + boost::shared_ptr<Log> _log; + /** Any running AnalyseAudioJob, or 0 */ + boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job; + boost::shared_ptr<Playlist> _playlist; + /** Complete path to directory containing the film metadata; * must not be relative. */ @@ -411,19 +354,12 @@ 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; + bool _trust_content_headers; + ContentList _content; /** 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.) */ @@ -443,22 +379,11 @@ private: 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; + bool _ab; /** 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 @@ -474,30 +399,13 @@ private: int _colour_lut; /** bandwidth for J2K files in bits per second */ int _j2k_bandwidth; - /** DCI naming stuff */ DCIMetadata _dci_metadata; - /** 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; - - /* 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; + /** The date that we should use in a DCI name */ + boost::gregorian::date _dci_date; + AudioMapping _audio_mapping; /** 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 045cbaa6a..a52c030fe 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -57,7 +57,7 @@ using libdcp::Size; * @param s Size of the images to process. * @param p Pixel format of the images to process. */ -FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p) +FilterGraph::FilterGraph (shared_ptr<const Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index 7e4e8422b..1ff5527ab 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -21,8 +21,8 @@ * @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" #include "ffmpeg_compatibility.h" @@ -37,7 +37,7 @@ class FFmpegDecoder; class FilterGraph { public: - FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p); + FilterGraph (boost::shared_ptr<const Film>, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p); bool can_process (libdcp::Size s, AVPixelFormat p) const; std::list<boost::shared_ptr<Image> > process (AVFrame const * frame); diff --git a/src/lib/format.cc b/src/lib/format.cc index 8c3d0d8ad..a83d53ebd 100644 --- a/src/lib/format.cc +++ b/src/lib/format.cc @@ -29,6 +29,7 @@ #include <iostream> #include "format.h" #include "film.h" +#include "playlist.h" #include "i18n.h" @@ -224,7 +225,7 @@ VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d) float VariableFormat::ratio (shared_ptr<const Film> f) const { - libdcp::Size const c = f->cropped_size (f->size ()); + libdcp::Size const c = f->cropped_size (f->video_size ()); return float (c.width) / c.height; } diff --git a/src/lib/format.h b/src/lib/format.h index e95306232..f240c89fc 100644 --- a/src/lib/format.h +++ b/src/lib/format.h @@ -41,7 +41,7 @@ public: /** @return the ratio of the container (including any padding) */ float container_ratio () const; - int dcp_padding (boost::shared_ptr<const Film> f) const; + int dcp_padding (boost::shared_ptr<const Film>) const; /** @return size in pixels of the images that we should * put in a DCP for this ratio. This size will not correspond 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.h b/src/lib/image.h index 62961a92e..de03d0e3f 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> diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc new file mode 100644 index 000000000..24f6d338d --- /dev/null +++ b/src/lib/imagemagick_content.cc @@ -0,0 +1,99 @@ +/* + 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_decoder.h" +#include "compose.hpp" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using boost::shared_ptr; + +ImageMagickContent::ImageMagickContent (boost::filesystem::path f) + : Content (f) + , VideoContent (f) +{ + +} + +ImageMagickContent::ImageMagickContent (shared_ptr<const cxml::Node> node) + : Content (node) + , VideoContent (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<Film> film, shared_ptr<Job> job, bool quick) +{ + Content::examine (film, job, quick); + shared_ptr<ImageMagickDecoder> decoder (new ImageMagickDecoder (film, shared_from_this())); + + { + boost::mutex::scoped_lock lm (_mutex); + /* Initial length */ + _video_length = 10 * 24; + } + + take_from_video_decoder (decoder); + + signal_changed (VideoContentProperty::VIDEO_LENGTH); +} + +shared_ptr<Content> +ImageMagickContent::clone () const +{ + return shared_ptr<Content> (new ImageMagickContent (*this)); +} + +void +ImageMagickContent::set_video_length (ContentVideoFrame len) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _video_length = len; + } + + signal_changed (VideoContentProperty::VIDEO_LENGTH); +} diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h new file mode 100644 index 000000000..b1e7f9495 --- /dev/null +++ b/src/lib/imagemagick_content.h @@ -0,0 +1,45 @@ +/* + 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/enable_shared_from_this.hpp> +#include "video_content.h" + +namespace cxml { + class Node; +} + +class ImageMagickContent : public VideoContent +{ +public: + ImageMagickContent (boost::filesystem::path); + ImageMagickContent (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<Film>, boost::shared_ptr<Job>, bool); + std::string summary () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr<Content> clone () const; + + void set_video_length (ContentVideoFrame); + + static bool valid_file (boost::filesystem::path); +}; diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index 5ce22c296..3888347ca 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -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,64 +32,53 @@ 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_content (c) + , _position (0) { - 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 ()); + Magick::Image* image = new Magick::Image (_imagemagick_content->file().string()); libdcp::Size const s = libdcp::Size (image->columns(), image->rows()); delete image; return s; } +int +ImageMagickDecoder::video_length () const +{ + return _imagemagick_content->video_length (); +} + bool ImageMagickDecoder::pass () { - if (_iter == _files.end()) { - if (video_frame() >= _film->still_duration_in_frames()) { - return true; - } + if (_position < 0 || _position >= _imagemagick_content->video_length ()) { + return true; + } - emit_video (_image, true, double (video_frame()) / frames_per_second()); + if (_image) { + emit_video (_image, true, double (_position) / 24); + _position++; return false; } - Magick::Image* magick_image = new Magick::Image (_film->content_path ()); + Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ()); libdcp::Size size = native_size (); - shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false)); + _image.reset (new SimpleImage (PIX_FMT_RGB24, size, false)); using namespace MagickCore; - uint8_t* p = image->data()[0]; + uint8_t* p = _image->data()[0]; for (int y = 0; y < size.height; ++y) { for (int x = 0; x < size.width; ++x) { Magick::Color c = magick_image->pixelColor (x, y); @@ -100,11 +90,10 @@ ImageMagickDecoder::pass () delete magick_image; - _image = image->crop (_film->crop(), true); - - emit_video (_image, false, double (video_frame()) / frames_per_second()); + _image = _image->crop (_film->crop(), true); + emit_video (_image, false, double (_position) / 24); - ++_iter; + ++_position; return false; } @@ -116,43 +105,15 @@ ImageMagickDecoder::pixel_format () const } bool -ImageMagickDecoder::seek_to_last () -{ - if (_iter == _files.end()) { - _iter = _files.begin(); - } else { - --_iter; - } - - return false; -} - -bool ImageMagickDecoder::seek (double t) { - int const f = t * frames_per_second(); - - _iter = _files.begin (); - for (int i = 0; i < f; ++i) { - if (_iter == _files.end()) { - return true; - } - ++_iter; - } - - return false; -} + int const f = t * _imagemagick_content->video_frame_rate (); -void -ImageMagickDecoder::film_changed (Film::Property p) -{ - if (p == Film::CROP) { - OutputChanged (); + if (f >= _imagemagick_content->video_length()) { + _position = 0; + return true; } -} -float -ImageMagickDecoder::frames_per_second () const -{ - return _film->source_frame_rate (); + _position = f; + return false; } diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index 80a08f81f..e7c9dee9a 100644 --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@ -23,37 +23,24 @@ namespace Magick { class Image; } +class ImageMagickContent; + class ImageMagickDecoder : public VideoDecoder { public: - ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions); - - float frames_per_second () const; + ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>); - libdcp::Size native_size () const; - - SourceFrame length () const { - /* We don't know */ - return 0; + float video_frame_rate () const { + return 24; } - int audio_channels () const { - return 0; - } - - int audio_sample_rate () const { - return 0; - } - - int64_t audio_channel_layout () const { - return 0; - } + libdcp::Size native_size () const; + ContentVideoFrame video_length () const; bool seek (double); - bool seek_to_last (); + bool pass (); protected: - bool pass (); PixelFormat pixel_format () const; int time_base_numerator () const { @@ -75,10 +62,7 @@ protected: } private: - void film_changed (Film::Property); - - std::list<std::string> _files; - std::list<std::string>::iterator _iter; - + boost::shared_ptr<const ImageMagickContent> _imagemagick_content; boost::shared_ptr<Image> _image; + ContentVideoFrame _position; }; diff --git a/src/lib/job.cc b/src/lib/job.cc index 1c66d87d3..812380594 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -34,8 +34,6 @@ using std::list; using std::stringstream; using boost::shared_ptr; -/** @param s Film that we are operating on. - */ Job::Job (shared_ptr<Film> f) : _film (f) , _thread (0) @@ -93,7 +91,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 (...) { @@ -102,7 +100,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)") ); } diff --git a/src/lib/job.h b/src/lib/job.h index fd036bce2..2119db2f3 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<Film>); virtual ~Job() {} /** @return user-readable name of this job */ @@ -87,7 +87,6 @@ protected: void set_state (State); void set_error (std::string s, std::string d); - /** Film for this job */ boost::shared_ptr<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 index 9924c003a..c56a56301 100644 --- a/src/lib/matcher.cc +++ b/src/lib/matcher.cc @@ -41,7 +41,7 @@ Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second) } void -Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t) +Matcher::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t) { _pixel_format = image->pixel_format (); _size = image->size (); @@ -90,7 +90,7 @@ Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost:: } void -Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t) +Matcher::process_audio (shared_ptr<const AudioBuffers> b, double t) { _channels = b->channels (); diff --git a/src/lib/player.cc b/src/lib/player.cc new file mode 100644 index 000000000..09f1f55a3 --- /dev/null +++ b/src/lib/player.cc @@ -0,0 +1,323 @@ +/* + 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 "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" + +using std::list; +using std::cout; +using std::vector; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; + +Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) + : _film (f) + , _playlist (p) + , _video (true) + , _audio (true) + , _subtitles (true) + , _have_valid_decoders (false) +{ + _playlist->Changed.connect (bind (&Player::playlist_changed, this)); + _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2)); +} + +void +Player::disable_video () +{ + _video = false; +} + +void +Player::disable_audio () +{ + _audio = false; +} + +void +Player::disable_subtitles () +{ + _subtitles = false; +} + +bool +Player::pass () +{ + if (!_have_valid_decoders) { + setup_decoders (); + _have_valid_decoders = true; + } + + bool done = true; + + if (_video && _video_decoder < _video_decoders.size ()) { + + /* Run video decoder; this may also produce audio */ + + if (_video_decoders[_video_decoder]->pass ()) { + _video_decoder++; + } + + if (_video_decoder < _video_decoders.size ()) { + done = false; + } + + } + + if (!_video && _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG && _sequential_audio_decoder < _audio_decoders.size ()) { + + /* We're not producing video, so we may need to run FFmpeg content to get the audio */ + + if (_audio_decoders[_sequential_audio_decoder]->pass ()) { + _sequential_audio_decoder++; + } + + if (_sequential_audio_decoder < _audio_decoders.size ()) { + done = false; + } + + } + + if (_audio && _playlist->audio_from() == Playlist::AUDIO_SNDFILE) { + + /* We're getting audio from SndfileContent */ + + for (vector<shared_ptr<AudioDecoder> >::iterator i = _audio_decoders.begin(); i != _audio_decoders.end(); ++i) { + if (!(*i)->pass ()) { + done = false; + } + } + + Audio (_audio_buffers, _audio_time.get()); + _audio_buffers.reset (); + _audio_time = boost::none; + } + + return done; +} + +void +Player::set_progress (shared_ptr<Job> job) +{ + /* Assume progress can be divined from how far through the video we are */ + + if (_video_decoder >= _video_decoders.size() || !_playlist->video_length()) { + return; + } + + job->set_progress ((_video_start[_video_decoder] + _video_decoders[_video_decoder]->video_frame()) / _playlist->video_length ()); +} + +void +Player::process_video (shared_ptr<const Image> i, bool same, shared_ptr<Subtitle> s, double t) +{ + Video (i, same, s, _video_start[_video_decoder] + t); +} + +void +Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<const AudioBuffers> b, double t) +{ + AudioMapping mapping = _film->audio_mapping (); + if (!_audio_buffers) { + _audio_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ())); + _audio_buffers->make_silent (); + _audio_time = t; + if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { + _audio_time = _audio_time.get() + _audio_start[_sequential_audio_decoder]; + } + } + + for (int i = 0; i < b->channels(); ++i) { + list<libdcp::Channel> dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i)); + for (list<libdcp::Channel>::iterator j = dcp.begin(); j != dcp.end(); ++j) { + _audio_buffers->accumulate (b, i, static_cast<int> (*j)); + } + } + + if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { + /* We can just emit this audio now as it will all be here */ + Audio (_audio_buffers, t); + _audio_buffers.reset (); + _audio_time = boost::none; + } +} + +/** @return true on error */ +bool +Player::seek (double t) +{ + if (!_have_valid_decoders) { + setup_decoders (); + _have_valid_decoders = true; + } + + if (_video_decoders.empty ()) { + return true; + } + + /* Find the decoder that contains this position */ + _video_decoder = 0; + while (1) { + ++_video_decoder; + if (_video_decoder >= _video_decoders.size () || t < _video_start[_video_decoder]) { + --_video_decoder; + t -= _video_start[_video_decoder]; + break; + } + } + + if (_video_decoder < _video_decoders.size()) { + _video_decoders[_video_decoder]->seek (t); + } else { + return true; + } + + /* XXX: don't seek audio because we don't need to... */ + + return false; +} + + +void +Player::seek_back () +{ + /* XXX */ +} + +void +Player::seek_forward () +{ + /* XXX */ +} + + +void +Player::setup_decoders () +{ + _video_decoders.clear (); + _video_decoder = 0; + _audio_decoders.clear (); + _sequential_audio_decoder = 0; + + _video_start.clear(); + _audio_start.clear(); + + double video_so_far = 0; + double audio_so_far = 0; + + list<shared_ptr<const VideoContent> > vc = _playlist->video (); + for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) { + + shared_ptr<const VideoContent> video_content; + shared_ptr<const AudioContent> audio_content; + shared_ptr<VideoDecoder> video_decoder; + shared_ptr<AudioDecoder> audio_decoder; + + /* 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 && _playlist->audio_from() == Playlist::AUDIO_FFMPEG, + _subtitles + ) + ); + + video_content = fc; + audio_content = fc; + video_decoder = fd; + audio_decoder = fd; + } + + shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i); + if (ic) { + video_content = ic; + video_decoder.reset (new ImageMagickDecoder (_film, ic)); + } + + video_decoder->connect_video (shared_from_this ()); + _video_decoders.push_back (video_decoder); + _video_start.push_back (video_so_far); + video_so_far += video_content->video_length() / video_content->video_frame_rate(); + + if (audio_decoder && _playlist->audio_from() == Playlist::AUDIO_FFMPEG) { + audio_decoder->Audio.connect (bind (&Player::process_audio, this, audio_content, _1, _2)); + _audio_decoders.push_back (audio_decoder); + _audio_start.push_back (audio_so_far); + audio_so_far += double(audio_content->audio_length()) / audio_content->audio_frame_rate(); + } + } + + _video_decoder = 0; + _sequential_audio_decoder = 0; + + if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) { + + list<shared_ptr<const AudioContent> > ac = _playlist->audio (); + for (list<shared_ptr<const AudioContent> >::iterator i = ac.begin(); i != ac.end(); ++i) { + + shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); + assert (sc); + + shared_ptr<AudioDecoder> d (new SndfileDecoder (_film, sc)); + d->Audio.connect (bind (&Player::process_audio, this, sc, _1, _2)); + _audio_decoders.push_back (d); + _audio_start.push_back (audio_so_far); + } + } +} + +double +Player::last_video_time () const +{ + return _video_start[_video_decoder] + _video_decoders[_video_decoder]->last_content_time (); +} + +void +Player::content_changed (weak_ptr<Content> w, int p) +{ + shared_ptr<Content> c = w.lock (); + if (!c) { + return; + } + + if (p == VideoContentProperty::VIDEO_LENGTH) { + if (dynamic_pointer_cast<FFmpegContent> (c)) { + /* FFmpeg content length changes are serious; we need new decoders */ + _have_valid_decoders = false; + } + } +} + +void +Player::playlist_changed () +{ + _have_valid_decoders = false; +} diff --git a/src/lib/player.h b/src/lib/player.h new file mode 100644 index 000000000..20b83bfdb --- /dev/null +++ b/src/lib/player.h @@ -0,0 +1,92 @@ +/* + 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 "video_source.h" +#include "audio_source.h" +#include "video_sink.h" +#include "audio_sink.h" + +class VideoDecoder; +class AudioDecoder; +class Job; +class Film; +class Playlist; +class AudioContent; + +/** @class Player + * @brief A class which can `play' a Playlist; emitting its audio and video. + */ + +class Player : public TimedVideoSource, public TimedAudioSource, public TimedVideoSink, 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 (); + void disable_subtitles (); + + bool pass (); + void set_progress (boost::shared_ptr<Job>); + bool seek (double); + void seek_back (); + void seek_forward (); + + double last_video_time () const; + +private: + void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s, double); + void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<const AudioBuffers>, double); + void setup_decoders (); + void playlist_changed (); + void content_changed (boost::weak_ptr<Content>, int); + + boost::shared_ptr<const Film> _film; + boost::shared_ptr<const Playlist> _playlist; + + bool _video; + bool _audio; + bool _subtitles; + + /** Our decoders are ready to go; if this is false the decoders must be (re-)created before they are used */ + bool _have_valid_decoders; + /** Video decoders in order of presentation */ + std::vector<boost::shared_ptr<VideoDecoder> > _video_decoders; + /** Start positions of each video decoder in seconds*/ + std::vector<double> _video_start; + /** Index of current video decoder */ + size_t _video_decoder; + /** Audio decoders in order of presentation (if they are from FFmpeg) */ + std::vector<boost::shared_ptr<AudioDecoder> > _audio_decoders; + /** Start positions of each audio decoder (if they are from FFmpeg) in seconds */ + std::vector<double> _audio_start; + /** Current audio decoder index if we are running them sequentially; otherwise undefined */ + size_t _sequential_audio_decoder; + + boost::shared_ptr<AudioBuffers> _audio_buffers; + boost::optional<double> _audio_time; +}; + +#endif diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc new file mode 100644 index 000000000..3c69ae15f --- /dev/null +++ b/src/lib/playlist.cc @@ -0,0 +1,290 @@ +/* + 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/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 "job.h" + +using std::list; +using std::cout; +using std::vector; +using std::min; +using std::max; +using std::string; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; +using boost::lexical_cast; + +Playlist::Playlist () + : _audio_from (AUDIO_FFMPEG) +{ + +} + +void +Playlist::setup (ContentList content) +{ + _audio_from = AUDIO_FFMPEG; + + _video.clear (); + _audio.clear (); + + for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) { + i->disconnect (); + } + + _content_connections.clear (); + + for (ContentList::const_iterator i = content.begin(); i != content.end(); ++i) { + + /* Video is video */ + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i); + if (vc) { + _video.push_back (vc); + } + + /* FFmpegContent is audio if we are doing AUDIO_FFMPEG */ + shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i); + if (fc && _audio_from == AUDIO_FFMPEG) { + _audio.push_back (fc); + } + + /* SndfileContent trumps FFmpegContent for audio */ + shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (*i); + if (sc) { + if (_audio_from == AUDIO_FFMPEG) { + /* This is our fist SndfileContent; clear any FFmpegContent and + say that we are using Sndfile. + */ + _audio.clear (); + _audio_from = AUDIO_SNDFILE; + } + + _audio.push_back (sc); + } + + _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2))); + } + + Changed (); +} + +/** @return Length of our audio */ +ContentAudioFrame +Playlist::audio_length () const +{ + ContentAudioFrame len = 0; + + switch (_audio_from) { + case AUDIO_FFMPEG: + /* FFmpeg content is sequential */ + for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { + len += (*i)->audio_length (); + } + break; + case AUDIO_SNDFILE: + /* Sndfile content is simultaneous */ + for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { + len = max (len, (*i)->audio_length ()); + } + break; + } + + return len; +} + +/** @return number of audio channels */ +int +Playlist::audio_channels () const +{ + int channels = 0; + + switch (_audio_from) { + case AUDIO_FFMPEG: + /* FFmpeg audio is sequential, so use the maximum channel count */ + for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { + channels = max (channels, (*i)->audio_channels ()); + } + break; + case AUDIO_SNDFILE: + /* Sndfile audio is simultaneous, so it's the sum of the channel counts */ + for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { + channels += (*i)->audio_channels (); + } + break; + } + + return channels; +} + +int +Playlist::audio_frame_rate () const +{ + if (_audio.empty ()) { + return 0; + } + + /* XXX: assuming that all content has the same rate */ + return _audio.front()->audio_frame_rate (); +} + +float +Playlist::video_frame_rate () const +{ + if (_video.empty ()) { + return 0; + } + + /* XXX: assuming all the same */ + return _video.front()->video_frame_rate (); +} + +libdcp::Size +Playlist::video_size () const +{ + if (_video.empty ()) { + return libdcp::Size (); + } + + /* XXX: assuming all the same */ + return _video.front()->video_size (); +} + +ContentVideoFrame +Playlist::video_length () const +{ + ContentVideoFrame len = 0; + for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) { + len += (*i)->video_length (); + } + + return len; +} + +bool +Playlist::has_audio () const +{ + return !_audio.empty (); +} + +void +Playlist::content_changed (weak_ptr<Content> c, int p) +{ + ContentChanged (c, p); +} + +AudioMapping +Playlist::default_audio_mapping () const +{ + AudioMapping m; + if (_audio.empty ()) { + return m; + } + + switch (_audio_from) { + case AUDIO_FFMPEG: + { + /* XXX: assumes all the same */ + if (_audio.front()->audio_channels() == 1) { + /* Map mono sources to centre */ + m.add (AudioMapping::Channel (_audio.front(), 0), libdcp::CENTRE); + } else { + int const N = min (_audio.front()->audio_channels (), MAX_AUDIO_CHANNELS); + /* Otherwise just start with a 1:1 mapping */ + for (int i = 0; i < N; ++i) { + m.add (AudioMapping::Channel (_audio.front(), i), (libdcp::Channel) i); + } + } + break; + } + + case AUDIO_SNDFILE: + { + int n = 0; + for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { + for (int j = 0; j < (*i)->audio_channels(); ++j) { + m.add (AudioMapping::Channel (*i, j), (libdcp::Channel) n); + ++n; + if (n >= MAX_AUDIO_CHANNELS) { + break; + } + } + if (n >= MAX_AUDIO_CHANNELS) { + break; + } + } + break; + } + } + + return m; +} + +string +Playlist::audio_digest () const +{ + string t; + + for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { + t += (*i)->digest (); + + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + if (fc) { + t += lexical_cast<string> (fc->audio_stream()->id); + } + } + + return md5_digest (t.c_str(), t.length()); +} + +string +Playlist::video_digest () const +{ + string t; + + for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) { + t += (*i)->digest (); + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + if (fc && fc->subtitle_stream()) { + t += fc->subtitle_stream()->id; + } + } + + return md5_digest (t.c_str(), t.length()); +} + +ContentVideoFrame +Playlist::content_length () const +{ + float const vfr = video_frame_rate() > 0 ? video_frame_rate() : 24; + int const afr = audio_frame_rate() > 0 ? audio_frame_rate() : 48000; + + return max ( + video_length(), + ContentVideoFrame (audio_length() * vfr / afr) + ); +} diff --git a/src/lib/playlist.h b/src/lib/playlist.h new file mode 100644 index 000000000..85bde64ff --- /dev/null +++ b/src/lib/playlist.h @@ -0,0 +1,106 @@ +/* + 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 <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include "video_source.h" +#include "audio_source.h" +#include "video_sink.h" +#include "audio_sink.h" +#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 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) + */ + +class Playlist +{ +public: + Playlist (); + + void setup (ContentList); + + ContentAudioFrame audio_length () const; + int audio_channels () const; + int audio_frame_rate () const; + bool has_audio () const; + + float video_frame_rate () const; + libdcp::Size video_size () const; + ContentVideoFrame video_length () const; + + AudioMapping default_audio_mapping () const; + ContentVideoFrame content_length () const; + + enum AudioFrom { + AUDIO_FFMPEG, + AUDIO_SNDFILE + }; + + AudioFrom audio_from () const { + return _audio_from; + } + + std::list<boost::shared_ptr<const VideoContent> > video () const { + return _video; + } + + std::list<boost::shared_ptr<const AudioContent> > audio () const { + return _audio; + } + + std::string audio_digest () const; + std::string video_digest () const; + + 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); + + /** where we should get our audio from */ + AudioFrom _audio_from; + + /** all our content which contains video */ + std::list<boost::shared_ptr<const VideoContent> > _video; + /** all our content which contains audio. This may contain the same objects + * as _video for FFmpegContent. + */ + std::list<boost::shared_ptr<const AudioContent> > _audio; + + std::list<boost::signals2::connection> _content_connections; +}; diff --git a/src/lib/po/es_ES.po b/src/lib/po/es_ES.po index 1608f3b0c..7d2f8511e 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-04-02 19:10-0500\n" @@ -250,10 +250,10 @@ msgstr "Horizontal deblocking filter A" #: src/lib/job.cc:96 src/lib/job.cc:105 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 d1123d84b..7f3da788b 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-03-20 00:39+0100\n" @@ -248,10 +248,10 @@ msgstr "Filtre dé-bloc horizontal" #: src/lib/job.cc:96 src/lib/job.cc:105 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 "" -"Erreur indéterminée. Merci de rapporter le problème à la liste DVD-o-matic " -"(dvdomatic@carlh.net)" +"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 5f9e9e862..1d7f57536 100644 --- a/src/lib/po/it_IT.po +++ b/src/lib/po/it_IT.po @@ -248,10 +248,10 @@ msgstr "Filtro A sblocco orizzontale" #: src/lib/job.cc:96 src/lib/job.cc:105 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 11aeff987..ff86e23af 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-04-10 15:35+0100\n" @@ -248,10 +248,10 @@ msgstr "Filter för horisontal kantighetsutjämning A" #: src/lib/job.cc:96 src/lib/job.cc:105 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 index 603239f8f..7b7735faa 100644 --- a/src/lib/processor.h +++ b/src/lib/processor.h @@ -21,8 +21,8 @@ * @brief Parent class for classes which accept and then emit video or audio data. */ -#ifndef DVDOMATIC_PROCESSOR_H -#define DVDOMATIC_PROCESSOR_H +#ifndef DCPOMATIC_PROCESSOR_H +#define DCPOMATIC_PROCESSOR_H #include "video_source.h" #include "video_sink.h" 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.h b/src/lib/scp_dcp_job.h index 08d8e2c78..8c16d53fb 100644 --- a/src/lib/scp_dcp_job.h +++ b/src/lib/scp_dcp_job.h @@ -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..ca0bec580 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) { 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..539b0dfb5 --- /dev/null +++ b/src/lib/sndfile_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 "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 (boost::filesystem::path f) + : Content (f) + , AudioContent (f) + , _audio_channels (0) + , _audio_length (0) + , _audio_frame_rate (0) +{ + +} + +SndfileContent::SndfileContent (shared_ptr<const cxml::Node> node) + : Content (node) + , AudioContent (node) +{ + _audio_channels = node->number_child<int> ("AudioChannels"); + _audio_length = node->number_child<ContentAudioFrame> ("AudioLength"); + _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); +} + +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(), + 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<Film> film, shared_ptr<Job> job, bool quick) +{ + job->set_progress_unknown (); + Content::examine (film, job, quick); + + 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); +} + +void +SndfileContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("Sndfile"); + Content::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)); +} + diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h new file mode 100644 index 000000000..e8e86b603 --- /dev/null +++ b/src/lib/sndfile_content.h @@ -0,0 +1,67 @@ +/* + 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::filesystem::path); + SndfileContent (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<Film>, boost::shared_ptr<Job>, bool); + std::string summary () const; + std::string information () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr<Content> clone () const; + + /* AudioContent */ + int audio_channels () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_channels; + } + + ContentAudioFrame audio_length () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_length; + } + + int audio_frame_rate () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_frame_rate; + } + + static bool valid_file (boost::filesystem::path); + +private: + int _audio_channels; + ContentAudioFrame _audio_length; + int _audio_frame_rate; +}; diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index af59c049c..4db45f1d4 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -19,6 +19,7 @@ #include <iostream> #include <sndfile.h> +#include "sndfile_content.h" #include "sndfile_decoder.h" #include "film.h" #include "exceptions.h" @@ -27,163 +28,64 @@ 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) +SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c) + : Decoder (f) + , AudioDecoder (f) + , _sndfile_content (c) { - sf_count_t frames; - vector<SNDFILE*> sf = open_files (frames); - close_files (sf); -} - -vector<SNDFILE*> -SndfileDecoder::open_files (sf_count_t & frames) -{ - 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; - } + _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info); + if (!_sndfile) { + throw DecodeError (_("could not open audio file for reading")); } - if (N == 0) { - return vector<SNDFILE*> (); - } - - bool first = true; - frames = 0; - - vector<SNDFILE*> sndfiles; - 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")); - } - - if (info.channels != 1) { - throw DecodeError (_("external audio files must be mono")); - } - - sndfiles.push_back (s); + _done = 0; + _remaining = _info.frames; +} - 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 () +{ + if (_sndfile) { + sf_close (_sndfile); } - - return sndfiles; } bool SndfileDecoder::pass () { - sf_count_t frames; - vector<SNDFILE*> sndfiles = open_files (frames); - if (sndfiles.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 done = 0; - while (frames > 0) { - sf_count_t const this_time = min (block, frames); - 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), block); - } - } - - audio->set_frames (this_time); - Audio (audio, double(done) / _audio_stream->sample_rate()); - done += this_time; - frames -= this_time; - } - - close_files (sndfiles); - - return true; -} - -void -SndfileDecoder::close_files (vector<SNDFILE*> const & sndfiles) -{ - for (size_t i = 0; i < sndfiles.size(); ++i) { - sf_close (sndfiles[i]); - } -} - -shared_ptr<SndfileStream> -SndfileStream::create () -{ - return shared_ptr<SndfileStream> (new SndfileStream); -} - -shared_ptr<SndfileStream> -SndfileStream::create (string t, optional<int> v) -{ - 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)); + sf_count_t const block = _sndfile_content->audio_frame_rate() / 2; + sf_count_t const this_time = min (block, _remaining); + + shared_ptr<AudioBuffers> audio (new AudioBuffers (_sndfile_content->audio_channels(), this_time)); + sf_read_float (_sndfile, audio->data(0), this_time); + audio->set_frames (this_time); + Audio (audio, double(_done) / audio_frame_rate()); + _done += this_time; + _remaining -= this_time; + + return (_remaining == 0); } -SndfileStream::SndfileStream (string t, optional<int> v) +int +SndfileDecoder::audio_channels () const { - assert (v); - - stringstream s (t); - string type; - s >> type >> _sample_rate >> _channel_layout; + return _info.channels; } -SndfileStream::SndfileStream () +ContentAudioFrame +SndfileDecoder::audio_length () const { - + return _info.frames; } -string -SndfileStream::to_string () const +int +SndfileDecoder::audio_frame_rate () const { - return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout); + return _info.samplerate; } diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index e16eab673..b999a66d1 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -20,35 +20,28 @@ #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 (); + int audio_channels () const; + ContentAudioFrame audio_length () const; + int audio_frame_rate () const; + private: - std::vector<SNDFILE*> open_files (sf_count_t &); - void close_files (std::vector<SNDFILE*> const &); + SNDFILE* open_file (sf_count_t &); + void close_file (SNDFILE*); + + boost::shared_ptr<const SndfileContent> _sndfile_content; + SNDFILE* _sndfile; + SF_INFO _info; + ContentAudioFrame _done; + ContentAudioFrame _remaining; }; 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/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/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..0c3b8c37b 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -39,11 +39,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<Film> f) : Job (f) - , _decode_opt (o) { } @@ -62,9 +60,8 @@ TranscodeJob::run () _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 +80,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 +103,28 @@ TranscodeJob::status () const int TranscodeJob::remaining_time () const { - float fps = _encoder->current_frames_per_second (); + if (!_transcoder) { + return 0; + } + + float fps = _transcoder->current_encoding_rate (); + if (fps == 0) { return 0; } - if (!_film->length()) { + if (!_film->video_length()) { 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()); + int length = _film->video_length(); + FrameRateConversion const frc (_film->video_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(); + int const left = length - _transcoder->video_frames_out(); return left / fps; } diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h index 9b69e4e65..7880a925e 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<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 faafcaf8b..2e33931bd 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -28,14 +28,13 @@ #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 "player.h" #include "trimmer.h" using std::string; @@ -44,87 +43,52 @@ using boost::dynamic_pointer_cast; /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. - * @param o 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<Film> f, shared_ptr<Job> j) : _job (j) - , _encoder (e) - , _decoders (decoder_factory (f, o)) + , _player (f->player ()) + , _encoder (new Encoder (f)) { - assert (_encoder); - - shared_ptr<AudioStream> st = f->audio_stream(); - if (st) { - _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)); + _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate())); + _delay_line.reset (new DelayLine (f->log(), f->audio_delay() * f->audio_frame_rate() / 1000)); _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() + f->log(), trim_start, trim_end, f->content_length(), + f->audio_frame_rate(), f->video_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 ()); + + if (!f->with_subtitles ()) { + _player->disable_subtitles (); } - _decoders.video->connect_video (_delay_line); - if (_matcher) { - _delay_line->connect_video (_matcher); - _matcher->connect_video (_trimmer); - } else { - _delay_line->connect_video (_trimmer); - } + _player->connect_video (_delay_line); + _delay_line->connect_video (_matcher); + _matcher->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); - } + _player->connect_audio (_delay_line); + _delay_line->connect_audio (_matcher); + _matcher->connect_audio (_gain); _gain->connect_audio (_trimmer); _trimmer->connect_audio (_encoder); } -/** 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]) { + if (_player->pass ()) { break; } + _player->set_progress (_job); } - + _delay_line->process_end (); if (_matcher) { _matcher->process_end (); @@ -132,3 +96,15 @@ Transcoder::go () _gain->process_end (); _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..97ecaabfc 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -18,27 +18,22 @@ */ /** @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 Player; class Trimmer; /** @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. @@ -48,24 +43,19 @@ class Transcoder public: Transcoder ( boost::shared_ptr<Film> f, - DecodeOptions o, - Job* j, - boost::shared_ptr<Encoder> e + 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: /** 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; diff --git a/src/lib/types.cc b/src/lib/types.cc new file mode 100644 index 000000000..1e0f48327 --- /dev/null +++ b/src/lib/types.cc @@ -0,0 +1,31 @@ +/* + 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" + +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); +} + diff --git a/src/lib/types.h b/src/lib/types.h new file mode 100644 index 000000000..c2bb9d853 --- /dev/null +++ b/src/lib/types.h @@ -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. + +*/ + +#ifndef DCPOMATIC_TYPES_H +#define DCPOMATIC_TYPES_H + +#include <vector> +#include <boost/shared_ptr.hpp> +#include <libdcp/util.h> + +class Content; + +typedef std::vector<boost::shared_ptr<Content> > ContentList; +typedef int64_t ContentAudioFrame; +typedef int ContentVideoFrame; + +/** @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; +}; + +/** @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; +}; + +#endif diff --git a/src/lib/ui_signaller.h b/src/lib/ui_signaller.h index 221bcbe95..428ab698f 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 859aa6de7..ec1fd47bd 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 @@ -63,19 +63,25 @@ extern "C" { #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 boost::shared_ptr; +using boost::thread; using boost::lexical_cast; using boost::optional; using libdcp::Size; @@ -143,7 +149,7 @@ seconds_to_approximate_hms (int s) return ap.str (); } -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX /** @param l Mangled C++ identifier. * @return Demangled version. */ @@ -242,11 +248,11 @@ seconds (struct timeval t) return t.tv_sec + (double (t.tv_usec) / 1e6); } -/** Call the required functions to set up DVD-o-matic's static arrays, etc. +/** Call the required functions to set up DCP-o-matic's static arrays, etc. * Must be called from the UI thread, if there is one. */ void -dvdomatic_setup () +dcpomatic_setup () { avfilter_register_all (); @@ -259,7 +265,7 @@ dvdomatic_setup () ui_thread = boost::this_thread::get_id (); } -#ifdef DVDOMATIC_WINDOWS +#ifdef DCPOMATIC_WINDOWS boost::filesystem::path mo_path () { @@ -274,9 +280,9 @@ mo_path () #endif void -dvdomatic_setup_i18n (string lang) +dcpomatic_setup_i18n (string lang) { -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX lang += ".UTF8"; #endif @@ -292,15 +298,15 @@ dvdomatic_setup_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 } @@ -361,11 +367,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); @@ -490,16 +496,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. */ @@ -889,6 +885,21 @@ AudioBuffers::move (int from, int to, int frames) } } +/** Add data from from `from', `from_channel' to our channel `to_channel' */ +void +AudioBuffers::accumulate (shared_ptr<const AudioBuffers> 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++; + } +} + /** Trip an assert if the caller is not in the UI thread */ void ensure_ui_thread () @@ -896,30 +907,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 (ContentVideoFrame 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")); -} - /** @return A pair containing CPU model name and the number of processors */ pair<string, int> cpu_info () @@ -927,7 +925,7 @@ cpu_info () pair<string, int> info; info.second = 0; -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX ifstream f (N_("/proc/cpuinfo")); while (f.good ()) { string l; @@ -966,58 +964,6 @@ audio_channel_name (int c) return channels[c]; } -AudioMapping::AudioMapping (int c) - : _source_channels (c) -{ - -} - -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); -} - -int -AudioMapping::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; -} - FrameRateConversion::FrameRateConversion (float source, int dcp) : skip (false) , repeat (false) diff --git a/src/lib/util.h b/src/lib/util.h index 99670110e..0edfe2076 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,9 @@ extern "C" { #include <libavfilter/avfilter.h> } #include "compose.hpp" +#include "types.h" -#ifdef DVDOMATIC_DEBUG +#ifdef DCPOMATIC_DEBUG #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING); #else #define TIMING(...) @@ -54,19 +55,17 @@ 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_i18n (std::string); +extern void dcpomatic_setup (); +extern void dcpomatic_setup_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); @@ -104,87 +103,6 @@ struct FrameRateConversion 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; -}; - -/** @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 std::string colour_lut_index_to_name (int index); @@ -199,7 +117,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. @@ -265,6 +183,7 @@ public: void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset); void move (int from, int to, int frames); + void accumulate (boost::shared_ptr<const AudioBuffers>, int, int); private: /** Number of channels */ @@ -277,21 +196,7 @@ private: float** _data; }; -class AudioMapping -{ -public: - AudioMapping (int); - - boost::optional<libdcp::Channel> source_to_dcp (int c) const; - boost::optional<int> dcp_to_source (libdcp::Channel c) const; - int dcp_channels () const; - -private: - int _source_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 (ContentVideoFrame v, float audio_sample_rate, float frames_per_second); extern std::pair<std::string, int> cpu_info (); class LocaleGuard diff --git a/src/lib/version.h b/src/lib/version.h index 71639e3bc..518862fc4 100644 --- a/src/lib/version.h +++ b/src/lib/version.h @@ -1,3 +1,3 @@ -extern char const * dvdomatic_version; -extern char const * dvdomatic_git_commit; +extern char const * dcpomatic_version; +extern char const * dcpomatic_git_commit; diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc new file mode 100644 index 000000000..9fb2b9bce --- /dev/null +++ b/src/lib/video_content.cc @@ -0,0 +1,106 @@ +/* + 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 "video_content.h" +#include "video_decoder.h" + +#include "i18n.h" + +int const VideoContentProperty::VIDEO_LENGTH = 0; +int const VideoContentProperty::VIDEO_SIZE = 1; +int const VideoContentProperty::VIDEO_FRAME_RATE = 2; + +using std::string; +using std::stringstream; +using std::setprecision; +using boost::shared_ptr; +using boost::lexical_cast; + +VideoContent::VideoContent (boost::filesystem::path f) + : Content (f) + , _video_length (0) +{ + +} + +VideoContent::VideoContent (shared_ptr<const cxml::Node> node) + : Content (node) +{ + _video_length = node->number_child<ContentVideoFrame> ("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"); +} + +VideoContent::VideoContent (VideoContent const & o) + : Content (o) + , _video_length (o._video_length) + , _video_size (o._video_size) + , _video_frame_rate (o._video_frame_rate) +{ + +} + +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)); +} + +void +VideoContent::take_from_video_decoder (shared_ptr<VideoDecoder> d) +{ + /* These decoder calls could call other content methods which take a lock on the mutex */ + libdcp::Size const vs = d->native_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 (); +} diff --git a/src/lib/video_content.h b/src/lib/video_content.h new file mode 100644 index 000000000..75e507d4d --- /dev/null +++ b/src/lib/video_content.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_VIDEO_CONTENT_H +#define DCPOMATIC_VIDEO_CONTENT_H + +#include "content.h" +#include "util.h" + +class VideoDecoder; + +class VideoContentProperty +{ +public: + static int const VIDEO_LENGTH; + static int const VIDEO_SIZE; + static int const VIDEO_FRAME_RATE; +}; + +class VideoContent : public virtual Content +{ +public: + VideoContent (boost::filesystem::path); + VideoContent (boost::shared_ptr<const cxml::Node>); + VideoContent (VideoContent const &); + + void as_xml (xmlpp::Node *) const; + virtual std::string information () const; + + ContentVideoFrame 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; + } + +protected: + void take_from_video_decoder (boost::shared_ptr<VideoDecoder>); + + ContentVideoFrame _video_length; + +private: + libdcp::Size _video_size; + float _video_frame_rate; +}; + +#endif diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 16a076698..fd8238441 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -22,7 +22,6 @@ #include "film.h" #include "image.h" #include "log.h" -#include "options.h" #include "job.h" #include "i18n.h" @@ -31,10 +30,10 @@ using std::cout; using boost::shared_ptr; using boost::optional; -VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o) - : Decoder (f, o) +VideoDecoder::VideoDecoder (shared_ptr<const Film> f) + : Decoder (f) , _video_frame (0) - , _last_source_time (0) + , _last_content_time (0) { } @@ -52,10 +51,11 @@ VideoDecoder::emit_video (shared_ptr<Image> image, bool same, double t) sub = _timed_subtitle->subtitle (); } + TIMING (N_("Decoder emits %1"), _video_frame); Video (image, same, sub, t); ++_video_frame; - - _last_source_time = t; + + _last_content_time = t; } /** Set up the current subtitle. This will be put onto frames that @@ -74,21 +74,12 @@ VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s) } } -/** 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()); + + if (_film->video_length()) { + j->set_progress (float (_video_frame) / _film->video_length()); } } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 6e4fd48c0..0b05b2f71 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -17,48 +17,39 @@ */ -#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" +class VideoContent; + class VideoDecoder : public TimedVideoSource, public virtual Decoder { public: - VideoDecoder (boost::shared_ptr<Film>, DecodeOptions); + VideoDecoder (boost::shared_ptr<const Film>); - /** @return video frames per second, or 0 if unknown */ - virtual float frames_per_second () const = 0; + /** @return video frame rate second, or 0 if unknown */ + virtual float video_frame_rate () 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; + /** @return length according to our content's header */ + virtual ContentVideoFrame video_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; - virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); - void set_progress (Job *) const; 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; + double last_content_time () const { + return _last_content_time; } protected: @@ -68,14 +59,9 @@ protected: 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; + double _last_content_time; boost::shared_ptr<TimedSubtitle> _timed_subtitle; }; diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h index 0170c7350..6239bc557 100644 --- a/src/lib/video_sink.h +++ b/src/lib/video_sink.h @@ -17,8 +17,8 @@ */ -#ifndef DVDOMATIC_VIDEO_SINK_H -#define DVDOMATIC_VIDEO_SINK_H +#ifndef DCPOMATIC_VIDEO_SINK_H +#define DCPOMATIC_VIDEO_SINK_H #include <boost/shared_ptr.hpp> #include "util.h" diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc index 539243402..2de4db68d 100644 --- a/src/lib/video_source.cc +++ b/src/lib/video_source.cc @@ -21,12 +21,25 @@ #include "video_sink.h" using boost::shared_ptr; +using boost::weak_ptr; using boost::bind; +static void +process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<const Image> i, bool same, shared_ptr<Subtitle> s) +{ + shared_ptr<VideoSink> p = sink.lock (); + if (p) { + p->process_video (i, same, s); + } +} + void VideoSource::connect_video (shared_ptr<VideoSink> s) { - Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3)); + /* If we bind, say, a Playlist (as the VideoSink) to a Decoder (which is owned + by the Playlist) we create a cycle. Use a weak_ptr to break it. + */ + Video.connect (bind (process_video_proxy, boost::weak_ptr<VideoSink> (s), _1, _2, _3)); } void diff --git a/src/lib/video_source.h b/src/lib/video_source.h index 748cb6fe9..9b4c9b4a2 100644 --- a/src/lib/video_source.h +++ b/src/lib/video_source.h @@ -21,8 +21,8 @@ * @brief Parent class for classes which emit video data. */ -#ifndef DVDOMATIC_VIDEO_SOURCE_H -#define DVDOMATIC_VIDEO_SOURCE_H +#ifndef DCPOMATIC_VIDEO_SOURCE_H +#define DCPOMATIC_VIDEO_SOURCE_H #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> diff --git a/src/lib/writer.cc b/src/lib/writer.cc index ad81686d1..8e771a5e2 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -22,12 +22,16 @@ #include <libdcp/sound_asset.h> #include <libdcp/picture_frame.h> #include <libdcp/reel.h> +#include <libdcp/dcp.h> #include "writer.h" #include "compose.hpp" #include "film.h" #include "format.h" #include "log.h" #include "dcp_video_frame.h" +#include "dcp_content_type.h" +#include "player.h" +#include "audio_mapping.h" #include "i18n.h" @@ -74,16 +78,14 @@ Writer::Writer (shared_ptr<Film> f) _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0); - AudioMapping m (_film->audio_channels ()); - - if (m.dcp_channels() > 0) { + if (_film->audio_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()) + _film->audio_mapping().dcp_channels (), + dcp_audio_sample_rate (_film->audio_frame_rate()) ) ); diff --git a/src/lib/wscript b/src/lib/wscript index 51b103afd..e53ac5a84 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -6,16 +6,18 @@ sources = """ ab_transcoder.cc analyse_audio_job.cc audio_analysis.cc + audio_content.cc audio_decoder.cc + audio_mapping.cc audio_source.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 @@ -23,31 +25,37 @@ sources = """ exceptions.cc filter_graph.cc ffmpeg_compatibility.cc + ffmpeg_content.cc ffmpeg_decoder.cc film.cc filter.cc format.cc gain.cc image.cc + imagemagick_content.cc imagemagick_decoder.cc job.cc job_manager.cc log.cc lut.cc matcher.cc + player.cc + playlist.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 + types.cc trimmer.cc ui_signaller.cc util.cc + video_content.cc video_decoder.cc video_source.cc writer.cc @@ -59,22 +67,24 @@ 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 """ if bld.env.TARGET_WINDOWS: obj.uselib += ' WINSOCK2' + if bld.env.STATIC: + obj.uselib += ' XML++' obj.source = sources + " version.cc" - obj.target = 'dvdomatic' + obj.target = 'dcpomatic' - 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 b161ac7e3..4312139f1 100644 --- a/src/tools/dvdomatic.cc +++ b/src/tools/dcpomatic.cc @@ -61,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 wxLocale* locale = 0; @@ -238,9 +239,6 @@ public: 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 ()); @@ -286,7 +284,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; } @@ -417,11 +415,11 @@ private: void help_about (wxCommandEvent &) { wxAboutDialogInfo info; - info.SetName (_("DVD-o-matic")); - if (strcmp (dvdomatic_git_commit, "release") == 0) { - info.SetVersion (std_to_wx (String::compose ("version %1", dvdomatic_version))); + info.SetName (_("DCP-o-matic")); + 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")); @@ -442,7 +440,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); } @@ -452,13 +450,15 @@ private: #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 @@ -486,12 +486,12 @@ setup_i18n () f << "Language is available.\n"; 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()) { f << "Locale is not ok.\n"; @@ -502,7 +502,7 @@ setup_i18n () } if (locale) { - dvdomatic_setup_i18n (wx_to_std (locale->GetCanonicalName ())); + dcpomatic_setup_i18n (wx_to_std (locale->GetCanonicalName ())); } } @@ -514,7 +514,7 @@ class App : public wxApp return false; } -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX unsetenv ("UBUNTU_MENUPROXY"); #endif @@ -522,7 +522,7 @@ 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. */ @@ -531,7 +531,7 @@ class App : public wxApp /* 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. @@ -547,7 +547,13 @@ class App : public wxApp } } - Frame* f = new Frame (_("DVD-o-matic")); + if (!film_to_create.empty ()) { + film.reset (new Film (film_to_create, false)); + 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 (); @@ -567,11 +573,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/makedcp.cc b/src/tools/dcpomatic_cli.cc index c594991a6..86c3cf4b1 100644 --- a/src/tools/makedcp.cc +++ b/src/tools/dcpomatic_cli.cc @@ -47,9 +47,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 and quit\n" + << " -d, --deps list DCP-o-matic dependency details and quit\n" << " -t, --test run in test mode (repeatable UUID generation, timestamps etc.)\n" << " -n, --no-progress do not print progress to stdout\n" << " -r, --no-remote do not use any remote servers\n" @@ -87,7 +87,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]); @@ -117,13 +117,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; @@ -146,12 +146,12 @@ main (int argc, char* argv[]) film->log()->set_level ((Log::Level) log_level); cout << "\nMaking "; - if (film->dcp_ab()) { + if (film->ab()) { cout << "A/B "; } cout << "DCP for " << film->name() << "\n"; cout << "Test mode: " << (test_mode ? "yes" : "no") << "\n"; - cout << "Content: " << film->content() << "\n"; +// cout << "Content: " << film->content() << "\n"; pair<string, string> const f = Filter::ffmpeg_strings (film->filters ()); cout << "Filters: " << f.first << " " << f.second << "\n"; @@ -162,7 +162,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 5e36660eb..152e063c1 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); @@ -103,7 +103,7 @@ public: wxIcon icon; icon.CopyFromBitmap (bitmap); #endif - SetIcon (icon, std_to_wx ("DVD-o-matic encode server")); + SetIcon (icon, std_to_wx ("DCP-o-matic encode server")); Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status)); Connect (ID_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::quit)); @@ -147,7 +147,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 1739f97cd..346aa2b39 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-03-23 21:08-0500\n" @@ -17,112 +17,111 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.5\n" -#: src/tools/dvdomatic.cc:179 +#: src/tools/dcpomatic.cc:177 msgid "&Analyse audio" msgstr "&Analizar audio" -#: src/tools/dvdomatic.cc:185 +#: src/tools/dcpomatic.cc:183 msgid "&Edit" msgstr "&Editar" -#: src/tools/dvdomatic.cc:184 +#: src/tools/dcpomatic.cc:182 msgid "&File" msgstr "&Archivo" -#: src/tools/dvdomatic.cc:187 +#: src/tools/dcpomatic.cc:185 msgid "&Help" msgstr "&Ayuda" -#: src/tools/dvdomatic.cc:186 +#: src/tools/dcpomatic.cc:184 msgid "&Jobs" msgstr "&Tareas" -#: src/tools/dvdomatic.cc:175 +#: src/tools/dcpomatic.cc:173 msgid "&Make DCP" msgstr "&Crear DCP" -#: src/tools/dvdomatic.cc:163 +#: src/tools/dcpomatic.cc:161 msgid "&Open..." msgstr "&Abrir..." -#: src/tools/dvdomatic.cc:172 +#: src/tools/dcpomatic.cc:170 msgid "&Preferences..." msgstr "&Preferencias..." -#: src/tools/dvdomatic.cc:167 +#: src/tools/dcpomatic.cc:165 msgid "&Properties..." msgstr "&Propiedades..." -#: src/tools/dvdomatic.cc:169 +#: src/tools/dcpomatic.cc:167 msgid "&Quit" msgstr "&Salir" -#: src/tools/dvdomatic.cc:165 +#: src/tools/dcpomatic.cc:163 msgid "&Save" msgstr "&Guardar" -#: src/tools/dvdomatic.cc:176 +#: src/tools/dcpomatic.cc:174 msgid "&Send DCP to TMS" msgstr "&Enviar DCP al TMS" -#: src/tools/dvdomatic.cc:419 +#: src/tools/dcpomatic.cc:417 msgid "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" msgstr "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" -#: src/tools/dvdomatic.cc:182 +#: src/tools/dcpomatic.cc:180 msgid "About" msgstr "Acerca de" -#: src/tools/dvdomatic.cc:538 +#: src/tools/dcpomatic.cc:527 #, fuzzy msgid "Could not load film %1 (%2)" msgstr "No se pudo cargar la película %s (%s)" -#: src/tools/dvdomatic.cc:341 +#: src/tools/dcpomatic.cc:339 #, c-format msgid "Could not open film at %s (%s)" msgstr "No se pudo cargar la película en %s (%s)" -#: src/tools/dvdomatic.cc:289 src/tools/dvdomatic.cc:412 -#: src/tools/dvdomatic.cc:542 -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:77 +#: src/tools/dcpomatic.cc:75 msgid "Film changed" msgstr "Película cambiada" -#: src/tools/dvdomatic.cc:418 +#: src/tools/dcpomatic.cc:416 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 " "abierto." -#: src/tools/dvdomatic.cc:162 +#: src/tools/dcpomatic.cc:160 msgid "New..." msgstr "Nuevo..." -#: src/tools/dvdomatic.cc:177 +#: src/tools/dcpomatic.cc:175 msgid "S&how DCP" msgstr "&Mostrar DCP" -#: src/tools/dvdomatic.cc:76 -#, c-format +#: src/tools/dcpomatic.cc:74 msgid "Save changes to film \"%s\" before closing?" msgstr "" -#: src/tools/dvdomatic.cc:321 +#: src/tools/dcpomatic.cc:319 msgid "Select film to open" msgstr "Selecciona la película a abrir" -#: src/tools/dvdomatic.cc:305 +#: src/tools/dcpomatic.cc:303 #, fuzzy msgid "The directory %1 already exists." msgstr "La carpeta %s ya existe." -#: src/tools/dvdomatic.cc:326 +#: src/tools/dcpomatic.cc:324 msgid "" "You did not select a folder. Make sure that you select a folder before " "clicking Open." diff --git a/src/tools/po/fr_FR.po b/src/tools/po/fr_FR.po index c1447f7e6..8ce305ccf 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-03-13 22:33+0100\n" @@ -16,110 +16,109 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/tools/dvdomatic.cc:179 +#: src/tools/dcpomatic.cc:177 msgid "&Analyse audio" msgstr "&Analyser le son" -#: src/tools/dvdomatic.cc:185 +#: src/tools/dcpomatic.cc:183 msgid "&Edit" msgstr "&Edition" -#: src/tools/dvdomatic.cc:184 +#: src/tools/dcpomatic.cc:182 msgid "&File" msgstr "&Fichier" -#: src/tools/dvdomatic.cc:187 +#: src/tools/dcpomatic.cc:185 msgid "&Help" msgstr "&Aide" -#: src/tools/dvdomatic.cc:186 +#: src/tools/dcpomatic.cc:184 msgid "&Jobs" msgstr "&Travaux" -#: src/tools/dvdomatic.cc:175 +#: src/tools/dcpomatic.cc:173 msgid "&Make DCP" msgstr "&Créer le DCP" -#: src/tools/dvdomatic.cc:163 +#: src/tools/dcpomatic.cc:161 msgid "&Open..." msgstr "&Ouvrir..." -#: src/tools/dvdomatic.cc:172 +#: src/tools/dcpomatic.cc:170 msgid "&Preferences..." msgstr "&Préférences..." -#: src/tools/dvdomatic.cc:167 +#: src/tools/dcpomatic.cc:165 msgid "&Properties..." msgstr "&Propriétés..." -#: src/tools/dvdomatic.cc:169 +#: src/tools/dcpomatic.cc:167 msgid "&Quit" msgstr "&Quitter" -#: src/tools/dvdomatic.cc:165 +#: src/tools/dcpomatic.cc:163 msgid "&Save" msgstr "&Enregistrer" -#: src/tools/dvdomatic.cc:176 +#: src/tools/dcpomatic.cc:174 msgid "&Send DCP to TMS" msgstr "&Envoyer le DCP dans le TMS" -#: src/tools/dvdomatic.cc:419 +#: src/tools/dcpomatic.cc:417 msgid "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" msgstr "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" -#: src/tools/dvdomatic.cc:182 +#: src/tools/dcpomatic.cc:180 msgid "About" msgstr "A Propos" -#: src/tools/dvdomatic.cc:538 +#: src/tools/dcpomatic.cc:527 #, fuzzy msgid "Could not load film %1 (%2)" msgstr "Impossible de charger le film %s (%s)" -#: src/tools/dvdomatic.cc:341 +#: src/tools/dcpomatic.cc:339 #, c-format msgid "Could not open film at %s (%s)" msgstr "Impossible d'ouvrir le film à %s (%s)" -#: src/tools/dvdomatic.cc:289 src/tools/dvdomatic.cc:412 -#: src/tools/dvdomatic.cc:542 -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:77 +#: src/tools/dcpomatic.cc:75 msgid "Film changed" msgstr "Film changé" -#: src/tools/dvdomatic.cc:418 +#: src/tools/dcpomatic.cc:416 msgid "Free, open-source DCP generation from almost anything." msgstr "Création de DCP libre et open-source à partir de presque tout." -#: src/tools/dvdomatic.cc:162 +#: src/tools/dcpomatic.cc:160 msgid "New..." msgstr "Nouveau..." -#: src/tools/dvdomatic.cc:177 +#: src/tools/dcpomatic.cc:175 msgid "S&how DCP" msgstr "Voir le DCP" -#: src/tools/dvdomatic.cc:76 -#, c-format +#: src/tools/dcpomatic.cc:74 msgid "Save changes to film \"%s\" before closing?" msgstr "" -#: src/tools/dvdomatic.cc:321 +#: src/tools/dcpomatic.cc:319 msgid "Select film to open" msgstr "Sélectionner le film à ouvrir" -#: src/tools/dvdomatic.cc:305 +#: src/tools/dcpomatic.cc:303 #, fuzzy msgid "The directory %1 already exists." msgstr "Le dossier %s existe déjà." -#: src/tools/dvdomatic.cc:326 +#: src/tools/dcpomatic.cc:324 msgid "" "You did not select a folder. Make sure that you select a folder before " "clicking Open." diff --git a/src/tools/po/it_IT.po b/src/tools/po/it_IT.po index 1e0d8446a..13732129b 100644 --- a/src/tools/po/it_IT.po +++ b/src/tools/po/it_IT.po @@ -17,108 +17,107 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.5\n" -#: src/tools/dvdomatic.cc:179 +#: src/tools/dcpomatic.cc:177 msgid "&Analyse audio" msgstr "&Analizza audio" -#: src/tools/dvdomatic.cc:185 +#: src/tools/dcpomatic.cc:183 msgid "&Edit" msgstr "&Modifica" -#: src/tools/dvdomatic.cc:184 +#: src/tools/dcpomatic.cc:182 msgid "&File" msgstr "&File" -#: src/tools/dvdomatic.cc:187 +#: src/tools/dcpomatic.cc:185 msgid "&Help" msgstr "&Aiuto" -#: src/tools/dvdomatic.cc:186 +#: src/tools/dcpomatic.cc:184 msgid "&Jobs" msgstr "&Lavori" -#: src/tools/dvdomatic.cc:175 +#: src/tools/dcpomatic.cc:173 msgid "&Make DCP" msgstr "&Crea DCP" -#: src/tools/dvdomatic.cc:163 +#: src/tools/dcpomatic.cc:161 msgid "&Open..." msgstr "&Apri..." -#: src/tools/dvdomatic.cc:172 +#: src/tools/dcpomatic.cc:170 msgid "&Preferences..." msgstr "&Preferenze..." -#: src/tools/dvdomatic.cc:167 +#: src/tools/dcpomatic.cc:165 msgid "&Properties..." msgstr "&Proprieta'..." -#: src/tools/dvdomatic.cc:169 +#: src/tools/dcpomatic.cc:167 msgid "&Quit" msgstr "&Esci" -#: src/tools/dvdomatic.cc:165 +#: src/tools/dcpomatic.cc:163 msgid "&Save" msgstr "&Salva" -#: src/tools/dvdomatic.cc:176 +#: src/tools/dcpomatic.cc:174 msgid "&Send DCP to TMS" msgstr "&Invia DCP a TMS" -#: src/tools/dvdomatic.cc:419 +#: src/tools/dcpomatic.cc:417 msgid "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" msgstr "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" -#: src/tools/dvdomatic.cc:182 +#: src/tools/dcpomatic.cc:180 msgid "About" msgstr "Informazioni" -#: src/tools/dvdomatic.cc:538 +#: src/tools/dcpomatic.cc:527 msgid "Could not load film %1 (%2)" msgstr "Non posso caricare il film %s (%s)" -#: src/tools/dvdomatic.cc:341 +#: src/tools/dcpomatic.cc:339 #, c-format msgid "Could not open film at %s (%s)" msgstr "Non posso aprire il film in %s (%s)" -#: src/tools/dvdomatic.cc:289 src/tools/dvdomatic.cc:412 -#: src/tools/dvdomatic.cc:542 -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:77 +#: src/tools/dcpomatic.cc:75 msgid "Film changed" msgstr "Film modificato" -#: src/tools/dvdomatic.cc:418 +#: src/tools/dcpomatic.cc:416 msgid "Free, open-source DCP generation from almost anything." msgstr "Genera DCP da quasi tutto, free e open-source." -#: src/tools/dvdomatic.cc:162 +#: src/tools/dcpomatic.cc:160 msgid "New..." msgstr "Nuovo" -#: src/tools/dvdomatic.cc:177 +#: src/tools/dcpomatic.cc:175 msgid "S&how DCP" msgstr "&Mostra DCP" -#: src/tools/dvdomatic.cc:76 -#, c-format +#: src/tools/dcpomatic.cc:74 msgid "Save changes to film \"%s\" before closing?" msgstr "Salvare i cambiamenti del film \"%s\" prima di chiudere?" -#: src/tools/dvdomatic.cc:321 +#: src/tools/dcpomatic.cc:319 msgid "Select film to open" msgstr "Seleziona il film da aprire" -#: src/tools/dvdomatic.cc:305 +#: src/tools/dcpomatic.cc:303 msgid "The directory %1 already exists." msgstr "La directory %s esiste gia'." -#: src/tools/dvdomatic.cc:326 +#: src/tools/dcpomatic.cc:324 msgid "" "You did not select a folder. Make sure that you select a folder before " "clicking Open." diff --git a/src/tools/po/sv_SE.po b/src/tools/po/sv_SE.po index 8ae68853f..69706d647 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-04-09 10:12+0100\n" @@ -17,109 +17,108 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.5\n" -#: src/tools/dvdomatic.cc:179 +#: src/tools/dcpomatic.cc:177 msgid "&Analyse audio" msgstr "&Analysera audio" -#: src/tools/dvdomatic.cc:185 +#: src/tools/dcpomatic.cc:183 msgid "&Edit" msgstr "&Redigera" -#: src/tools/dvdomatic.cc:184 +#: src/tools/dcpomatic.cc:182 msgid "&File" msgstr "&Fil" -#: src/tools/dvdomatic.cc:187 +#: src/tools/dcpomatic.cc:185 msgid "&Help" msgstr "&Hjälp" -#: src/tools/dvdomatic.cc:186 +#: src/tools/dcpomatic.cc:184 msgid "&Jobs" msgstr "&Jobb" -#: src/tools/dvdomatic.cc:175 +#: src/tools/dcpomatic.cc:173 msgid "&Make DCP" msgstr "&Skapa DCP" -#: src/tools/dvdomatic.cc:163 +#: src/tools/dcpomatic.cc:161 msgid "&Open..." msgstr "&Öppna" -#: src/tools/dvdomatic.cc:172 +#: src/tools/dcpomatic.cc:170 msgid "&Preferences..." msgstr "&Inställningar" -#: src/tools/dvdomatic.cc:167 +#: src/tools/dcpomatic.cc:165 msgid "&Properties..." msgstr "&Egenskaper" -#: src/tools/dvdomatic.cc:169 +#: src/tools/dcpomatic.cc:167 msgid "&Quit" msgstr "&Avsluta" -#: src/tools/dvdomatic.cc:165 +#: src/tools/dcpomatic.cc:163 msgid "&Save" msgstr "&Spara" -#: src/tools/dvdomatic.cc:176 +#: src/tools/dcpomatic.cc:174 msgid "&Send DCP to TMS" msgstr "&Skicka DCP till TMS" -#: src/tools/dvdomatic.cc:419 +#: src/tools/dcpomatic.cc:417 msgid "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" msgstr "" "(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen" -#: src/tools/dvdomatic.cc:182 +#: src/tools/dcpomatic.cc:180 msgid "About" msgstr "Om" -#: src/tools/dvdomatic.cc:538 +#: src/tools/dcpomatic.cc:527 msgid "Could not load film %1 (%2)" msgstr "Kunde inte öppna filmen %1 (%2)" -#: src/tools/dvdomatic.cc:341 +#: src/tools/dcpomatic.cc:339 #, c-format msgid "Could not open film at %s (%s)" msgstr "Kunde inte öppna filmen vid %s (%s)" -#: src/tools/dvdomatic.cc:289 src/tools/dvdomatic.cc:412 -#: src/tools/dvdomatic.cc:542 -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:77 +#: src/tools/dcpomatic.cc:75 msgid "Film changed" msgstr "Film ändrad" -#: src/tools/dvdomatic.cc:418 +#: src/tools/dcpomatic.cc:416 msgid "Free, open-source DCP generation from almost anything." msgstr "" "Fri, öppen-källkodsprogramvara för DCP-generering från nästan vad som helst." -#: src/tools/dvdomatic.cc:162 +#: src/tools/dcpomatic.cc:160 msgid "New..." msgstr "Ny..." -#: src/tools/dvdomatic.cc:177 +#: src/tools/dcpomatic.cc:175 msgid "S&how DCP" msgstr "&Visa DCP" -#: src/tools/dvdomatic.cc:76 -#, fuzzy, c-format +#: src/tools/dcpomatic.cc:74 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:321 +#: src/tools/dcpomatic.cc:319 msgid "Select film to open" msgstr "Välj film att öppna" -#: src/tools/dvdomatic.cc:305 +#: src/tools/dcpomatic.cc:303 msgid "The directory %1 already exists." msgstr "Katalogen %1 finns redan." -#: src/tools/dvdomatic.cc:326 +#: src/tools/dcpomatic.cc:324 msgid "" "You did not select a folder. Make sure that you select a folder before " "clicking Open." diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc index 5e1cf49b4..af176ac18 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,19 @@ 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)); - 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 9f0f52152..466f29031 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -4,29 +4,29 @@ 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' + obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC' 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', 'servomatic_gui']: + for t in ['dcpomatic', 'dcpomatic_server']: obj = bld(features = 'cxx cxxprogram') - obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC' + obj.uselib = 'DCP CXML OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC' obj.includes = ['..'] - obj.use = ['libdvdomatic', 'libdvdomatic-wx'] + obj.use = ['libdcpomatic', 'libdcpomatic-wx'] obj.source = '%s.cc' % t if bld.env.TARGET_WINDOWS: - obj.source += ' ../../windows/dvdomatic.rc' + obj.source += ' ../../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/audio_dialog.cc b/src/wx/audio_dialog.cc index 39650d157..15d746839 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,40 +83,26 @@ AudioDialog::AudioDialog (wxWindow* parent) } void -AudioDialog::set_film (boost::shared_ptr<Film> f) +AudioDialog::set_film (shared_ptr<Film> f) { _film_changed_connection.disconnect (); _film_audio_analysis_succeeded_connection.disconnect (); _film = f; + for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { + _channel_checkbox[i]->Show (!_film->audio_mapping().dcp_to_content (static_cast<libdcp::Channel> (i)).empty()); + } + try_to_load_analysis (); - setup_channels (); _plot->set_gain (_film->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)); - 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(_film->name()).data())); } -void -AudioDialog::setup_channels () -{ - if (!_film->audio_stream()) { - return; - } - - AudioMapping m (_film->audio_stream()->channels ()); - - 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 () @@ -134,12 +119,10 @@ AudioDialog::try_to_load_analysis () _plot->set_analysis (a); - AudioMapping m (_film->audio_stream()->channels ()); - 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,11 +140,7 @@ AudioDialog::channel_clicked (wxCommandEvent& ev) assert (c < MAX_AUDIO_CHANNELS); - AudioMapping m (_film->audio_stream()->channels ()); - 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 @@ -171,11 +150,6 @@ AudioDialog::film_changed (Film::Property 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; } diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h index 514faeea0..db1d74f30 100644 --- a/src/wx/audio_dialog.h +++ b/src/wx/audio_dialog.h @@ -39,7 +39,6 @@ private: void type_clicked (wxCommandEvent &); void smoothing_changed (wxScrollEvent &); void try_to_load_analysis (); - void setup_channels (); boost::shared_ptr<Film> _film; AudioPlot* _plot; diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc new file mode 100644 index 000000000..a5dacdfc2 --- /dev/null +++ b/src/wx/audio_mapping_view.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 <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) + { + 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 (); + + wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); + s->Add (_grid, 1, wxEXPAND); + SetSizerAndFit (s); + + 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")); + } +} + +void +AudioMappingView::set_mapping (AudioMapping map) +{ + if (_grid->GetNumberRows ()) { + _grid->DeleteRows (0, _grid->GetNumberRows ()); + } + + list<AudioMapping::Channel> 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<AudioMapping::Channel>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) { + shared_ptr<const AudioContent> ac = i->content.lock (); + assert (ac); + _grid->SetCellValue (n, 0, wxString::Format (wxT("%s %d"), std_to_wx (ac->file().filename().string()).data(), i->index + 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; + } + + _grid->AutoSize (); +} + diff --git a/src/lib/options.h b/src/wx/audio_mapping_view.h index 0d2c07fd5..36429412f 100644 --- a/src/lib/options.h +++ b/src/wx/audio_mapping_view.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,27 +17,19 @@ */ -#ifndef DVDOMATIC_OPTIONS_H -#define DVDOMATIC_OPTIONS_H +#include <boost/signals2.hpp> +#include <wx/wx.h> +#include <wx/grid.h> -/** @file src/options.h - * @brief Options for a decoding operation. - */ - -class DecodeOptions +class AudioMappingView : public wxPanel { public: - DecodeOptions () - : decode_video (true) - , decode_audio (true) - , decode_subtitles (false) - , video_sync (true) - {} - - bool decode_video; - bool decode_audio; - bool decode_subtitles; - bool video_sync; -}; + AudioMappingView (wxWindow *); + + void set_mapping (AudioMapping); -#endif +private: + void left_click (wxGridEvent &); + + wxGrid* _grid; +}; diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc index cf44eb69f..2a6210164 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" diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index c32b03ec0..c3eebc015 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -18,7 +18,7 @@ */ /** @file src/config_dialog.cc - * @brief A dialogue to edit DVD-o-matic configuration. + * @brief A dialogue to edit DCP-o-matic configuration. */ #include <iostream> @@ -41,7 +41,7 @@ using namespace std; using boost::bind; ConfigDialog::ConfigDialog (wxWindow* parent) - : wxDialog (parent, wxID_ANY, _("DVD-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : wxDialog (parent, wxID_ANY, _("DCP-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { wxFlexGridSizer* table = new wxFlexGridSizer (3, 6, 6); table->AddGrowableCol (1, 1); @@ -58,7 +58,7 @@ ConfigDialog::ConfigDialog (wxWindow* parent) table->AddSpacer (0); table->AddSpacer (0); - wxStaticText* restart = add_label_to_sizer (table, this, _("(restart DVD-o-matic to see language changes)")); + wxStaticText* restart = add_label_to_sizer (table, this, _("(restart DCP-o-matic to see language changes)")); wxFont font = restart->GetFont(); font.SetStyle (wxFONTSTYLE_ITALIC); font.SetPointSize (font.GetPointSize() - 1); diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h index f6f3b3707..a2fc1f4b1 100644 --- a/src/wx/config_dialog.h +++ b/src/wx/config_dialog.h @@ -18,7 +18,7 @@ */ /** @file src/config_dialog.h - * @brief A dialogue to edit DVD-o-matic configuration. + * @brief A dialogue to edit DCP-o-matic configuration. */ #include <wx/wx.h> @@ -31,7 +31,7 @@ class DirPickerCtrl; class ServerDescription; /** @class ConfigDialog - * @brief A dialogue to edit DVD-o-matic configuration. + * @brief A dialogue to edit DCP-o-matic configuration. */ class ConfigDialog : public wxDialog { diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index a21782a6f..f909a8e2e 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -25,6 +25,7 @@ #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> @@ -37,6 +38,9 @@ #include "lib/filter.h" #include "lib/config.h" #include "lib/ffmpeg_decoder.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" @@ -45,6 +49,8 @@ #include "dci_metadata_dialog.h" #include "scaler.h" #include "audio_dialog.h" +#include "imagemagick_content_dialog.h" +#include "audio_mapping_view.h" using std::string; using std::cout; @@ -54,23 +60,26 @@ 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) { 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_content_panel (); + _notebook->AddPage (_content_panel, _("Content"), false); make_video_panel (); _notebook->AddPage (_video_panel, _("Video"), false); make_audio_panel (); @@ -78,15 +87,16 @@ FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent) make_subtitle_panel (); _notebook->AddPage (_subtitle_panel, _("Subtitles"), false); - 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 @@ -117,14 +127,8 @@ FilmEditor::make_film_panel () grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan); ++r; - add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), 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)); + _trust_content_headers = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header")); + grid->Add (_trust_content_headers, wxGBPosition (r, 0), wxGBSpan(1, 2)); ++r; add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), wxGBPosition (r, 0)); @@ -132,11 +136,6 @@ FilmEditor::make_film_panel () grid->Add (_dcp_content_type, wxGBPosition (r, 1)); ++r; - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Frame Rate"), 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"), wxGBPosition (r, 0)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); @@ -149,54 +148,40 @@ FilmEditor::make_film_panel () ++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); + grid->Add (_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"), wxGBPosition (r, 0))); + add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), wxGBPosition (r, 0)); _length = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + grid->Add (_length, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); ++r; { - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0))); + add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - video_control (add_label_to_sizer (s, _film_panel, _("Start"))); + add_label_to_sizer (s, _film_panel, _("Start")); _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_trim_start)); - video_control (add_label_to_sizer (s, _film_panel, _("End"))); + s->Add (_trim_start); + add_label_to_sizer (s, _film_panel, _("End")); _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_trim_end)); + s->Add (_trim_end); grid->Add (s, wxGBPosition (r, 1)); } ++r; - video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), wxGBPosition (r, 0))); + add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), wxGBPosition (r, 0)); _trim_type = new wxChoice (_film_panel, wxID_ANY); - grid->Add (video_control (_trim_type), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + grid->Add (_trim_type, 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; - - /* STILL-only stuff */ - { - still_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Duration"), 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"))); - grid->Add (s, wxGBPosition (r, 1)); - } + _ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B")); + grid->Add (_ab, wxGBPosition (r, 0)); ++r; vector<DCPContentType const *> const ct = DCPContentType::all (); @@ -216,46 +201,45 @@ FilmEditor::make_film_panel () 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); - _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); + _format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this); + _trust_content_headers->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_headers_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->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler (FilmEditor::content_activated), 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_edit->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_edit_clicked), 0, this); + _content_earlier->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this); + _content_later->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_later_clicked), 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); + _ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::ab_toggled), 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); + _ffmpeg_subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_subtitle_stream_changed), 0, this); + _ffmpeg_audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_audio_stream_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); } void @@ -304,21 +288,19 @@ FilmEditor::make_video_panel () /* VIDEO-only stuff */ { - video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0))); + add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), 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); grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); } ++r; - video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0))); + add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0)); _scaler = new wxChoice (_video_panel, wxID_ANY); - grid->Add (video_control (_scaler), wxGBPosition (r, 1)); + grid->Add (_scaler, wxGBPosition (r, 1)); ++r; vector<Scaler const *> const sc = Scaler::all (); @@ -349,13 +331,49 @@ FilmEditor::make_video_panel () _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, 100); _trim_end->SetRange (0, 100); _j2k_bandwidth->SetRange (50, 250); } void +FilmEditor::make_content_panel () +{ + _content_panel = new wxPanel (_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); + _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove")); + b->Add (_content_remove); + _content_edit = new wxButton (_content_panel, wxID_ANY, _("Edit...")); + b->Add (_content_edit); + _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Earlier")); + b->Add (_content_earlier); + _content_later = new wxButton (_content_panel, wxID_ANY, _("Later")); + b->Add (_content_later); + + s->Add (b, 0, wxALL, 4); + + _content_sizer->Add (s, 1, wxEXPAND | wxALL, 6); + } + + _content_information = new wxTextCtrl (_content_panel, wxID_ANY, wxT ("\n\n\n\n"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE); + _content_sizer->Add (_content_information, 1, wxEXPAND | wxALL, 6); +} + +void FilmEditor::make_audio_panel () { _audio_panel = new wxPanel (_notebook); @@ -370,48 +388,39 @@ FilmEditor::make_audio_panel () grid->AddSpacer (0); { - video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain"))); + add_label_to_sizer (grid, _audio_panel, _("Audio Gain")); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_gain = new wxSpinCtrl (_audio_panel); - s->Add (video_control (_audio_gain), 1); - video_control (add_label_to_sizer (s, _audio_panel, _("dB"))); + s->Add (_audio_gain, 1); + add_label_to_sizer (s, _audio_panel, _("dB")); _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate...")); - video_control (_audio_gain_calculate_button); s->Add (_audio_gain_calculate_button, 1, wxEXPAND); grid->Add (s); } { - video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay"))); + add_label_to_sizer (grid, _audio_panel, _("Audio Delay")); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _audio_delay = new wxSpinCtrl (_audio_panel); - s->Add (video_control (_audio_delay), 1); + 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"))); + add_label_to_sizer (s, _audio_panel, _("ms")); grid->Add (s); } - { - _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); - grid->Add (video_control (_use_content_audio)); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _audio_stream = new wxChoice (_audio_panel, wxID_ANY); - s->Add (video_control (_audio_stream), 1); - _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT ("")); - s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); - grid->Add (s, 1, wxEXPAND); - } - - _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))); - _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav")); - grid->Add (_external_audio[i], 1, wxEXPAND); - } + { + add_label_to_sizer (grid, _audio_panel, _("Audio Stream")); + wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _ffmpeg_audio_stream = new wxChoice (_audio_panel, wxID_ANY); + s->Add (_ffmpeg_audio_stream, 1); + _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT ("")); + s->Add (_audio, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); + grid->Add (s, 1, wxEXPAND); + } + _audio_mapping = new AudioMappingView (_audio_panel); + _audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6); + _audio_gain->SetRange (-60, 60); _audio_delay->SetRange (-1000, 1000); } @@ -426,27 +435,26 @@ FilmEditor::make_subtitle_panel () _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); - _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY); - grid->Add (video_control (_subtitle_stream)); + _ffmpeg_subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY); + grid->Add (_ffmpeg_subtitle_stream); { - video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"))); + add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset")); 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"))); + add_label_to_sizer (s, _subtitle_panel, _("pixels")); grid->Add (s); } { - video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"))); + add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale")); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); _subtitle_scale = new wxSpinCtrl (_subtitle_panel); - s->Add (video_control (_subtitle_scale)); - video_control (add_label_to_sizer (s, _subtitle_panel, _("%"))); + s->Add (_subtitle_scale); + add_label_to_sizer (s, _subtitle_panel, _("%")); grid->Add (s); } @@ -498,41 +506,25 @@ FilmEditor::bottom_crop_changed (wxCommandEvent &) _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 &) +FilmEditor::trust_content_headers_changed (wxCommandEvent &) { if (!_film) { return; } - _film->set_trust_content_header (_trust_content_header->GetValue ()); + _film->set_trust_content_headers (_trust_content_headers->GetValue ()); } /** Called when the DCP A/B switch has been toggled */ void -FilmEditor::dcp_ab_toggled (wxCommandEvent &) +FilmEditor::ab_toggled (wxCommandEvent &) { if (!_film) { return; } - _film->set_dcp_ab (_dcp_ab->GetValue ()); + _film->set_ab (_ab->GetValue ()); } /** Called when the name widget has been changed */ @@ -620,43 +612,20 @@ FilmEditor::film_changed (Film::Property p) case Film::NONE: break; case Film::CONTENT: - checked_set (_content, _film->content ()); - setup_visibility (); + setup_content (); setup_formats (); + setup_format (); setup_subtitle_control_sensitivity (); setup_streams (); setup_show_audio_sensitivity (); - setup_frame_rate_description (); + setup_length (); break; - case Film::TRUST_CONTENT_HEADER: - checked_set (_trust_content_header, _film->trust_content_header ()); - break; - case Film::SUBTITLE_STREAMS: - setup_subtitle_control_sensitivity (); - setup_streams (); - break; - case Film::CONTENT_AUDIO_STREAMS: - setup_streams (); - setup_show_audio_sensitivity (); - setup_frame_rate_description (); + case Film::TRUST_CONTENT_HEADERS: + checked_set (_trust_content_headers, _film->trust_content_headers ()); 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 (); + setup_format (); break; - } case Film::CROP: checked_set (_left_crop, _film->crop().left); checked_set (_right_crop, _film->crop().right); @@ -680,34 +649,12 @@ FilmEditor::film_changed (Film::Property p) 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 ()); + case Film::AB: + checked_set (_ab, _film->ab ()); break; case Film::SCALER: checked_set (_scaler, Scaler::as_index (_film->scaler ())); @@ -727,9 +674,6 @@ FilmEditor::film_changed (Film::Property p) 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 (); @@ -754,75 +698,125 @@ 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 (); - break; - case 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 (); - break; - case Film::SUBTITLE_STREAM: - if (_film->subtitle_stream()) { - checked_set (_subtitle_stream, _film->subtitle_stream()->to_string()); - } - break; - case Film::EXTERNAL_AUDIO: - { - 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 (); - 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; - } + 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 ()); + if (!done) { + checked_set (_dcp_frame_rate, -1); + } + + if (_film->video_frame_rate()) { + _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->video_frame_rate ()) != _film->dcp_frame_rate ()); } else { _best_dcp_frame_rate->Disable (); } - setup_frame_rate_description (); + break; + } + case Film::AUDIO_MAPPING: + _audio_mapping->set_mapping (_film->audio_mapping ()); + break; } } void +FilmEditor::film_content_changed (weak_ptr<Content> content, int property) +{ + if (!_film) { + /* We call this method ourselves (as well as using it as a signal handler) + so _film can be 0. + */ + return; + } + + if (property == FFmpegContentProperty::SUBTITLE_STREAMS) { + setup_subtitle_control_sensitivity (); + setup_streams (); + } else if (property == FFmpegContentProperty::AUDIO_STREAMS) { + setup_streams (); + setup_show_audio_sensitivity (); + } else if (property == VideoContentProperty::VIDEO_LENGTH || property == AudioContentProperty::AUDIO_LENGTH) { + setup_length (); + boost::shared_ptr<Content> c = content.lock (); + if (c && c == selected_content()) { + setup_content_information (); + } + } else if (property == FFmpegContentProperty::AUDIO_STREAM) { + if (_film->ffmpeg_audio_stream()) { + checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id)); + } + setup_dcp_name (); + setup_audio_details (); + setup_show_audio_sensitivity (); + } else if (property == FFmpegContentProperty::SUBTITLE_STREAM) { + if (_film->ffmpeg_subtitle_stream()) { + checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id)); + } + } +} + +void +FilmEditor::setup_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 (); +} + +void +FilmEditor::setup_length () +{ + stringstream s; + ContentVideoFrame const frames = _film->content_length (); + + if (frames && _film->video_frame_rate()) { + s << frames << " " << wx_to_std (_("frames")) << "; " << seconds_to_hms (frames / _film->video_frame_rate()); + } else if (frames) { + s << frames << " " << wx_to_std (_("frames")); + } + + _length->SetLabel (std_to_wx (s.str ())); + + if (frames) { + _trim_start->SetRange (0, frames); + _trim_end->SetRange (0, frames); + } +} + +void FilmEditor::setup_frame_rate_description () { wxString d; int lines = 0; - if (_film->source_frame_rate()) { - d << std_to_wx (FrameRateConversion (_film->source_frame_rate(), _film->dcp_frame_rate()).description); + if (_film->video_frame_rate()) { + d << std_to_wx (FrameRateConversion (_film->video_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 ()) { + if (_film->audio_frame_rate() && _film->audio_frame_rate() != _film->target_audio_sample_rate ()) { d << wxString::Format ( _("Audio will be resampled from %dHz to %dHz\n"), - _film->audio_stream()->sample_rate(), + _film->audio_frame_rate(), _film->target_audio_sample_rate() ); ++lines; @@ -870,12 +864,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) { @@ -891,7 +890,7 @@ FilmEditor::set_film (shared_ptr<Film> f) film_changed (Film::NAME); film_changed (Film::USE_DCI_NAME); film_changed (Film::CONTENT); - film_changed (Film::TRUST_CONTENT_HEADER); + film_changed (Film::TRUST_CONTENT_HEADERS); film_changed (Film::DCP_CONTENT_TYPE); film_changed (Film::FORMAT); film_changed (Film::CROP); @@ -899,26 +898,25 @@ FilmEditor::set_film (shared_ptr<Film> f) film_changed (Film::SCALER); film_changed (Film::TRIM_START); film_changed (Film::TRIM_END); + film_changed (Film::AB); 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::AUDIO_MAPPING); + + film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAMS); + film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAM); + film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAMS); + film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAM); + + setup_content_information (); } /** Updates the sensitivity of lots of widgets to a given value. @@ -934,31 +932,32 @@ FilmEditor::set_things_sensitive (bool s) _edit_dci_button->Enable (s); _format->Enable (s); _content->Enable (s); - _trust_content_header->Enable (s); + _trust_content_headers->Enable (s); + _content->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); + _ffmpeg_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); + _ab->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); setup_subtitle_control_sensitivity (); - setup_audio_control_sensitivity (); setup_show_audio_sensitivity (); + setup_content_button_sensitivity (); } /** Called when the `Edit filters' button has been clicked */ @@ -1005,40 +1004,6 @@ FilmEditor::audio_delay_changed (wxCommandEvent &) _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; -} - -void -FilmEditor::setup_visibility () -{ - 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); - } - - setup_notebook_size (); -} - void FilmEditor::setup_notebook_size () { @@ -1058,16 +1023,6 @@ FilmEditor::setup_notebook_size () } void -FilmEditor::still_duration_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - _film->set_still_duration (_still_duration->GetValue ()); -} - -void FilmEditor::trim_start_changed (wxCommandEvent &) { if (!_film) { @@ -1117,20 +1072,7 @@ FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &) void FilmEditor::setup_formats () { - ContentType c = VIDEO; - - if (_film) { - c = _film->content_type (); - } - - _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); - } - } + _formats = Format::all (); _format->Clear (); for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) { @@ -1155,7 +1097,7 @@ FilmEditor::setup_subtitle_control_sensitivity () { bool h = false; if (_generally_sensitive && _film) { - h = !_film->subtitle_streams().empty(); + h = !_film->ffmpeg_subtitle_streams().empty(); } _with_subtitles->Enable (h); @@ -1165,27 +1107,12 @@ FilmEditor::setup_subtitle_control_sensitivity () j = _film->with_subtitles (); } - _subtitle_stream->Enable (j); + _ffmpeg_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); - } -} - -void FilmEditor::use_dci_name_toggled (wxCommandEvent &) { if (!_film) { @@ -1211,73 +1138,84 @@ FilmEditor::edit_dci_button_clicked (wxCommandEvent &) void FilmEditor::setup_streams () { - _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) { + return; + } + + _ffmpeg_audio_stream->Clear (); + vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams (); + for (vector<FFmpegAudioStream>::iterator i = a.begin(); i != a.end(); ++i) { + _ffmpeg_audio_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id)))); } - if (_film->use_content_audio() && _film->audio_stream()) { - checked_set (_audio_stream, _film->audio_stream()->to_string()); + if (_film->ffmpeg_audio_stream()) { + checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id)); } - _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 ()))); + _ffmpeg_subtitle_stream->Clear (); + vector<FFmpegSubtitleStream> s = _film->ffmpeg_subtitle_streams (); + for (vector<FFmpegSubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) { + _ffmpeg_subtitle_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id)))); } - if (_film->subtitle_stream()) { - checked_set (_subtitle_stream, _film->subtitle_stream()->to_string()); + + if (_film->ffmpeg_subtitle_stream()) { + checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id)); } else { - _subtitle_stream->SetSelection (wxNOT_FOUND); + _ffmpeg_subtitle_stream->SetSelection (wxNOT_FOUND); } } void -FilmEditor::audio_stream_changed (wxCommandEvent &) +FilmEditor::ffmpeg_audio_stream_changed (wxCommandEvent &) { if (!_film) { return; } - _film->set_content_audio_stream ( - audio_stream_factory ( - string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())), - Film::state_version - ) - ); + vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams (); + vector<FFmpegAudioStream>::iterator i = a.begin (); + string const s = string_client_data (_ffmpeg_audio_stream->GetClientObject (_ffmpeg_audio_stream->GetSelection ())); + while (i != a.end() && lexical_cast<string> (i->id) != s) { + ++i; + } + + if (i != a.end ()) { + _film->set_ffmpeg_audio_stream (*i); + } } void -FilmEditor::subtitle_stream_changed (wxCommandEvent &) +FilmEditor::ffmpeg_subtitle_stream_changed (wxCommandEvent &) { if (!_film) { return; } - _film->set_subtitle_stream ( - subtitle_stream_factory ( - string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())), - Film::state_version - ) - ); + vector<FFmpegSubtitleStream> a = _film->ffmpeg_subtitle_streams (); + vector<FFmpegSubtitleStream>::iterator i = a.begin (); + string const s = string_client_data (_ffmpeg_subtitle_stream->GetClientObject (_ffmpeg_subtitle_stream->GetSelection ())); + while (i != a.end() && lexical_cast<string> (i->id) != s) { + ++i; + } + + if (i != a.end ()) { + _film->set_ffmpeg_subtitle_stream (*i); + } } void FilmEditor::setup_audio_details () { - if (!_film->content_audio_stream()) { + if (!_film->ffmpeg_audio_stream()) { _audio->SetLabel (wxT ("")); } else { wxString s; - if (_film->audio_stream()->channels() == 1) { + if (_film->audio_channels() == 1) { s << _("1 channel"); } else { - s << _film->audio_stream()->channels () << wxT (" ") << _("channels"); + s << _film->audio_channels() << wxT (" ") << _("channels"); } - s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz"); + s << wxT (", ") << _film->audio_frame_rate() << _("Hz"); _audio->SetLabel (s); } @@ -1291,23 +1229,6 @@ FilmEditor::active_jobs_changed (bool a) } void -FilmEditor::use_audio_changed (wxCommandEvent &) -{ - _film->set_use_content_audio (_use_content_audio->GetValue()); -} - -void -FilmEditor::external_audio_changed (wxCommandEvent &) -{ - vector<string> a; - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - a.push_back (wx_to_std (_external_audio[i]->GetPath())); - } - - _film->set_external_audio (a); -} - -void FilmEditor::setup_dcp_name () { string s = _film->dcp_name (true); @@ -1339,7 +1260,7 @@ FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &) return; } - _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ())); + _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->video_frame_rate ())); } void @@ -1349,24 +1270,181 @@ FilmEditor::setup_show_audio_sensitivity () } void +FilmEditor::setup_content () +{ + 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 (); + + ContentList content = _film->content (); + for (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); + } + } + + if (selected_summary.empty () && !content.empty ()) { + /* Select the first item of content if non was selected before */ + _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } +} + +void +FilmEditor::content_add_clicked (wxCommandEvent &) +{ + wxFileDialog* d = new wxFileDialog (this); + int const r = d->ShowModal (); + d->Destroy (); + + if (r != wxID_OK) { + return; + } + + boost::filesystem::path p (wx_to_std (d->GetPath())); + + if (ImageMagickContent::valid_file (p)) { + _film->add_content (shared_ptr<ImageMagickContent> (new ImageMagickContent (p))); + } else if (SndfileContent::valid_file (p)) { + _film->add_content (shared_ptr<SndfileContent> (new SndfileContent (p))); + } else { + _film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (p))); + } + +} + +void +FilmEditor::content_remove_clicked (wxCommandEvent &) +{ + shared_ptr<Content> c = selected_content (); + if (c) { + _film->remove_content (c); + } +} + +void +FilmEditor::content_activated (wxListEvent& ev) +{ + ContentList c = _film->content (); + assert (ev.GetIndex() >= 0 && size_t (ev.GetIndex()) < c.size ()); + + edit_content (c[ev.GetIndex()]); +} + +void +FilmEditor::content_edit_clicked (wxCommandEvent &) +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + edit_content (c); +} + +void +FilmEditor::edit_content (shared_ptr<Content> c) +{ + shared_ptr<ImageMagickContent> im = dynamic_pointer_cast<ImageMagickContent> (c); + if (im) { + ImageMagickContentDialog* d = new ImageMagickContentDialog (this, im); + d->ShowModal (); + d->Destroy (); + + im->set_video_length (d->video_length() * 24); + } +} + +void +FilmEditor::content_earlier_clicked (wxCommandEvent &) +{ + shared_ptr<Content> c = selected_content (); + if (c) { + _film->move_content_earlier (c); + } +} + +void +FilmEditor::content_later_clicked (wxCommandEvent &) +{ + shared_ptr<Content> c = selected_content (); + if (c) { + _film->move_content_later (c); + } +} + +void +FilmEditor::content_selection_changed (wxListEvent &) +{ + setup_content_button_sensitivity (); + setup_content_information (); +} + +void +FilmEditor::setup_content_information () +{ + shared_ptr<Content> c = selected_content (); + if (!c) { + _content_information->SetValue (wxT ("")); + return; + } + + _content_information->SetValue (std_to_wx (c->information ())); +} + +void +FilmEditor::setup_content_button_sensitivity () +{ + _content_add->Enable (_generally_sensitive); + + shared_ptr<Content> selection = selected_content (); + + _content_edit->Enable (selection && _generally_sensitive && dynamic_pointer_cast<ImageMagickContent> (selection)); + _content_remove->Enable (selection && _generally_sensitive); + _content_earlier->Enable (selection && _generally_sensitive); + _content_later->Enable (selection && _generally_sensitive); +} + +shared_ptr<Content> +FilmEditor::selected_content () +{ + int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s == -1) { + return shared_ptr<Content> (); + } + + ContentList c = _film->content (); + if (s < 0 || size_t (s) >= c.size ()) { + return shared_ptr<Content> (); + } + + return c[s]; +} + +void FilmEditor::setup_scaling_description () { wxString d; 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; } Crop const crop = _film->crop (); if (crop.left || crop.right || crop.top || crop.bottom) { - libdcp::Size const cropped = _film->cropped_size (_film->size ()); + libdcp::Size const cropped = _film->cropped_size (_film->video_size ()); d << wxString::Format ( _("Cropped to %dx%d (%.2f:1)\n"), cropped.width, cropped.height, diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index e2a4d5836..ffffc1e76 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,11 @@ #include "lib/film.h" class wxNotebook; +class wxListCtrl; +class wxListEvent; class Film; class AudioDialog; +class AudioMappingView; /** @class FilmEditor * @brief A wx widget to edit a film's metadata, and perform various functions. @@ -41,12 +44,12 @@ public: FilmEditor (boost::shared_ptr<Film>, wxWindow *); void set_film (boost::shared_ptr<Film>); - void setup_visibility (); boost::signals2::signal<void (std::string)> FileChanged; private: void make_film_panel (); + void make_content_panel (); void make_video_panel (); void make_audio_panel (); void make_subtitle_panel (); @@ -60,14 +63,21 @@ 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 trust_content_headers_changed (wxCommandEvent &); + void content_selection_changed (wxListEvent &); + void content_activated (wxListEvent &); + void content_add_clicked (wxCommandEvent &); + void content_remove_clicked (wxCommandEvent &); + void content_edit_clicked (wxCommandEvent &); + void content_earlier_clicked (wxCommandEvent &); + void content_later_clicked (wxCommandEvent &); + void imagemagick_video_length_changed (wxCommandEvent &); void format_changed (wxCommandEvent &); void trim_start_changed (wxCommandEvent &); void trim_end_changed (wxCommandEvent &); void trim_type_changed (wxCommandEvent &); void dcp_content_type_changed (wxCommandEvent &); - void dcp_ab_toggled (wxCommandEvent &); + void ab_toggled (wxCommandEvent &); void scaler_changed (wxCommandEvent &); void audio_gain_changed (wxCommandEvent &); void audio_gain_calculate_button_clicked (wxCommandEvent &); @@ -78,24 +88,19 @@ 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 ffmpeg_audio_stream_changed (wxCommandEvent &); + void ffmpeg_subtitle_stream_changed (wxCommandEvent &); void dcp_frame_rate_changed (wxCommandEvent &); void best_dcp_frame_rate_clicked (wxCommandEvent &); + void edit_filters_clicked (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_subtitle_control_sensitivity (); - void setup_audio_control_sensitivity (); void setup_streams (); void setup_audio_details (); void setup_dcp_name (); @@ -103,15 +108,21 @@ private: void setup_scaling_description (); void setup_notebook_size (); void setup_frame_rate_description (); + void setup_content (); + void setup_format (); + void setup_length (); + void setup_content_information (); + void setup_content_button_sensitivity (); - wxControl* video_control (wxControl *); - wxControl* still_control (wxControl *); - void active_jobs_changed (bool); + boost::shared_ptr<Content> selected_content (); + void edit_content (boost::shared_ptr<Content>); wxNotebook* _notebook; wxPanel* _film_panel; wxSizer* _film_sizer; + wxPanel* _content_panel; + wxSizer* _content_sizer; wxPanel* _video_panel; wxSizer* _video_sizer; wxPanel* _audio_panel; @@ -125,66 +136,50 @@ private: wxTextCtrl* _name; wxStaticText* _dcp_name; wxCheckBox* _use_dci_name; + wxListCtrl* _content; + wxButton* _content_add; + wxButton* _content_remove; + wxButton* _content_edit; + wxButton* _content_earlier; + wxButton* _content_later; + wxTextCtrl* _content_information; wxButton* _edit_dci_button; - /** The Film's format */ wxChoice* _format; + wxStaticText* _format_description; + wxCheckBox* _trust_content_headers; 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; + wxChoice* _ffmpeg_audio_stream; + AudioMappingView* _audio_mapping; wxCheckBox* _with_subtitles; - wxChoice* _subtitle_stream; + wxChoice* _ffmpeg_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; 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; + wxCheckBox* _ab; std::vector<Format const *> _formats; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 4f2985a06..e9a1a574b 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -28,13 +28,16 @@ #include "lib/format.h" #include "lib/util.h" #include "lib/job_manager.h" -#include "lib/options.h" #include "lib/subtitle.h" #include "lib/image.h" #include "lib/scaler.h" #include "lib/exceptions.h" #include "lib/examine_content_job.h" #include "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" @@ -45,6 +48,8 @@ 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) @@ -114,43 +119,22 @@ FilmViewer::film_changed (Film::Property p) 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->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4)); - _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this)); - _decoders.video->set_subtitle_stream (_film->subtitle_stream()); calculate_sizes (); - get_frame (); - _panel->Refresh (); - _slider->Show (_film->content_type() == VIDEO); - _play_button->Show (_film->content_type() == VIDEO); - _v_sizer->Layout (); + wxScrollEvent ev; + slider_moved (ev); break; } case Film::WITH_SUBTITLES: case Film::SUBTITLE_OFFSET: case Film::SUBTITLE_SCALE: + raw_to_display (); + _panel->Refresh (); + _panel->Update (); + break; case Film::SCALER: case Film::FILTERS: - update_from_raw (); - break; - case Film::SUBTITLE_STREAM: - if (_decoders.video) { - _decoders.video->set_subtitle_stream (_film->subtitle_stream ()); - } + case Film::CROP: + update_from_decoder (); break; default: break; @@ -163,7 +147,7 @@ FilmViewer::set_film (shared_ptr<Film> f) if (_film == f) { return; } - + _film = f; _raw_frame.reset (); @@ -175,20 +159,28 @@ FilmViewer::set_film (shared_ptr<Film> f) return; } + _player = f->player (); + _player->disable_audio (); + /* Don't disable subtitles here as we may need them, and it's nice to be able to turn them + on and off without needing obtain a new Player. + */ + + _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4)); + _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::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 || _player->seek (_player->last_video_time ())) { return; } @@ -200,7 +192,7 @@ FilmViewer::decoder_changed () void FilmViewer::timer (wxTimerEvent &) { - if (!_film || !_decoders.video) { + if (!_player) { return; } @@ -209,8 +201,8 @@ FilmViewer::timer (wxTimerEvent &) get_frame (); - if (_film->length()) { - int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate()); + if (_film->video_length()) { + int const new_slider_position = 4096 * _player->last_video_time() / (_film->video_length() / _film->video_frame_rate()); if (new_slider_position != _slider->GetValue()) { _slider->SetValue (new_slider_position); } @@ -266,11 +258,11 @@ FilmViewer::paint_panel (wxPaintEvent &) void FilmViewer::slider_moved (wxScrollEvent &) { - if (!_film || !_film->length() || !_decoders.video) { + if (!_film || !_player) { return; } - - if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) { + + if (_player->seek (_slider->GetValue() * _film->video_length() / (4096 * _film->video_frame_rate()))) { return; } @@ -308,7 +300,7 @@ FilmViewer::raw_to_display () return; } - boost::shared_ptr<const Image> input = _raw_frame; + shared_ptr<const Image> input = _raw_frame; pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); if (!s.second.empty ()) { @@ -324,7 +316,7 @@ FilmViewer::raw_to_display () when working out the scale that we are applying. */ - Size const cropped_size = _film->cropped_size (_film->size ()); + Size const cropped_size = _film->cropped_size (_film->video_size ()); Rect tx = subtitle_transformed_area ( float (_film_size.width) / cropped_size.width, @@ -343,7 +335,7 @@ FilmViewer::raw_to_display () void FilmViewer::calculate_sizes () { - if (!_film) { + if (!_film || !_player) { return; } @@ -393,7 +385,7 @@ FilmViewer::check_play_state () } if (_play_button->GetValue()) { - _timer.Start (1000 / _film->source_frame_rate()); + _timer.Start (1000 / _film->video_frame_rate()); } else { _timer.Stop (); } @@ -409,7 +401,7 @@ FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subti _got_frame = true; - double const fps = _decoders.video->frames_per_second (); + double const fps = _film->video_frame_rate (); _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps)))); double w = t; @@ -423,21 +415,24 @@ 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; } - + 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. */ @@ -473,13 +468,23 @@ FilmViewer::active_jobs_changed (bool a) } void +FilmViewer::film_content_changed (weak_ptr<Content>, int p) +{ + if (p == VideoContentProperty::VIDEO_LENGTH) { + /* Force an update to our frame */ + wxScrollEvent ev; + slider_moved (ev); + } +} + +void FilmViewer::back_clicked (wxCommandEvent &) { - if (!_decoders.video) { + if (!_player) { return; } - _decoders.video->seek_back (); + _player->seek_back (); get_frame (); _panel->Refresh (); _panel->Update (); @@ -488,11 +493,11 @@ FilmViewer::back_clicked (wxCommandEvent &) void FilmViewer::forward_clicked (wxCommandEvent &) { - if (!_decoders.video) { + if (!_player) { return; } - _decoders.video->seek_forward (); + _player->seek_forward (); get_frame (); _panel->Refresh (); _panel->Update (); diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index ed5874fbc..02d862ca0 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -23,7 +23,6 @@ #include <wx/wx.h> #include "lib/film.h" -#include "lib/decoder_factory.h" class wxToggleButton; class FFmpegPlayer; @@ -33,6 +32,25 @@ 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 and subtitle from the decoder (_raw_frame and _raw_sub) + * 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. It also + * blits the subtitle, if required. + * + * 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,6 +61,7 @@ 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 &); @@ -52,7 +71,7 @@ private: 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 +79,7 @@ private: void forward_clicked (wxCommandEvent &); boost::shared_ptr<Film> _film; + boost::shared_ptr<Player> _player; wxSizer* _v_sizer; wxPanel* _panel; @@ -71,7 +91,6 @@ 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; diff --git a/src/wx/imagemagick_content_dialog.cc b/src/wx/imagemagick_content_dialog.cc new file mode 100644 index 000000000..726e4b8e2 --- /dev/null +++ b/src/wx/imagemagick_content_dialog.cc @@ -0,0 +1,62 @@ +/* + 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; + +ImageMagickContentDialog::ImageMagickContentDialog (wxWindow* parent, shared_ptr<ImageMagickContent> content) + : wxDialog (parent, wxID_ANY, _("Image")) +{ + wxFlexGridSizer* grid = new wxFlexGridSizer (3, 6, 6); + grid->AddGrowableCol (1, 1); + + { + add_label_to_sizer (grid, this, (_("Duration"))); + 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")); + 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); +} + +int +ImageMagickContentDialog::video_length () const +{ + return _video_length->GetValue (); +} diff --git a/src/wx/imagemagick_content_dialog.h b/src/wx/imagemagick_content_dialog.h new file mode 100644 index 000000000..2fa955929 --- /dev/null +++ b/src/wx/imagemagick_content_dialog.h @@ -0,0 +1,34 @@ +/* + 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> + +class wxSpinCtrl; +class ImageMagickContent; + +class ImageMagickContentDialog : public wxDialog +{ +public: + ImageMagickContentDialog (wxWindow *, boost::shared_ptr<ImageMagickContent>); + + int video_length () const; + +private: + wxSpinCtrl* _video_length; +}; diff --git a/src/wx/job_manager_view.cc b/src/wx/job_manager_view.cc index f7d2315cc..a7788ddd0 100644 --- a/src/wx/job_manager_view.cc +++ b/src/wx/job_manager_view.cc @@ -135,7 +135,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]); @@ -149,7 +149,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/po/es_ES.po b/src/wx/po/es_ES.po index 56c0856bd..a193325e6 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-04-02 19:08-0500\n" @@ -22,7 +22,7 @@ msgid "%" msgstr "%" #: src/wx/config_dialog.cc:61 -msgid "(restart DVD-o-matic to see language changes)" +msgid "(restart DCP-o-matic to see language changes)" msgstr "" #: src/wx/film_editor.cc:1276 @@ -141,17 +141,17 @@ msgid "DCP Name" msgstr "Nombre DCP" #: src/wx/wx_util.cc:61 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +msgid "DCP-o-matic" +msgstr "DCP-o-matic" #: src/wx/config_dialog.cc:44 -msgid "DVD-o-matic Preferences" -msgstr "Preferencias DVD-o-matic" +msgid "DCP-o-matic Preferences" +msgstr "Preferencias DCP-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:102 msgid "Default DCI name details" diff --git a/src/wx/po/fr_FR.po b/src/wx/po/fr_FR.po index c7ef31f5a..36ae4a925 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-03-20 00:34+0100\n" @@ -21,7 +21,7 @@ msgid "%" msgstr "%" #: src/wx/config_dialog.cc:61 -msgid "(restart DVD-o-matic to see language changes)" +msgid "(restart DCP-o-matic to see language changes)" msgstr "" #: src/wx/film_editor.cc:1276 @@ -140,17 +140,17 @@ msgid "DCP Name" msgstr "Nom du DCP" #: src/wx/wx_util.cc:61 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +msgid "DCP-o-matic" +msgstr "DCP-o-matic" #: src/wx/config_dialog.cc:44 -msgid "DVD-o-matic Preferences" -msgstr "Préférences DVD-o-matic" +msgid "DCP-o-matic Preferences" +msgstr "Préférences 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:102 msgid "Default DCI name details" diff --git a/src/wx/po/it_IT.po b/src/wx/po/it_IT.po index c730a7ff7..f53c40b97 100644 --- a/src/wx/po/it_IT.po +++ b/src/wx/po/it_IT.po @@ -22,8 +22,8 @@ msgid "%" msgstr "%" #: src/wx/config_dialog.cc:61 -msgid "(restart DVD-o-matic to see language changes)" -msgstr "(riavviare DVD-o-matic per vedere i cambiamenti di lingua)" +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" @@ -141,17 +141,17 @@ msgid "DCP Name" msgstr "Nome del DCP" #: src/wx/wx_util.cc:61 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +msgid "DCP-o-matic" +msgstr "DCP-o-matic" #: src/wx/config_dialog.cc:44 -msgid "DVD-o-matic Preferences" -msgstr "Preferenze DVD-o-matic" +msgid "DCP-o-matic Preferences" +msgstr "Preferenze DCP-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:102 msgid "Default DCI name details" diff --git a/src/wx/po/sv_SE.po b/src/wx/po/sv_SE.po index 4127d77f8..9ed7ee2be 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-04-22 15:06+0100\n" "PO-Revision-Date: 2013-04-09 10:13+0100\n" @@ -22,8 +22,8 @@ msgid "%" msgstr "%" #: src/wx/config_dialog.cc:61 -msgid "(restart DVD-o-matic to see language changes)" -msgstr "(starta om DVD-o-matic för att se språkändringar)" +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" @@ -141,17 +141,17 @@ msgid "DCP Name" msgstr "DCP Namn" #: src/wx/wx_util.cc:61 -msgid "DVD-o-matic" -msgstr "DVD-o-matic" +msgid "DCP-o-matic" +msgstr "DCP-o-matic" #: src/wx/config_dialog.cc:44 -msgid "DVD-o-matic Preferences" -msgstr "DVD-o-matic Inställningar" +msgid "DCP-o-matic Preferences" +msgstr "DCP-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:102 msgid "Default DCI name details" diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index 44a713dc3..06e245832 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -50,6 +50,7 @@ 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 0 if (_film->length()) { _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get()))); FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate()); @@ -62,6 +63,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) _frames->SetLabel (_("unknown")); _disk->SetLabel (_("unknown")); } +#endif wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); overall_sizer->Add (table, 0, wxALL, 6); @@ -85,9 +87,9 @@ PropertiesDialog::frames_already_encoded () const return ""; } - if (_film->length()) { - /* XXX: encoded_frames() should check which frames have been encoded */ - u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)"; - } +// if (_film->length()) { +// /* XXX: encoded_frames() should check which frames have been encoded */ +// u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)"; +// } return u.str (); } diff --git a/src/wx/wscript b/src/wx/wscript index 42bb8ca88..09bf40393 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -5,6 +5,7 @@ import i18n sources = """ audio_dialog.cc + audio_mapping_view.cc audio_plot.cc config_dialog.cc dci_metadata_dialog.cc @@ -14,6 +15,7 @@ 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 @@ -32,18 +34,18 @@ 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' - 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 77f5da293..e0d7d843f 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -58,7 +58,7 @@ add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, wxGBPos 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 (); } |
