diff options
Diffstat (limited to 'src')
115 files changed, 3049 insertions, 1452 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index bfe0ed61f..fc0abe0a3 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -18,6 +18,7 @@ */ #include "audio_analysis.h" +#include "audio_buffers.h" #include "analyse_audio_job.h" #include "compose.hpp" #include "film.h" @@ -69,13 +70,13 @@ AnalyseAudioJob::run () player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2)); - _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points); + int64_t const len = _film->length().frames (_film->audio_frame_rate()); + _samples_per_point = max (int64_t (1), len / _num_points); _current.resize (_film->audio_channels ()); _analysis.reset (new AudioAnalysis (_film->audio_channels ())); _done = 0; - OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ()); while (!player->pass ()) { set_progress (double (_done) / len); } @@ -87,7 +88,7 @@ AnalyseAudioJob::run () } void -AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time) +AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, DCPTime) { for (int i = 0; i < b->frames(); ++i) { for (int j = 0; j < b->channels(); ++j) { diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 3e376634c..0a0be8fa0 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -20,6 +20,7 @@ #include "job.h" #include "audio_analysis.h" #include "types.h" +#include "dcpomatic_time.h" class AudioBuffers; class AudioContent; @@ -34,10 +35,10 @@ public: void run (); private: - void audio (boost::shared_ptr<const AudioBuffers>, Time); + void audio (boost::shared_ptr<const AudioBuffers>, DCPTime); boost::weak_ptr<AudioContent> _content; - OutputAudioFrame _done; + int64_t _done; int64_t _samples_per_point; std::vector<AudioPoint> _current; diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index b96300e15..6da5afa0c 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203; int const AudioContentProperty::AUDIO_DELAY = 204; int const AudioContentProperty::AUDIO_MAPPING = 205; -AudioContent::AudioContent (shared_ptr<const Film> f, Time s) +AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s) : Content (f, s) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) @@ -149,5 +149,11 @@ AudioContent::audio_analysis_path () const string AudioContent::technical_summary () const { - return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate()); + return String::compose ( + "audio: channels %1, length %2, raw rate %3, out rate %4", + audio_channels(), + audio_length().seconds(), + content_audio_frame_rate(), + output_audio_frame_rate() + ); } diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index d30db02d7..cecc8f13d 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -43,7 +43,7 @@ class AudioContent : public virtual Content public: typedef int64_t Frame; - AudioContent (boost::shared_ptr<const Film>, Time); + AudioContent (boost::shared_ptr<const Film>, DCPTime); AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path); AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); @@ -52,7 +52,7 @@ public: std::string technical_summary () const; virtual int audio_channels () const = 0; - virtual AudioContent::Frame audio_length () const = 0; + virtual ContentTime audio_length () const = 0; virtual int content_audio_frame_rate () const = 0; virtual int output_audio_frame_rate () const = 0; virtual AudioMapping audio_mapping () const = 0; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index c0ef02f65..32453cc13 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -22,6 +22,7 @@ #include "exceptions.h" #include "log.h" #include "resampler.h" +#include "util.h" #include "i18n.h" @@ -32,27 +33,52 @@ using std::cout; using boost::optional; using boost::shared_ptr; -AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content) - : Decoder (film) - , _audio_content (content) - , _audio_position (0) +AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content) + : _audio_content (content) { + if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) { + _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ())); + } +} + +/** Audio timestamping is made hard by many factors, but the final nail in the coffin is resampling. + * We have to assume that we are feeding continuous data into the resampler, and so we get continuous + * data out. Hence we do the timestamping here, post-resampler, just by counting samples. + * + * The time is passed in here so that after a seek we can set up our _audio_position. The + * time is ignored once this has been done. + */ +void +AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time) +{ + if (_resampler) { + data = _resampler->run (data); + } + if (!_audio_position) { + _audio_position = time; + } + + _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (_audio_position.get (), data))); + _audio_position = _audio_position.get() + ContentTime (data->frames (), _audio_content->output_audio_frame_rate ()); } void -AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame) +AudioDecoder::flush () { - Audio (data, frame); - _audio_position = frame + data->frames (); + if (!_resampler) { + return; + } + + shared_ptr<const AudioBuffers> b = _resampler->flush (); + if (b) { + _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (_audio_position.get (), b))); + _audio_position = _audio_position.get() + ContentTime (b->frames (), _audio_content->output_audio_frame_rate ()); + } } -/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio. - * The player needs to know that there is no audio otherwise it will keep trying to - * pass() the decoder to get it to emit audio. - */ -bool -AudioDecoder::has_audio () const +void +AudioDecoder::seek (ContentTime, bool) { - return _audio_content->audio_channels () > 0; + _audio_position.reset (); } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index ab6c4b8a9..35d9f3560 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -27,8 +27,10 @@ #include "decoder.h" #include "content.h" #include "audio_content.h" +#include "decoded.h" class AudioBuffers; +class Resampler; /** @class AudioDecoder. * @brief Parent class for audio decoders. @@ -36,18 +38,22 @@ class AudioBuffers; class AudioDecoder : public virtual Decoder { public: - AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>); - - bool has_audio () const; - - /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio; - + AudioDecoder (boost::shared_ptr<const AudioContent>); + + boost::shared_ptr<const AudioContent> audio_content () const { + return _audio_content; + } + + void seek (ContentTime time, bool accurate); + protected: - void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); + void audio (boost::shared_ptr<const AudioBuffers>, ContentTime); + void flush (); + boost::shared_ptr<const AudioContent> _audio_content; - AudioContent::Frame _audio_position; + boost::shared_ptr<Resampler> _resampler; + boost::optional<ContentTime> _audio_position; }; #endif diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index ae7702998..1db827046 100644 --- a/src/lib/audio_mapping.cc +++ b/src/lib/audio_mapping.cc @@ -68,11 +68,11 @@ AudioMapping::make_default () if (_content_channels == 1) { /* Mono -> Centre */ - set (0, libdcp::CENTRE, 1); + set (0, dcp::CENTRE, 1); } else { /* 1:1 mapping */ for (int i = 0; i < _content_channels; ++i) { - set (i, static_cast<libdcp::Channel> (i), 1); + set (i, static_cast<dcp::Channel> (i), 1); } } } @@ -85,14 +85,14 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version /* Old-style: on/off mapping */ list<cxml::NodePtr> const c = node->node_children ("Map"); for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) { - set ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")), 1); + set ((*i)->number_child<int> ("ContentIndex"), static_cast<dcp::Channel> ((*i)->number_child<int> ("DCP")), 1); } } else { list<cxml::NodePtr> const c = node->node_children ("Gain"); for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) { set ( (*i)->number_attribute<int> ("Content"), - static_cast<libdcp::Channel> ((*i)->number_attribute<int> ("DCP")), + static_cast<dcp::Channel> ((*i)->number_attribute<int> ("DCP")), lexical_cast<float> ((*i)->content ()) ); } @@ -100,13 +100,13 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version } void -AudioMapping::set (int c, libdcp::Channel d, float g) +AudioMapping::set (int c, dcp::Channel d, float g) { _gain[c][d] = g; } float -AudioMapping::get (int c, libdcp::Channel d) const +AudioMapping::get (int c, dcp::Channel d) const { return _gain[c][d]; } @@ -121,7 +121,7 @@ AudioMapping::as_xml (xmlpp::Node* node) const xmlpp::Element* t = node->add_child ("Gain"); t->set_attribute ("Content", lexical_cast<string> (c)); t->set_attribute ("DCP", lexical_cast<string> (d)); - t->add_child_text (lexical_cast<string> (get (c, static_cast<libdcp::Channel> (d)))); + t->add_child_text (lexical_cast<string> (get (c, static_cast<dcp::Channel> (d)))); } } } diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h index 26087bfff..f3096764c 100644 --- a/src/lib/audio_mapping.h +++ b/src/lib/audio_mapping.h @@ -21,7 +21,7 @@ #define DCPOMATIC_AUDIO_MAPPING_H #include <vector> -#include <libdcp/types.h> +#include <dcp/types.h> #include <boost/shared_ptr.hpp> namespace xmlpp { @@ -50,8 +50,8 @@ public: void make_default (); - void set (int, libdcp::Channel, float); - float get (int, libdcp::Channel) const; + void set (int, dcp::Channel, float); + float get (int, dcp::Channel) const; int content_channels () const { return _content_channels; diff --git a/src/lib/audio_merger.cc b/src/lib/audio_merger.cc new file mode 100644 index 000000000..ad110a44f --- /dev/null +++ b/src/lib/audio_merger.cc @@ -0,0 +1,92 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "audio_buffers.h" +#include "audio_merger.h" + +using std::min; +using std::max; +using boost::shared_ptr; + +AudioMerger::AudioMerger (int channels, int frame_rate) + : _buffers (new AudioBuffers (channels, 0)) + , _frame_rate (frame_rate) + , _last_pull (0) +{ + +} + + +TimedAudioBuffers +AudioMerger::pull (DCPTime time) +{ + assert (time >= _last_pull); + + TimedAudioBuffers out; + + int64_t const to_return = DCPTime (time - _last_pull).frames (_frame_rate); + out.audio.reset (new AudioBuffers (_buffers->channels(), to_return)); + /* And this is how many we will get from our buffer */ + int64_t const to_return_from_buffers = min (to_return, int64_t (_buffers->frames ())); + + /* Copy the data that we have to the back end of the return buffer */ + out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers); + /* Silence any gap at the start */ + out.audio->make_silent (0, to_return - to_return_from_buffers); + + out.time = _last_pull; + _last_pull = time; + + /* And remove the data we're returning from our buffers */ + if (_buffers->frames() > to_return_from_buffers) { + _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers); + } + _buffers->set_frames (_buffers->frames() - to_return_from_buffers); + + return out; +} + +void +AudioMerger::push (shared_ptr<const AudioBuffers> audio, DCPTime time) +{ + assert (time >= _last_pull); + + int64_t frame = time.frames (_frame_rate); + int64_t after = max (int64_t (_buffers->frames()), frame + audio->frames() - _last_pull.frames (_frame_rate)); + _buffers->ensure_size (after); + _buffers->accumulate_frames (audio.get(), 0, frame - _last_pull.frames (_frame_rate), audio->frames ()); + _buffers->set_frames (after); +} + +TimedAudioBuffers +AudioMerger::flush () +{ + if (_buffers->frames() == 0) { + return TimedAudioBuffers (); + } + + return TimedAudioBuffers (_buffers, _last_pull); +} + +void +AudioMerger::clear (DCPTime t) +{ + _last_pull = t; + _buffers.reset (new AudioBuffers (_buffers->channels(), 0)); +} diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h index 226601e0e..756e5ab31 100644 --- a/src/lib/audio_merger.h +++ b/src/lib/audio_merger.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2014 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,93 +17,25 @@ */ -#include "audio_buffers.h" #include "util.h" -template <class T, class F> +class AudioBuffers; + class AudioMerger { public: - AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t) - : _buffers (new AudioBuffers (channels, 0)) - , _last_pull (0) - , _t_to_f (t_to_f) - , _f_to_t (f_to_t) - {} + AudioMerger (int channels, int frame_rate); /** Pull audio up to a given time; after this call, no more data can be pushed * before the specified time. */ - TimedAudioBuffers<T> - pull (T time) - { - TimedAudioBuffers<T> out; - - F const to_return = _t_to_f (time - _last_pull); - out.audio.reset (new AudioBuffers (_buffers->channels(), to_return)); - /* And this is how many we will get from our buffer */ - F const to_return_from_buffers = min (to_return, _buffers->frames ()); - - /* Copy the data that we have to the back end of the return buffer */ - out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers); - /* Silence any gap at the start */ - out.audio->make_silent (0, to_return - to_return_from_buffers); - - out.time = _last_pull; - _last_pull = time; - - /* And remove the data we're returning from our buffers */ - if (_buffers->frames() > to_return_from_buffers) { - _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers); - } - _buffers->set_frames (_buffers->frames() - to_return_from_buffers); - - return out; - } - - void - push (boost::shared_ptr<const AudioBuffers> audio, T time) - { - assert (time >= _last_pull); - - F frame = _t_to_f (time); - F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull)); - _buffers->ensure_size (after); - _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ()); - _buffers->set_frames (after); - } - - F min (F a, int b) - { - if (a < b) { - return a; - } - - return b; - } - - F max (int a, F b) - { - if (a > b) { - return a; - } - - return b; - } - - TimedAudioBuffers<T> - flush () - { - if (_buffers->frames() == 0) { - return TimedAudioBuffers<T> (); - } - - return TimedAudioBuffers<T> (_buffers, _last_pull); - } + TimedAudioBuffers pull (DCPTime time); + void push (boost::shared_ptr<const AudioBuffers> audio, DCPTime time); + TimedAudioBuffers flush (); + void clear (DCPTime t); private: boost::shared_ptr<AudioBuffers> _buffers; - T _last_pull; - boost::function<F (T)> _t_to_f; - boost::function<T (F)> _f_to_t; + int _frame_rate; + DCPTime _last_pull; }; diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc index fca6b6afd..43a432239 100644 --- a/src/lib/cinema.cc +++ b/src/lib/cinema.cc @@ -70,7 +70,7 @@ Cinema::remove_screen (shared_ptr<Screen> s) Screen::Screen (shared_ptr<const cxml::Node> node) { name = node->string_child ("Name"); - certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate"))); + certificate = shared_ptr<dcp::Certificate> (new dcp::Certificate (node->string_child ("Certificate"))); } void diff --git a/src/lib/cinema.h b/src/lib/cinema.h index 40dc15ae0..d8e28ecfd 100644 --- a/src/lib/cinema.h +++ b/src/lib/cinema.h @@ -18,7 +18,7 @@ */ #include <boost/enable_shared_from_this.hpp> -#include <libdcp/certificates.h> +#include <dcp/certificates.h> class Cinema; @@ -29,7 +29,7 @@ namespace cxml { class Screen { public: - Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert) + Screen (std::string const & n, boost::shared_ptr<dcp::Certificate> cert) : name (n) , certificate (cert) {} @@ -40,7 +40,7 @@ public: boost::shared_ptr<Cinema> cinema; std::string name; - boost::shared_ptr<libdcp::Certificate> certificate; + boost::shared_ptr<dcp::Certificate> certificate; }; class Cinema : public boost::enable_shared_from_this<Cinema> diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc index c3fa05426..e4a2a84bf 100644 --- a/src/lib/colour_conversion.cc +++ b/src/lib/colour_conversion.cc @@ -19,7 +19,7 @@ #include <boost/lexical_cast.hpp> #include <libxml++/libxml++.h> -#include <libdcp/colour_matrix.h> +#include <dcp/colour_matrix.h> #include <libcxml/cxml.h> #include "config.h" #include "colour_conversion.h" @@ -43,7 +43,7 @@ ColourConversion::ColourConversion () { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j]; + matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j]; } } } diff --git a/src/lib/config.cc b/src/lib/config.cc index 30f85850d..ad1408cff 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -23,7 +23,7 @@ #include <glib.h> #include <boost/filesystem.hpp> #include <boost/algorithm/string.hpp> -#include <libdcp/colour_matrix.h> +#include <dcp/colour_matrix.h> #include <libcxml/cxml.h> #include "config.h" #include "server.h" @@ -78,9 +78,9 @@ Config::Config () _allowed_dcp_frame_rates.push_back (50); _allowed_dcp_frame_rates.push_back (60); - _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6)); - _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6)); - _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6)); + _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6)); + _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6)); + _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6)); } void @@ -164,7 +164,7 @@ Config::read () /* Loading version 0 (before Rec. 709 was added as a preset). Add it in. */ - _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6)); + _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6)); } list<cxml::NodePtr> cin = f.node_children ("Cinema"); diff --git a/src/lib/config.h b/src/lib/config.h index b9e8d6b02..68aae7414 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -28,7 +28,7 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> #include <boost/filesystem.hpp> -#include <libdcp/metadata.h> +#include <dcp/metadata.h> #include "dci_metadata.h" #include "colour_conversion.h" #include "server.h" @@ -137,7 +137,7 @@ public: return _default_dcp_content_type; } - libdcp::XMLMetadata dcp_metadata () const { + dcp::XMLMetadata dcp_metadata () const { return _dcp_metadata; } @@ -265,7 +265,7 @@ public: write (); } - void set_dcp_metadata (libdcp::XMLMetadata m) { + void set_dcp_metadata (dcp::XMLMetadata m) { _dcp_metadata = m; write (); } @@ -362,7 +362,7 @@ private: int _default_still_length; Ratio const * _default_container; DCPContentType const * _default_dcp_content_type; - libdcp::XMLMetadata _dcp_metadata; + dcp::XMLMetadata _dcp_metadata; int _default_j2k_bandwidth; int _default_audio_delay; std::vector<PresetColourConversion> _colour_conversions; diff --git a/src/lib/content.cc b/src/lib/content.cc index 829468247..1fb4681a2 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f) } -Content::Content (shared_ptr<const Film> f, Time p) +Content::Content (shared_ptr<const Film> f, DCPTime p) : _film (f) , _position (p) , _trim_start (0) @@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) _paths.push_back ((*i)->content ()); } _digest = node->string_child ("Digest"); - _position = node->number_child<Time> ("Position"); - _trim_start = node->number_child<Time> ("TrimStart"); - _trim_end = node->number_child<Time> ("TrimEnd"); + _position = DCPTime (node->number_child<double> ("Position")); + _trim_start = DCPTime (node->number_child<double> ("TrimStart")); + _trim_end = DCPTime (node->number_child<double> ("TrimEnd")); } Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c) @@ -96,11 +96,11 @@ Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c) , _change_signals_frequent (false) { for (size_t i = 0; i < c.size(); ++i) { - if (i > 0 && c[i]->trim_start ()) { + if (i > 0 && c[i]->trim_start() > DCPTime()) { throw JoinError (_("Only the first piece of content to be joined can have a start trim.")); } - if (i < (c.size() - 1) && c[i]->trim_end ()) { + if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) { throw JoinError (_("Only the last piece of content to be joined can have an end trim.")); } @@ -119,9 +119,9 @@ Content::as_xml (xmlpp::Node* node) const node->add_child("Path")->add_child_text (i->string ()); } node->add_child("Digest")->add_child_text (_digest); - node->add_child("Position")->add_child_text (lexical_cast<string> (_position)); - node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start)); - node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end)); + node->add_child("Position")->add_child_text (lexical_cast<string> (_position.get ())); + node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start.get ())); + node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end.get ())); } void @@ -146,7 +146,7 @@ Content::signal_changed (int p) } void -Content::set_position (Time p) +Content::set_position (DCPTime p) { { boost::mutex::scoped_lock lm (_mutex); @@ -161,7 +161,7 @@ Content::set_position (Time p) } void -Content::set_trim_start (Time t) +Content::set_trim_start (DCPTime t) { { boost::mutex::scoped_lock lm (_mutex); @@ -172,7 +172,7 @@ Content::set_trim_start (Time t) } void -Content::set_trim_end (Time t) +Content::set_trim_end (DCPTime t) { { boost::mutex::scoped_lock lm (_mutex); @@ -204,24 +204,15 @@ Content::clone () const string Content::technical_summary () const { - return String::compose ("%1 %2 %3", path_summary(), digest(), position()); + return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds()); } -Time +DCPTime Content::length_after_trim () const { return full_length() - trim_start() - trim_end(); } -/** @param t A time relative to the start of this content (not the position). - * @return true if this time is trimmed by our trim settings. - */ -bool -Content::trimmed (Time t) const -{ - return (t < trim_start() || t > (full_length() - trim_end ())); -} - /** @return string which includes everything about how this content affects * its playlist. */ @@ -231,9 +222,9 @@ Content::identifier () const stringstream s; s << Content::digest() - << "_" << position() - << "_" << trim_start() - << "_" << trim_end(); + << "_" << position().get() + << "_" << trim_start().get() + << "_" << trim_end().get(); return s.str (); } diff --git a/src/lib/content.h b/src/lib/content.h index 596a0a905..fc3a531fa 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -27,6 +27,7 @@ #include <boost/enable_shared_from_this.hpp> #include <libxml++/libxml++.h> #include "types.h" +#include "dcpomatic_time.h" namespace cxml { class Node; @@ -49,7 +50,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no { public: Content (boost::shared_ptr<const Film>); - Content (boost::shared_ptr<const Film>, Time); + Content (boost::shared_ptr<const Film>, DCPTime); Content (boost::shared_ptr<const Film>, boost::filesystem::path); Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); @@ -63,7 +64,7 @@ public: virtual std::string technical_summary () const; virtual std::string information () const = 0; virtual void as_xml (xmlpp::Node *) const; - virtual Time full_length () const = 0; + virtual DCPTime full_length () const = 0; virtual std::string identifier () const; boost::shared_ptr<Content> clone () const; @@ -95,42 +96,40 @@ public: return _digest; } - void set_position (Time); + void set_position (DCPTime); - /** Time that this content starts; i.e. the time that the first + /** DCPTime that this content starts; i.e. the time that the first * bit of the content (trimmed or not) will happen. */ - Time position () const { + DCPTime position () const { boost::mutex::scoped_lock lm (_mutex); return _position; } - void set_trim_start (Time); + void set_trim_start (DCPTime); - Time trim_start () const { + DCPTime trim_start () const { boost::mutex::scoped_lock lm (_mutex); return _trim_start; } - void set_trim_end (Time); + void set_trim_end (DCPTime); - Time trim_end () const { + DCPTime trim_end () const { boost::mutex::scoped_lock lm (_mutex); return _trim_end; } - Time end () const { - return position() + length_after_trim() - 1; + DCPTime end () const { + return position() + length_after_trim(); } - Time length_after_trim () const; + DCPTime length_after_trim () const; void set_change_signals_frequent (bool f) { _change_signals_frequent = f; } - bool trimmed (Time) const; - boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed; protected: @@ -148,9 +147,9 @@ protected: private: std::string _digest; - Time _position; - Time _trim_start; - Time _trim_end; + DCPTime _position; + DCPTime _trim_start; + DCPTime _trim_end; bool _change_signals_frequent; }; diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc index 98b1dd859..092efd790 100644 --- a/src/lib/content_factory.cc +++ b/src/lib/content_factory.cc @@ -21,6 +21,7 @@ #include "ffmpeg_content.h" #include "image_content.h" #include "sndfile_content.h" +#include "subrip_content.h" #include "util.h" using std::string; @@ -40,6 +41,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, l content.reset (new ImageContent (film, node, version)); } else if (type == "Sndfile") { content.reset (new SndfileContent (film, node, version)); + } else if (type == "SubRip") { + content.reset (new SubRipContent (film, node, version)); } return content; @@ -49,11 +52,16 @@ shared_ptr<Content> content_factory (shared_ptr<const Film> film, boost::filesystem::path path) { shared_ptr<Content> content; + + string ext = path.extension().string (); + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); if (valid_image_file (path)) { content.reset (new ImageContent (film, path)); } else if (SndfileContent::valid_file (path)) { content.reset (new SndfileContent (film, path)); + } else if (ext == ".srt") { + content.reset (new SubRipContent (film, path)); } else { content.reset (new FFmpegContent (film, path)); } diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc index 82bd5fa01..b3a45e40e 100644 --- a/src/lib/dcp_content_type.cc +++ b/src/lib/dcp_content_type.cc @@ -30,7 +30,7 @@ using namespace std; vector<DCPContentType const *> DCPContentType::_dcp_content_types; -DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d) +DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d) : _pretty_name (p) , _libdcp_kind (k) , _dci_name (d) @@ -41,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d) void DCPContentType::setup_dcp_content_types () { - _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR"))); - _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR"))); - _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR"))); - _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST"))); - _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN"))); - _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG"))); - _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR"))); - _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL"))); - _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA"))); - _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV"))); + _dcp_content_types.push_back (new DCPContentType (_("Feature"), dcp::FEATURE, N_("FTR"))); + _dcp_content_types.push_back (new DCPContentType (_("Short"), dcp::SHORT, N_("SHR"))); + _dcp_content_types.push_back (new DCPContentType (_("Trailer"), dcp::TRAILER, N_("TLR"))); + _dcp_content_types.push_back (new DCPContentType (_("Test"), dcp::TEST, N_("TST"))); + _dcp_content_types.push_back (new DCPContentType (_("Transitional"), dcp::TRANSITIONAL, N_("XSN"))); + _dcp_content_types.push_back (new DCPContentType (_("Rating"), dcp::RATING, N_("RTG"))); + _dcp_content_types.push_back (new DCPContentType (_("Teaser"), dcp::TEASER, N_("TSR"))); + _dcp_content_types.push_back (new DCPContentType (_("Policy"), dcp::POLICY, N_("POL"))); + _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), dcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA"))); + _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), dcp::ADVERTISEMENT, N_("ADV"))); } DCPContentType const * diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h index 965c16347..05f30af55 100644 --- a/src/lib/dcp_content_type.h +++ b/src/lib/dcp_content_type.h @@ -26,7 +26,7 @@ #include <string> #include <vector> -#include <libdcp/dcp.h> +#include <dcp/dcp.h> /** @class DCPContentType * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.) @@ -34,14 +34,14 @@ class DCPContentType : public boost::noncopyable { public: - DCPContentType (std::string, libdcp::ContentKind, std::string); + DCPContentType (std::string, dcp::ContentKind, std::string); /** @return user-visible `pretty' name */ std::string pretty_name () const { return _pretty_name; } - libdcp::ContentKind libdcp_kind () const { + dcp::ContentKind libdcp_kind () const { return _libdcp_kind; } @@ -58,7 +58,7 @@ public: private: std::string _pretty_name; - libdcp::ContentKind _libdcp_kind; + dcp::ContentKind _libdcp_kind; std::string _dci_name; /** All available DCP content types */ diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index 78d73ad00..3044efd8d 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -43,12 +43,10 @@ #include <boost/asio.hpp> #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> -#include <libdcp/rec709_linearised_gamma_lut.h> -#include <libdcp/srgb_linearised_gamma_lut.h> -#include <libdcp/gamma_lut.h> -#include <libdcp/xyz_frame.h> -#include <libdcp/rgb_xyz.h> -#include <libdcp/colour_matrix.h> +#include <dcp/gamma_lut.h> +#include <dcp/xyz_frame.h> +#include <dcp/rgb_xyz.h> +#include <dcp/colour_matrix.h> #include <libcxml/cxml.h> #include "film.h" #include "dcp_video_frame.h" @@ -68,7 +66,7 @@ using std::stringstream; using std::cout; using boost::shared_ptr; using boost::lexical_cast; -using libdcp::Size; +using dcp::Size; #define DCI_COEFFICENT (48.0 / 52.37) @@ -120,12 +118,8 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const Image> image, shared_ptr<const cx shared_ptr<EncodedData> DCPVideoFrame::encode_locally () { - shared_ptr<libdcp::LUT> in_lut; - if (_conversion.input_gamma_linearised) { - in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma); - } else { - in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma); - } + shared_ptr<dcp::GammaLUT> in_lut; + in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised); /* XXX: libdcp should probably use boost */ @@ -136,10 +130,10 @@ DCPVideoFrame::encode_locally () } } - shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz ( + shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz ( _image, in_lut, - libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma), + dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false), matrix ); @@ -397,7 +391,7 @@ EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const } void -EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const +EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const { boost::filesystem::path const info = film->info_path (frame, eyes); FILE* h = fopen_boost (info, "w"); diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index 40f758c74..0a8b5f287 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> Taken from code Copyright (C) 2010-2011 Terrence Meiczinger This program is free software; you can redistribute it and/or modify @@ -18,9 +18,7 @@ */ -#include <openjpeg.h> -#include <libdcp/picture_asset.h> -#include <libdcp/picture_asset_writer.h> +#include <dcp/picture_mxf_writer.h> #include "util.h" /** @file src/dcp_video_frame.h @@ -49,7 +47,7 @@ public: void send (boost::shared_ptr<Socket> socket); void write (boost::shared_ptr<const Film>, int, Eyes) const; - void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const; + void write_info (boost::shared_ptr<const Film>, int, Eyes, dcp::FrameInfo) const; /** @return data */ uint8_t* data () const { diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc new file mode 100644 index 000000000..98888646d --- /dev/null +++ b/src/lib/dcpomatic_time.cc @@ -0,0 +1,51 @@ +/* + Copyright (C) 2014 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 "dcpomatic_time.h" + +using std::ostream; + +ContentTime::ContentTime (DCPTime d, FrameRateChange f) + : Time (rint (d.get() * f.speed_up)) +{ + +} + +DCPTime min (DCPTime a, DCPTime b) +{ + if (a < b) { + return a; + } + + return b; +} + +ostream & +operator<< (ostream& s, ContentTime t) +{ + s << "[CONT " << t.get() << " " << t.seconds() << "s]"; + return s; +} + +ostream & +operator<< (ostream& s, DCPTime t) +{ + s << "[DCP " << t.get() << " " << t.seconds() << "s]"; + return s; +} diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h new file mode 100644 index 000000000..b19a94ad7 --- /dev/null +++ b/src/lib/dcpomatic_time.h @@ -0,0 +1,229 @@ +/* + Copyright (C) 2014 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_TIME_H +#define DCPOMATIC_TIME_H + +#include <cmath> +#include <ostream> +#include <stdint.h> +#include "frame_rate_change.h" + +class dcpomatic_round_up_test; + +class Time; + +/** A time in seconds, expressed as a number scaled up by Time::HZ. */ +class Time +{ +public: + Time () + : _t (0) + {} + + explicit Time (int64_t t) + : _t (t) + {} + + virtual ~Time () {} + + int64_t get () const { + return _t; + } + + double seconds () const { + return double (_t) / HZ; + } + + template <typename T> + int64_t frames (T r) const { + return rint (_t * r / HZ); + } + +protected: + friend class dcptime_round_up_test; + + int64_t _t; + static const int HZ = 96000; +}; + +class DCPTime; + +class ContentTime : public Time +{ +public: + ContentTime () : Time () {} + explicit ContentTime (int64_t t) : Time (t) {} + ContentTime (int64_t n, int64_t d) : Time (n * HZ / d) {} + ContentTime (DCPTime d, FrameRateChange f); + + bool operator< (ContentTime const & o) const { + return _t < o._t; + } + + bool operator<= (ContentTime const & o) const { + return _t <= o._t; + } + + bool operator== (ContentTime const & o) const { + return _t == o._t; + } + + bool operator!= (ContentTime const & o) const { + return _t != o._t; + } + + bool operator>= (ContentTime const & o) const { + return _t >= o._t; + } + + bool operator> (ContentTime const & o) const { + return _t > o._t; + } + + ContentTime operator+ (ContentTime const & o) const { + return ContentTime (_t + o._t); + } + + ContentTime & operator+= (ContentTime const & o) { + _t += o._t; + return *this; + } + + ContentTime operator- () const { + return ContentTime (-_t); + } + + ContentTime operator- (ContentTime const & o) const { + return ContentTime (_t - o._t); + } + + ContentTime & operator-= (ContentTime const & o) { + _t -= o._t; + return *this; + } + + /** Round up to the nearest sampling interval + * at some sampling rate. + * @param r Sampling rate. + */ + ContentTime round_up (int r) { + int64_t const n = HZ / r; + int64_t const a = _t + n - 1; + return ContentTime (a - (a % n)); + } + + + static ContentTime from_seconds (double s) { + return ContentTime (s * HZ); + } + + template <class T> + static ContentTime from_frames (int64_t f, T r) { + return ContentTime (f * HZ / r); + } +}; + +std::ostream& operator<< (std::ostream& s, ContentTime t); + +class DCPTime : public Time +{ +public: + DCPTime () : Time () {} + explicit DCPTime (int64_t t) : Time (t) {} + DCPTime (ContentTime t, FrameRateChange c) : Time (rint (t.get() / c.speed_up)) {} + + bool operator< (DCPTime const & o) const { + return _t < o._t; + } + + bool operator<= (DCPTime const & o) const { + return _t <= o._t; + } + + bool operator== (DCPTime const & o) const { + return _t == o._t; + } + + bool operator!= (DCPTime const & o) const { + return _t != o._t; + } + + bool operator>= (DCPTime const & o) const { + return _t >= o._t; + } + + bool operator> (DCPTime const & o) const { + return _t > o._t; + } + + DCPTime operator+ (DCPTime const & o) const { + return DCPTime (_t + o._t); + } + + DCPTime & operator+= (DCPTime const & o) { + _t += o._t; + return *this; + } + + DCPTime operator- (DCPTime const & o) const { + return DCPTime (_t - o._t); + } + + DCPTime & operator-= (DCPTime const & o) { + _t -= o._t; + return *this; + } + + /** Round up to the nearest sampling interval + * at some sampling rate. + * @param r Sampling rate. + */ + DCPTime round_up (int r) { + int64_t const n = HZ / r; + int64_t const a = _t + n - 1; + return DCPTime (a - (a % n)); + } + + DCPTime abs () const { + return DCPTime (std::abs (_t)); + } + + static DCPTime from_seconds (double s) { + return DCPTime (s * HZ); + } + + template <class T> + static DCPTime from_frames (int64_t f, T r) { + return DCPTime (f * HZ / r); + } + + static DCPTime delta () { + return DCPTime (1); + } + + static DCPTime max () { + return DCPTime (INT64_MAX); + } +}; + +DCPTime min (DCPTime a, DCPTime b); +std::ostream& operator<< (std::ostream& s, DCPTime t); + +#endif diff --git a/src/lib/decoded.h b/src/lib/decoded.h new file mode 100644 index 000000000..ebf4e57c1 --- /dev/null +++ b/src/lib/decoded.h @@ -0,0 +1,142 @@ +/* + 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_LIB_DECODED_H +#define DCPOMATIC_LIB_DECODED_H + +#include <dcp/subtitle_string.h> +#include "types.h" +#include "rect.h" +#include "util.h" + +class Image; + +class Decoded +{ +public: + Decoded () + : content_time (0) + , dcp_time (0) + {} + + Decoded (ContentTime t) + : content_time (t) + , dcp_time (0) + {} + + virtual ~Decoded () {} + + virtual void set_dcp_times (FrameRateChange frc, DCPTime offset) + { + dcp_time = DCPTime (content_time, frc) + offset; + } + + ContentTime content_time; + DCPTime dcp_time; +}; + +/** One frame of video from a VideoDecoder */ +class DecodedVideo : public Decoded +{ +public: + DecodedVideo () + : eyes (EYES_BOTH) + , same (false) + {} + + DecodedVideo (ContentTime t, boost::shared_ptr<const Image> im, Eyes e, bool s) + : Decoded (t) + , image (im) + , eyes (e) + , same (s) + {} + + boost::shared_ptr<const Image> image; + Eyes eyes; + bool same; +}; + +class DecodedAudio : public Decoded +{ +public: + DecodedAudio (ContentTime t, boost::shared_ptr<const AudioBuffers> d) + : Decoded (t) + , data (d) + {} + + boost::shared_ptr<const AudioBuffers> data; +}; + +class DecodedImageSubtitle : public Decoded +{ +public: + DecodedImageSubtitle () + : content_time_to (0) + , dcp_time_to (0) + {} + + DecodedImageSubtitle (ContentTime f, ContentTime t, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r) + : Decoded (f) + , content_time_to (t) + , dcp_time_to (0) + , image (im) + , rect (r) + {} + + void set_dcp_times (FrameRateChange frc, DCPTime offset) + { + Decoded::set_dcp_times (frc, offset); + dcp_time_to = DCPTime (content_time_to, frc) + offset; + } + + ContentTime content_time_to; + DCPTime dcp_time_to; + boost::shared_ptr<Image> image; + dcpomatic::Rect<double> rect; +}; + +class DecodedTextSubtitle : public Decoded +{ +public: + DecodedTextSubtitle () + : content_time_to (0) + , dcp_time_to (0) + {} + + /* Assuming that all subs are at the same time */ + DecodedTextSubtitle (std::list<dcp::SubtitleString> s) + : Decoded (ContentTime::from_seconds (s.front().in().to_ticks() * 4 / 1000.0)) + , content_time_to (ContentTime::from_seconds (s.front().out().to_ticks() * 4 / 1000.0)) + , subs (s) + { + + } + + void set_dcp_times (FrameRateChange frc, DCPTime offset) + { + Decoded::set_dcp_times (frc, offset); + dcp_time_to = DCPTime (content_time_to, frc) + offset; + } + + ContentTime content_time_to; + DCPTime dcp_time_to; + std::list<dcp::SubtitleString> subs; +}; + +#endif diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 3f4cda6eb..0901f73b0 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -21,18 +21,56 @@ * @brief Parent class for decoders of content. */ -#include "film.h" #include "decoder.h" +#include "decoded.h" #include "i18n.h" +using std::cout; using boost::shared_ptr; -/** @param f Film. - * @param o Decode options. +/** @param o Decode options. */ -Decoder::Decoder (shared_ptr<const Film> f) - : _film (f) +Decoder::Decoder () + : _done (false) { } + +struct DecodedSorter +{ + bool operator() (shared_ptr<Decoded> a, shared_ptr<Decoded> b) + { + return a->dcp_time < b->dcp_time; + } +}; + +shared_ptr<Decoded> +Decoder::peek () +{ + while (!_done && _pending.empty ()) { + _done = pass (); + } + + if (_done && _pending.empty ()) { + return shared_ptr<Decoded> (); + } + + _pending.sort (DecodedSorter ()); + return _pending.front (); +} + +void +Decoder::consume () +{ + if (!_pending.empty ()) { + _pending.pop_front (); + } +} + +void +Decoder::seek (ContentTime, bool) +{ + _pending.clear (); + _done = false; +} diff --git a/src/lib/decoder.h b/src/lib/decoder.h index d67592ed8..0f14dbba7 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -27,8 +27,10 @@ #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <boost/utility.hpp> +#include "types.h" +#include "dcpomatic_time.h" -class Film; +class Decoded; /** @class Decoder. * @brief Parent class for decoders of content. @@ -36,21 +38,36 @@ class Film; class Decoder : public boost::noncopyable { public: - Decoder (boost::shared_ptr<const Film>); + Decoder (); virtual ~Decoder () {} - /** Perform one decode pass of the content, which may or may not - * cause the object to emit some data. + /** Seek so that the next peek() will yield the next thing + * (video/sound frame, subtitle etc.) at or after the requested + * time. Pass accurate = true to try harder to get close to + * the request. + */ + virtual void seek (ContentTime time, bool accurate); + + boost::shared_ptr<Decoded> peek (); + + /* Consume the last peek()ed thing so that it won't be returned + * from the next peek(). */ - virtual void pass () = 0; - virtual bool done () const = 0; + void consume (); protected: + /** Perform one decode pass of the content, which may or may not + * result in a complete quantum (Decoded object) of decoded stuff + * being added to _pending. + * @return true if the decoder is done (i.e. no more data will be + * produced by any future calls to pass() without a seek() first). + */ + virtual bool pass () = 0; virtual void flush () {}; - /** The Film that we are decoding in */ - boost::weak_ptr<const Film> _film; + std::list<boost::shared_ptr<Decoded> > _pending; + bool _done; }; #endif diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 73af11c55..7863859de 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -217,7 +217,7 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi TIMING ("adding to queue of %1", _queue.size ()); _queue.push_back (shared_ptr<DCPVideoFrame> ( new DCPVideoFrame ( - image->image(), _video_frames_out, eyes, conversion, _film->video_frame_rate(), + image->image(PIX_FMT_RGB24, false), _video_frames_out, eyes, conversion, _film->video_frame_rate(), _film->j2k_bandwidth(), _film->resolution(), _film->log() ) )); diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc index 8144f41b9..e05ac4ff0 100644 --- a/src/lib/exceptions.cc +++ b/src/lib/exceptions.cc @@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s) } -PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f) +PixelFormatError::PixelFormatError (string o, AVPixelFormat f) : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o)) { } + +SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f) + : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f) +{ + +} diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 3423a5754..213be6186 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -230,6 +230,13 @@ public: PixelFormatError (std::string o, AVPixelFormat f); }; +/** An error that occurs while parsing a SubRip file */ +class SubRipError : public FileError +{ +public: + SubRipError (std::string, std::string, boost::filesystem::path); +}; + /** A parent class for classes which have a need to catch and * re-throw exceptions. This is intended for classes * which run their own thread; they should do something like diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index fae9baa2b..5fc333489 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -193,6 +193,10 @@ FFmpeg::video_codec_context () const AVCodecContext * FFmpeg::audio_codec_context () const { + if (!_ffmpeg_content->audio_stream ()) { + return 0; + } + return _ffmpeg_content->audio_stream()->stream(_format_context)->codec; } diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index fadeec9cd..90c00283d 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -152,7 +152,7 @@ FFmpegContent::as_xml (xmlpp::Node* node) const } if (_first_video) { - node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ())); + node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get().get())); } } @@ -163,14 +163,14 @@ FFmpegContent::examine (shared_ptr<Job> job) Content::examine (job); - shared_ptr<const Film> film = _film.lock (); - assert (film); - shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ())); + take_from_video_examiner (examiner); + + ContentTime video_length = examiner->video_length (); - VideoContent::Frame video_length = 0; - video_length = examiner->video_length (); - film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length)); + shared_ptr<const Film> film = _film.lock (); + assert (film); + film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ()))); { boost::mutex::scoped_lock lm (_mutex); @@ -190,8 +190,6 @@ FFmpegContent::examine (shared_ptr<Job> job) _first_video = examiner->first_video (); } - take_from_video_examiner (examiner); - signal_changed (ContentProperty::LENGTH); signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS); signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); @@ -233,13 +231,13 @@ FFmpegContent::technical_summary () const string FFmpegContent::information () const { - if (video_length() == 0 || video_frame_rate() == 0) { + if (video_length() == ContentTime (0) || video_frame_rate() == 0) { return ""; } stringstream s; - s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n"; + s << String::compose (_("%1 frames; %2 frames per second"), video_length().frames (video_frame_rate()), video_frame_rate()) << "\n"; s << VideoContent::information (); return s.str (); @@ -267,19 +265,17 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s) signal_changed (FFmpegContentProperty::AUDIO_STREAM); } -AudioContent::Frame +ContentTime FFmpegContent::audio_length () const { - int const cafr = content_audio_frame_rate (); - int const vfr = video_frame_rate (); - VideoContent::Frame const vl = video_length (); - - boost::mutex::scoped_lock lm (_mutex); - if (!_audio_stream) { - return 0; + { + boost::mutex::scoped_lock lm (_mutex); + if (!_audio_stream) { + return ContentTime (); + } } - - return video_frames_to_audio_frames (vl, cafr, vfr); + + return video_length(); } int @@ -315,16 +311,15 @@ FFmpegContent::output_audio_frame_rate () const /* Resample to a DCI-approved sample rate */ double t = dcp_audio_frame_rate (content_audio_frame_rate ()); - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate()); + FrameRateChange frc (video_frame_rate(), film->video_frame_rate()); /* Compensate if the DCP is being run at a different frame rate to the source; that is, if the video is run such that it will look different in the DCP compared to the source (slower or faster). - skip/repeat doesn't come into effect here. */ if (frc.change_speed) { - t *= video_frame_rate() * frc.factor() / film->video_frame_rate(); + t /= frc.speed_up; } return rint (t); @@ -372,7 +367,7 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate)); root->add_child("Channels")->add_child_text (lexical_cast<string> (channels)); if (first_audio) { - root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get ())); + root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get().get())); } mapping.as_xml (root->add_child("Mapping")); } @@ -422,14 +417,12 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const FFmpegStream::as_xml (root); } -Time +DCPTime FFmpegContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); - - FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ()); - return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate (); + return DCPTime (video_length(), FrameRateChange (video_frame_rate (), film->video_frame_rate ())); } AudioMapping diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 6ab95d2fe..e4c4a8a52 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -87,7 +87,7 @@ public: int frame_rate; int channels; AudioMapping mapping; - boost::optional<double> first_audio; + boost::optional<ContentTime> first_audio; private: friend class ffmpeg_pts_offset_test; @@ -139,13 +139,13 @@ public: std::string technical_summary () const; std::string information () const; void as_xml (xmlpp::Node *) const; - Time full_length () const; + DCPTime full_length () const; std::string identifier () const; /* AudioContent */ int audio_channels () const; - AudioContent::Frame audio_length () const; + ContentTime audio_length () const; int content_audio_frame_rate () const; int output_audio_frame_rate () const; AudioMapping audio_mapping () const; @@ -182,7 +182,7 @@ public: void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>); void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>); - boost::optional<double> first_video () const { + boost::optional<ContentTime> first_video () const { boost::mutex::scoped_lock lm (_mutex); return _first_video; } @@ -194,7 +194,7 @@ private: boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream; std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; boost::shared_ptr<FFmpegAudioStream> _audio_stream; - boost::optional<double> _first_video; + boost::optional<ContentTime> _first_video; /** Video filters that should be used when generating DCPs */ std::vector<Filter const *> _filters; }; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 851c64606..32b00a1d6 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -33,7 +33,6 @@ extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } -#include "film.h" #include "filter.h" #include "exceptions.h" #include "image.h" @@ -56,20 +55,19 @@ using std::pair; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; -using libdcp::Size; +using dcp::Size; -FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio) - : Decoder (f) - , VideoDecoder (f, c) - , AudioDecoder (f, c) - , SubtitleDecoder (f) +FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log, bool video, bool audio, bool subtitles) + : VideoDecoder (c) + , AudioDecoder (c) , FFmpeg (c) + , _log (log) , _subtitle_codec_context (0) , _subtitle_codec (0) , _decode_video (video) , _decode_audio (audio) + , _decode_subtitles (subtitles) , _pts_offset (0) - , _just_sought (false) { setup_subtitle (); @@ -82,13 +80,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC Then we remove big initial gaps in PTS and we allow our insertion of black frames to work. - We will do: - audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset; - video_pts_to_use = video_pts_from_ffmpeg + pts_offset; + We will do pts_to_use = pts_from_ffmpeg + pts_offset; */ bool const have_video = video && c->first_video(); - bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio; + bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio; /* First, make one of them start at 0 */ @@ -102,15 +98,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC /* Now adjust both so that the video pts starts on a frame */ if (have_video && have_audio) { - double first_video = c->first_video().get() + _pts_offset; - double const old_first_video = first_video; - - /* Round the first video up to a frame boundary */ - if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) { - first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate (); - } - - _pts_offset += first_video - old_first_video; + ContentTime first_video = c->first_video().get() + _pts_offset; + ContentTime const old_first_video = first_video; + _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video; } } @@ -139,14 +129,11 @@ FFmpegDecoder::flush () if (_ffmpeg_content->audio_stream() && _decode_audio) { decode_audio_packet (); + AudioDecoder::flush (); } - - /* Stop us being asked for any more data */ - _video_position = _ffmpeg_content->video_length (); - _audio_position = _ffmpeg_content->audio_length (); } -void +bool FFmpegDecoder::pass () { int r = av_read_frame (_format_context, &_packet); @@ -156,29 +143,25 @@ FFmpegDecoder::pass () /* Maybe we should fail here, but for now we'll just finish off instead */ char buf[256]; av_strerror (r, buf, sizeof(buf)); - shared_ptr<const Film> film = _film.lock (); - assert (film); - film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); + _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); } flush (); - return; + return true; } - shared_ptr<const Film> film = _film.lock (); - assert (film); - int const si = _packet.stream_index; if (si == _video_stream && _decode_video) { decode_video_packet (); } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) { decode_audio_packet (); - } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) { + } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && _decode_subtitles) { decode_subtitle_packet (); } av_free_packet (&_packet); + return false; } /** @param data pointer to array of pointers to buffers. @@ -310,77 +293,133 @@ FFmpegDecoder::bytes_per_audio_sample () const return av_get_bytes_per_sample (audio_sample_format ()); } -void -FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate) +int +FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished) { - double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base); + int frames_read = 0; + optional<ContentTime> last_video; + optional<ContentTime> last_audio; - /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being - a number plucked from the air) earlier than we want to end up. The loop below - will hopefully then step through to where we want to be. - */ - int initial = frame; + while (!finished (last_video, last_audio, frames_read)) { + int r = av_read_frame (_format_context, &_packet); + if (r < 0) { + /* We should flush our decoders here, possibly yielding a few more frames, + but the consequence of having to do that is too hideous to contemplate. + Instead we give up and say that you can't seek too close to the end + of a file. + */ + return frames_read; + } + + ++frames_read; + + double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base); - if (accurate) { - initial -= 5; + if (_packet.stream_index == _video_stream) { + + avcodec_get_frame_defaults (_frame); + + int finished = 0; + r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); + if (r >= 0 && finished) { + last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset; + } + + } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) { + AVPacket copy_packet = _packet; + while (copy_packet.size > 0) { + + int finished; + r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet); + if (r >= 0 && finished) { + last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset; + } + + copy_packet.data += r; + copy_packet.size -= r; + } + } + + av_free_packet (&_packet); } - if (initial < 0) { - initial = 0; + return frames_read; +} + +bool +FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const +{ + return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek); +} + +bool +FFmpegDecoder::seek_final_finished (int n, int done) const +{ + return n == done; +} + +void +FFmpegDecoder::seek_and_flush (ContentTime t) +{ + ContentTime const u = t - _pts_offset; + int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base); + + if (_ffmpeg_content->audio_stream ()) { + s = min ( + s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)) + ); } - /* Initial seek time in the stream's timebase */ - int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base; + /* Ridiculous empirical hack */ + s--; + if (s < 0) { + s = 0; + } - av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD); + av_seek_frame (_format_context, _video_stream, s, 0); avcodec_flush_buffers (video_codec_context()); + if (audio_codec_context ()) { + avcodec_flush_buffers (audio_codec_context ()); + } if (_subtitle_codec_context) { avcodec_flush_buffers (_subtitle_codec_context); } +} - /* This !accurate is piling hack upon hack; setting _just_sought to true - even with accurate == true defeats our attempt to align the start - of the video and audio. Here we disable that defeat when accurate == true - i.e. when we are making a DCP rather than just previewing one. - Ewww. This should be gone in 2.0. - */ - if (!accurate) { - _just_sought = true; +void +FFmpegDecoder::seek (ContentTime time, bool accurate) +{ + Decoder::seek (time, accurate); + if (_decode_audio) { + AudioDecoder::seek (time, accurate); } - _video_position = frame; - - if (frame == 0 || !accurate) { - /* We're already there, or we're as close as we need to be */ - return; + /* If we are doing an accurate seek, our initial shot will be 200ms (200 being + a number plucked from the air) earlier than we want to end up. The loop below + will hopefully then step through to where we want to be. + */ + + ContentTime pre_roll = accurate ? ContentTime::from_seconds (0.2) : ContentTime (0); + ContentTime initial_seek = time - pre_roll; + if (initial_seek < ContentTime (0)) { + initial_seek = ContentTime (0); } - while (1) { - int r = av_read_frame (_format_context, &_packet); - if (r < 0) { - return; - } + /* Initial seek time in the video stream's timebase */ - if (_packet.stream_index != _video_stream) { - av_free_packet (&_packet); - continue; - } - - int finished = 0; - r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); - if (r >= 0 && finished) { - _video_position = rint ( - (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate() - ); + seek_and_flush (initial_seek); - if (_video_position >= (frame - 1)) { - av_free_packet (&_packet); - break; - } - } - - av_free_packet (&_packet); + if (!accurate) { + /* That'll do */ + return; + } + + int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2)); + + seek_and_flush (initial_seek); + if (N > 0) { + minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3)); } } @@ -397,39 +436,23 @@ FFmpegDecoder::decode_audio_packet () int frame_finished; int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet); + if (decode_result < 0) { - shared_ptr<const Film> film = _film.lock (); - assert (film); - film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result)); + _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result)); return; } if (frame_finished) { - - if (_audio_position == 0) { - /* Where we are in the source, in seconds */ - double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame) + _pts_offset; - - if (pts > 0) { - /* Emit some silence */ - shared_ptr<AudioBuffers> silence ( - new AudioBuffers ( - _ffmpeg_content->audio_channels(), - pts * _ffmpeg_content->content_audio_frame_rate() - ) - ); - - silence->make_silent (); - audio (silence, _audio_position); - } - } + ContentTime const ct = ContentTime::from_seconds ( + av_frame_get_best_effort_timestamp (_frame) * + av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base)) + + _pts_offset; int const data_size = av_samples_get_buffer_size ( 0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1 ); - - audio (deinterleave_audio (_frame->data, data_size), _audio_position); + + audio (deinterleave_audio (_frame->data, data_size), ct); } copy_packet.data += decode_result; @@ -450,18 +473,14 @@ FFmpegDecoder::decode_video_packet () shared_ptr<FilterGraph> graph; list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); - while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { + while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { ++i; } if (i == _filter_graphs.end ()) { - shared_ptr<const Film> film = _film.lock (); - assert (film); - - graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); + graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); _filter_graphs.push_back (graph); - - film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); + _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); } else { graph = *i; } @@ -473,49 +492,9 @@ FFmpegDecoder::decode_video_packet () shared_ptr<Image> image = i->first; if (i->second != AV_NOPTS_VALUE) { - - double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset; - - if (_just_sought) { - /* We just did a seek, so disable any attempts to correct for where we - are / should be. - */ - _video_position = rint (pts * _ffmpeg_content->video_frame_rate ()); - _just_sought = false; - } - - double const next = _video_position / _ffmpeg_content->video_frame_rate(); - double const one_frame = 1 / _ffmpeg_content->video_frame_rate (); - double delta = pts - next; - - while (delta > one_frame) { - /* This PTS is more than one frame forward in time of where we think we should be; emit - a black frame. - */ - - /* XXX: I think this should be a copy of the last frame... */ - boost::shared_ptr<Image> black ( - new Image ( - static_cast<AVPixelFormat> (_frame->format), - libdcp::Size (video_codec_context()->width, video_codec_context()->height), - true - ) - ); - - black->make_black (); - video (image, false, _video_position); - delta -= one_frame; - } - - if (delta > -one_frame) { - /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */ - video (image, false, _video_position); - } - + video (image, false, ContentTime::from_seconds (i->second * av_q2d (_format_context->streams[_video_stream]->time_base)) + _pts_offset); } else { - shared_ptr<const Film> film = _film.lock (); - assert (film); - film->log()->log ("Dropping frame without PTS"); + _log->log ("Dropping frame without PTS"); } } @@ -548,14 +527,6 @@ FFmpegDecoder::setup_subtitle () } } -bool -FFmpegDecoder::done () const -{ - bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length()); - bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length()); - return vd && ad; -} - void FFmpegDecoder::decode_subtitle_packet () { @@ -569,20 +540,20 @@ FFmpegDecoder::decode_subtitle_packet () indicate that the previous subtitle should stop. */ if (sub.num_rects <= 0) { - subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0); + image_subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), ContentTime (), ContentTime ()); return; } else if (sub.num_rects > 1) { throw DecodeError (_("multi-part subtitles not yet supported")); } - /* Subtitle PTS in seconds (within the source, not taking into account any of the + /* Subtitle PTS (within the source, not taking into account any of the source that we may have chopped off for the DCP) */ - double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset; - + ContentTime packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset; + /* hence start time for this sub */ - Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ; - Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ; + ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3); + ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3); AVSubtitleRect const * rect = sub.rects[0]; @@ -593,7 +564,7 @@ FFmpegDecoder::decode_subtitle_packet () /* Note RGBA is expressed little-endian, so the first byte in the word is R, second G, third B, fourth A. */ - shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true)); + shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true)); /* Start of the first line in the subtitle */ uint8_t* sub_p = rect->pict.data[0]; @@ -615,9 +586,9 @@ FFmpegDecoder::decode_subtitle_packet () out_p += image->stride()[0] / sizeof (uint32_t); } - libdcp::Size const vs = _ffmpeg_content->video_size (); + dcp::Size const vs = _ffmpeg_content->video_size (); - subtitle ( + image_subtitle ( image, dcpomatic::Rect<double> ( static_cast<double> (rect->x) / vs.width, diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 63a8f0c71..fbf802bb0 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -38,7 +38,7 @@ extern "C" { #include "subtitle_decoder.h" #include "ffmpeg.h" -class Film; +class Log; class FilterGraph; class ffmpeg_pts_offset_test; @@ -48,18 +48,15 @@ class ffmpeg_pts_offset_test; class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg { public: - FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio); + FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>, bool video, bool audio, bool subtitles); ~FFmpegDecoder (); - void pass (); - void seek (VideoContent::Frame, bool); - bool done () const; + void seek (ContentTime time, bool); private: friend class ::ffmpeg_pts_offset_test; - static double compute_pts_offset (double, double, float); - + bool pass (); void flush (); void setup_subtitle (); @@ -74,6 +71,12 @@ private: void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size); + bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const; + bool seek_final_finished (int, int) const; + int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>); + void seek_and_flush (ContentTime); + + boost::shared_ptr<Log> _log; AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle @@ -82,7 +85,7 @@ private: bool _decode_video; bool _decode_audio; + bool _decode_subtitles; - double _pts_offset; - bool _just_sought; + ContentTime _pts_offset; }; diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index ec090ed61..6daba4b40 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -102,14 +102,14 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c) } } -optional<double> +optional<ContentTime> FFmpegExaminer::frame_time (AVStream* s) const { - optional<double> t; + optional<ContentTime> t; int64_t const bet = av_frame_get_best_effort_timestamp (_frame); if (bet != AV_NOPTS_VALUE) { - t = bet * av_q2d (s->time_base); + t = ContentTime (bet * av_q2d (s->time_base)); } return t; @@ -127,18 +127,18 @@ FFmpegExaminer::video_frame_rate () const return av_q2d (s->r_frame_rate); } -libdcp::Size +dcp::Size FFmpegExaminer::video_size () const { - return libdcp::Size (video_codec_context()->width, video_codec_context()->height); + return dcp::Size (video_codec_context()->width, video_codec_context()->height); } -/** @return Length (in video frames) according to our content's header */ -VideoContent::Frame +/** @return Length according to our content's header */ +ContentTime FFmpegExaminer::video_length () const { - VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate(); - return max (1, length); + ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE); + return ContentTime (max (int64_t (1), length.get ())); } string diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 369dac29c..381c5cea9 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -30,8 +30,8 @@ public: FFmpegExaminer (boost::shared_ptr<const FFmpegContent>); float video_frame_rate () const; - libdcp::Size video_size () const; - VideoContent::Frame video_length () const; + dcp::Size video_size () const; + ContentTime video_length () const; std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const { return _subtitle_streams; @@ -41,7 +41,7 @@ public: return _audio_streams; } - boost::optional<double> first_video () const { + boost::optional<ContentTime> first_video () const { return _first_video; } @@ -49,9 +49,9 @@ private: std::string stream_name (AVStream* s) const; std::string audio_stream_name (AVStream* s) const; std::string subtitle_stream_name (AVStream* s) const; - boost::optional<double> frame_time (AVStream* s) const; + boost::optional<ContentTime> frame_time (AVStream* s) const; std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams; std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; - boost::optional<double> _first_video; + boost::optional<ContentTime> _first_video; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index 04692fc1e..cc80f5bc2 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -31,11 +31,11 @@ #include <boost/date_time.hpp> #include <libxml++/libxml++.h> #include <libcxml/cxml.h> -#include <libdcp/signer_chain.h> -#include <libdcp/cpl.h> -#include <libdcp/signer.h> -#include <libdcp/util.h> -#include <libdcp/kdm.h> +#include <dcp/signer_chain.h> +#include <dcp/cpl.h> +#include <dcp/signer.h> +#include <dcp/util.h> +#include <dcp/kdm.h> #include "film.h" #include "job.h" #include "util.h" @@ -78,8 +78,8 @@ using boost::to_upper_copy; using boost::ends_with; using boost::starts_with; using boost::optional; -using libdcp::Size; -using libdcp::Signer; +using dcp::Size; +using dcp::Signer; /* 5 -> 6 * AudioMapping XML changed. @@ -431,7 +431,7 @@ Film::read_metadata () _sequence_video = f.bool_child ("SequenceVideo"); _three_d = f.bool_child ("ThreeD"); _interop = f.bool_child ("Interop"); - _key = libdcp::Key (f.string_child ("Key")); + _key = dcp::Key (f.string_child ("Key")); list<string> notes; /* This method is the only one that can return notes (so far) */ @@ -761,7 +761,7 @@ Film::j2c_path (int f, Eyes e, bool t) const return file (p); } -/** @return List of subdirectories (not full paths) containing DCPs that can be successfully libdcp::DCP::read() */ +/** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */ list<boost::filesystem::path> Film::dcps () const { @@ -775,7 +775,7 @@ Film::dcps () const ) { try { - libdcp::DCP dcp (*i); + dcp::DCP dcp (*i); dcp.read (); out.push_back (i->path().leaf ()); } catch (...) { @@ -870,7 +870,7 @@ Film::move_content_later (shared_ptr<Content> c) _playlist->move_later (c); } -Time +DCPTime Film::length () const { return _playlist->length (); @@ -882,12 +882,18 @@ Film::has_subtitles () const return _playlist->has_subtitles (); } -OutputVideoFrame +int Film::best_video_frame_rate () const { return _playlist->best_dcp_frame_rate (); } +FrameRateChange +Film::active_frame_rate_change (DCPTime t) const +{ + return _playlist->active_frame_rate_change (t, video_frame_rate ()); +} + void Film::playlist_content_changed (boost::weak_ptr<Content> c, int p) { @@ -906,31 +912,7 @@ Film::playlist_changed () signal_changed (CONTENT); } -OutputAudioFrame -Film::time_to_audio_frames (Time t) const -{ - return divide_with_round (t * audio_frame_rate (), TIME_HZ); -} - -OutputVideoFrame -Film::time_to_video_frames (Time t) const -{ - return divide_with_round (t * video_frame_rate (), TIME_HZ); -} - -Time -Film::audio_frames_to_time (OutputAudioFrame f) const -{ - return divide_with_round (f * TIME_HZ, audio_frame_rate ()); -} - -Time -Film::video_frames_to_time (OutputVideoFrame f) const -{ - return divide_with_round (f * TIME_HZ, video_frame_rate ()); -} - -OutputAudioFrame +int Film::audio_frame_rate () const { /* XXX */ @@ -946,30 +928,30 @@ Film::set_sequence_video (bool s) } /** @return Size of the largest possible image in whatever resolution we are using */ -libdcp::Size +dcp::Size Film::full_frame () const { switch (_resolution) { case RESOLUTION_2K: - return libdcp::Size (2048, 1080); + return dcp::Size (2048, 1080); case RESOLUTION_4K: - return libdcp::Size (4096, 2160); + return dcp::Size (4096, 2160); } assert (false); - return libdcp::Size (); + return dcp::Size (); } /** @return Size of the frame */ -libdcp::Size +dcp::Size Film::frame_size () const { return fit_ratio_within (container()->ratio(), full_frame ()); } -libdcp::KDM +dcp::KDM Film::make_kdm ( - shared_ptr<libdcp::Certificate> target, + shared_ptr<dcp::Certificate> target, boost::filesystem::path dcp_dir, boost::posix_time::ptime from, boost::posix_time::ptime until @@ -977,7 +959,7 @@ Film::make_kdm ( { shared_ptr<const Signer> signer = make_signer (); - libdcp::DCP dcp (dir (dcp_dir.string ())); + dcp::DCP dcp (dir (dcp_dir.string ())); try { dcp.read (); @@ -987,14 +969,14 @@ Film::make_kdm ( time_t now = time (0); struct tm* tm = localtime (&now); - string const issue_date = libdcp::tm_to_string (tm); + string const issue_date = dcp::tm_to_string (tm); dcp.cpls().front()->set_mxf_keys (key ()); - return libdcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date); + return dcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date); } -list<libdcp::KDM> +list<dcp::KDM> Film::make_kdms ( list<shared_ptr<Screen> > screens, boost::filesystem::path dcp, @@ -1002,7 +984,7 @@ Film::make_kdms ( boost::posix_time::ptime until ) const { - list<libdcp::KDM> kdms; + list<dcp::KDM> kdms; for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) { kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until)); @@ -1017,7 +999,7 @@ Film::make_kdms ( uint64_t Film::required_disk_space () const { - return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ; + return uint64_t (j2k_bandwidth() / 8) * length().seconds(); } /** This method checks the disk that the Film is on and tries to decide whether or not diff --git a/src/lib/film.h b/src/lib/film.h index 162b67b35..9d1445d92 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -31,8 +31,8 @@ #include <boost/signals2.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/filesystem.hpp> -#include <libdcp/key.h> -#include <libdcp/kdm.h> +#include <dcp/key.h> +#include <dcp/kdm.h> #include "util.h" #include "types.h" #include "dci_metadata.h" @@ -95,20 +95,15 @@ public: return _dirty; } - libdcp::Size full_frame () const; - libdcp::Size frame_size () const; + dcp::Size full_frame () const; + dcp::Size frame_size () const; std::list<boost::filesystem::path> dcps () const; boost::shared_ptr<Player> make_player () const; boost::shared_ptr<Playlist> playlist () const; - OutputAudioFrame audio_frame_rate () const; - - OutputAudioFrame time_to_audio_frames (Time) const; - OutputVideoFrame time_to_video_frames (Time) const; - Time video_frames_to_time (OutputVideoFrame) const; - Time audio_frames_to_time (OutputAudioFrame) const; + int audio_frame_rate () const; uint64_t required_disk_space () const; bool should_be_enough_disk_space (double &, double &) const; @@ -116,26 +111,27 @@ public: /* Proxies for some Playlist methods */ ContentList content () const; - Time length () const; + DCPTime length () const; bool has_subtitles () const; - OutputVideoFrame best_video_frame_rate () const; + int best_video_frame_rate () const; + FrameRateChange active_frame_rate_change (DCPTime) const; - libdcp::KDM + dcp::KDM make_kdm ( - boost::shared_ptr<libdcp::Certificate> target, + boost::shared_ptr<dcp::Certificate> target, boost::filesystem::path dcp, boost::posix_time::ptime from, boost::posix_time::ptime until ) const; - std::list<libdcp::KDM> make_kdms ( + std::list<dcp::KDM> make_kdms ( std::list<boost::shared_ptr<Screen> >, boost::filesystem::path dcp, boost::posix_time::ptime from, boost::posix_time::ptime until ) const; - libdcp::Key key () const { + dcp::Key key () const { return _key; } @@ -328,7 +324,7 @@ private: bool _three_d; bool _sequence_video; bool _interop; - libdcp::Key _key; + dcp::Key _key; int _state_version; diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index a36a41f43..5add16d19 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -45,14 +45,14 @@ using std::make_pair; using std::cout; using boost::shared_ptr; using boost::weak_ptr; -using libdcp::Size; +using dcp::Size; /** Construct a FilterGraph for the settings in a piece of content. * @param content Content. * @param s Size of the images to process. * @param p Pixel format of the images to process. */ -FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p) +FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) @@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size throw DecodeError (N_("could not configure filter graph.")); } - /* XXX: leaking `inputs' / `outputs' ? */ + avfilter_inout_free (&inputs); + avfilter_inout_free (&outputs); } FilterGraph::~FilterGraph () @@ -159,7 +160,7 @@ FilterGraph::process (AVFrame* frame) * @return true if this chain can process images with `s' and `p', otherwise false. */ bool -FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const +FilterGraph::can_process (dcp::Size s, AVPixelFormat p) const { return (_size == s && _pixel_format == p); } diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index 9b403c2bc..45ad5d998 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -36,16 +36,16 @@ class FFmpegContent; class FilterGraph : public boost::noncopyable { public: - FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p); + FilterGraph (boost::shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p); ~FilterGraph (); - bool can_process (libdcp::Size s, AVPixelFormat p) const; + bool can_process (dcp::Size s, AVPixelFormat p) const; std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame); private: AVFilterContext* _buffer_src_context; AVFilterContext* _buffer_sink_context; - libdcp::Size _size; ///< size of the images that this chain can process + dcp::Size _size; ///< size of the images that this chain can process AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process AVFrame* _frame; }; diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc new file mode 100644 index 000000000..3e9c4b505 --- /dev/null +++ b/src/lib/frame_rate_change.cc @@ -0,0 +1,91 @@ +/* + Copyright (C) 2012-2014 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 <cmath> +#include "frame_rate_change.h" +#include "compose.hpp" + +#include "i18n.h" + +static bool +about_equal (float a, float b) +{ + /* A film of F seconds at f FPS will be Ff frames; + Consider some delta FPS d, so if we run the same + film at (f + d) FPS it will last F(f + d) seconds. + + Hence the difference in length over the length of the film will + be F(f + d) - Ff frames + = Ff + Fd - Ff frames + = Fd frames + = Fd/f seconds + + So if we accept a difference of 1 frame, ie 1/f seconds, we can + say that + + 1/f = Fd/f + ie 1 = Fd + ie d = 1/F + + So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable + FPS error is 1/F ~= 0.0001 ~= 10-e4 + */ + + return (fabs (a - b) < 1e-4); +} + + +FrameRateChange::FrameRateChange (float source, int dcp) + : skip (false) + , repeat (1) + , change_speed (false) +{ + if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) { + /* The difference between source and DCP frame rate will be lower + (i.e. better) if we skip. + */ + skip = true; + } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { + /* The difference between source and DCP frame rate would be better + if we repeated each frame once; it may be better still if we + repeated more than once. Work out the required repeat. + */ + repeat = round (dcp / source); + } + + speed_up = dcp / (source * factor()); + change_speed = !about_equal (speed_up, 1.0); + + if (!skip && repeat == 1 && !change_speed) { + description = _("Content and DCP have the same rate.\n"); + } else { + if (skip) { + description = _("DCP will use every other frame of the content.\n"); + } else if (repeat == 2) { + description = _("Each content frame will be doubled in the DCP.\n"); + } else if (repeat > 2) { + description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1); + } + + if (change_speed) { + float const pc = dcp * 100 / (source * factor()); + description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc); + } + } +} diff --git a/src/lib/frame_rate_change.h b/src/lib/frame_rate_change.h new file mode 100644 index 000000000..6165f6840 --- /dev/null +++ b/src/lib/frame_rate_change.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2012-2014 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 <string> + +struct FrameRateChange +{ + FrameRateChange (float, int); + + /** @return factor by which to multiply a source frame rate + to get the effective rate after any skip or repeat has happened. + */ + float factor () const { + if (skip) { + return 0.5; + } + + return repeat; + } + + /** true to skip every other frame */ + bool skip; + /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */ + int repeat; + /** true if this DCP will run its video faster or slower than the source + * without taking into account `repeat' nor `skip'. + * (e.g. change_speed will be true if + * source is 29.97fps, DCP is 30fps + * source is 14.50fps, DCP is 30fps + * but not if + * source is 15.00fps, DCP is 30fps + * source is 12.50fps, DCP is 25fps) + */ + bool change_speed; + + /** Amount by which the video is being sped-up in the DCP; e.g. for a + * 24fps source in a 25fps DCP this would be 25/24. + */ + float speed_up; + + std::string description; +}; diff --git a/src/lib/image.cc b/src/lib/image.cc index 25d1ef276..c3b1ca77a 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2014 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 @@ -31,6 +31,7 @@ extern "C" { #include "image.h" #include "exceptions.h" #include "scaler.h" +#include "timer.h" #include "i18n.h" @@ -39,7 +40,7 @@ using std::min; using std::cout; using std::cerr; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; int Image::line_factor (int n) const @@ -83,7 +84,7 @@ Image::components () const /** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */ shared_ptr<Image> -Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const +Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const { assert (scaler); /* Empirical testing suggests that sws_scale() will crash if @@ -99,13 +100,13 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s out->make_black (); /* Size of the image after any crop */ - libdcp::Size const cropped_size = crop.apply (size ()); + dcp::Size const cropped_size = crop.apply (size ()); /* Scale context for a scale from cropped_size to inter_size */ struct SwsContext* scale_context = sws_getContext ( - cropped_size.width, cropped_size.height, pixel_format(), - inter_size.width, inter_size.height, out_format, - scaler->ffmpeg_id (), 0, 0, 0 + cropped_size.width, cropped_size.height, pixel_format(), + inter_size.width, inter_size.height, out_format, + scaler->ffmpeg_id (), 0, 0, 0 ); if (!scale_context) { @@ -139,7 +140,7 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s } shared_ptr<Image> -Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const +Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const { assert (scaler); /* Empirical testing suggests that sws_scale() will crash if @@ -170,7 +171,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_fo shared_ptr<Image> Image::crop (Crop crop, bool aligned) const { - libdcp::Size cropped_size = crop.apply (size ()); + dcp::Size cropped_size = crop.apply (size ()); shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned)); for (int c = 0; c < components(); ++c) { @@ -345,8 +346,18 @@ Image::make_black () void Image::alpha_blend (shared_ptr<const Image> other, Position<int> position) { - /* Only implemented for RGBA onto RGB24 so far */ - assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA); + int this_bpp = 0; + int other_bpp = 0; + + if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) { + this_bpp = 4; + other_bpp = 4; + } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) { + this_bpp = 3; + other_bpp = 4; + } else { + assert (false); + } int start_tx = position.x; int start_ox = 0; @@ -365,15 +376,15 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position) } for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3; + uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp; uint8_t* op = other->data()[0] + oy * other->stride()[0]; for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { float const alpha = float (op[3]) / 255; tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha; tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha; tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha; - tp += 3; - op += 4; + tp += this_bpp; + op += other_bpp; } } } @@ -457,8 +468,8 @@ Image::bytes_per_pixel (int c) const * @param p Pixel format. * @param s Size in pixels. */ -Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned) - : libdcp::Image (s) +Image::Image (AVPixelFormat p, dcp::Size s, bool aligned) + : dcp::Image (s) , _pixel_format (p) , _aligned (aligned) { @@ -495,7 +506,7 @@ Image::allocate () } Image::Image (Image const & other) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other._pixel_format) , _aligned (other._aligned) { @@ -513,7 +524,7 @@ Image::Image (Image const & other) } Image::Image (AVFrame* frame) - : libdcp::Image (libdcp::Size (frame->width, frame->height)) + : dcp::Image (dcp::Size (frame->width, frame->height)) , _pixel_format (static_cast<AVPixelFormat> (frame->format)) , _aligned (true) { @@ -532,7 +543,7 @@ Image::Image (AVFrame* frame) } Image::Image (shared_ptr<const Image> other, bool aligned) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other->_pixel_format) , _aligned (aligned) { @@ -565,7 +576,7 @@ Image::operator= (Image const & other) void Image::swap (Image & other) { - libdcp::Image::swap (other); + dcp::Image::swap (other); std::swap (_pixel_format, other._pixel_format); @@ -608,7 +619,7 @@ Image::stride () const return _stride; } -libdcp::Size +dcp::Size Image::size () const { return _size; diff --git a/src/lib/image.h b/src/lib/image.h index 2d9f32231..5eba11041 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -31,16 +31,16 @@ extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> } -#include <libdcp/image.h> +#include <dcp/image.h> #include "util.h" #include "position.h" class Scaler; -class Image : public libdcp::Image +class Image : public dcp::Image { public: - Image (AVPixelFormat, libdcp::Size, bool); + Image (AVPixelFormat, dcp::Size, bool); Image (AVFrame *); Image (Image const &); Image (boost::shared_ptr<const Image>, bool); @@ -50,17 +50,17 @@ public: uint8_t ** data () const; int * line_size () const; int * stride () const; - libdcp::Size size () const; + dcp::Size size () const; bool aligned () const; int components () const; int line_factor (int) const; int lines (int) const; - boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; + boost::shared_ptr<Image> scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; boost::shared_ptr<Image> crop (Crop c, bool aligned) const; - boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; + boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; void make_black (); void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos); diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc index fd0b57894..6a37f3067 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -110,7 +110,7 @@ ImageContent::examine (shared_ptr<Job> job) } void -ImageContent::set_video_length (VideoContent::Frame len) +ImageContent::set_video_length (ContentTime len) { { boost::mutex::scoped_lock lm (_mutex); @@ -120,14 +120,12 @@ ImageContent::set_video_length (VideoContent::Frame len) signal_changed (ContentProperty::LENGTH); } -Time +DCPTime ImageContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); - - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ()); - return video_length() * frc.factor() * TIME_HZ / video_frame_rate(); + return DCPTime (video_length(), FrameRateChange (video_frame_rate(), film->video_frame_rate())); } string @@ -135,7 +133,7 @@ ImageContent::identifier () const { stringstream s; s << VideoContent::identifier (); - s << "_" << video_length(); + s << "_" << video_length().get(); return s.str (); } diff --git a/src/lib/image_content.h b/src/lib/image_content.h index e56abce4a..6db24767d 100644 --- a/src/lib/image_content.h +++ b/src/lib/image_content.h @@ -41,11 +41,11 @@ public: std::string summary () const; std::string technical_summary () const; void as_xml (xmlpp::Node *) const; - Time full_length () const; + DCPTime full_length () const; std::string identifier () const; - void set_video_length (VideoContent::Frame); + void set_video_length (ContentTime); bool still () const; void set_video_frame_rate (float); }; diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index a7999c02a..58ba732bb 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -30,37 +30,41 @@ using std::cout; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; -ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c) - : Decoder (f) - , VideoDecoder (f, c) +ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c) + : VideoDecoder (c) , _image_content (c) { } -void +bool ImageDecoder::pass () { if (_video_position >= _image_content->video_length ()) { - return; + return true; } if (_image && _image_content->still ()) { video (_image, true, _video_position); - return; + _video_position += ContentTime::from_frames (1, _image_content->video_frame_rate ()); + return false; } Magick::Image* magick_image = 0; - boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position); + + boost::filesystem::path const path = _image_content->path ( + _image_content->still() ? 0 : _video_position.frames (_image_content->video_frame_rate ()) + ); + try { magick_image = new Magick::Image (path.string ()); } catch (...) { throw OpenFileError (path); } - libdcp::Size size (magick_image->columns(), magick_image->rows()); + dcp::Size size (magick_image->columns(), magick_image->rows()); _image.reset (new Image (PIX_FMT_RGB24, size, true)); @@ -81,16 +85,14 @@ ImageDecoder::pass () delete magick_image; video (_image, false, _video_position); -} + _video_position += ContentTime::from_frames (1, _image_content->video_frame_rate ()); -void -ImageDecoder::seek (VideoContent::Frame frame, bool) -{ - _video_position = frame; + return false; } -bool -ImageDecoder::done () const +void +ImageDecoder::seek (ContentTime time, bool accurate) { - return _video_position >= _image_content->video_length (); + Decoder::seek (time, accurate); + _video_position = time; } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index c7500243e..13ffea13d 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -28,20 +28,19 @@ class ImageContent; class ImageDecoder : public VideoDecoder { public: - ImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>); + ImageDecoder (boost::shared_ptr<const ImageContent>); boost::shared_ptr<const ImageContent> content () { return _image_content; } - /* Decoder */ - - void pass (); - void seek (VideoContent::Frame, bool); - bool done () const; + void seek (ContentTime, bool); private: + bool pass (); + boost::shared_ptr<const ImageContent> _image_content; boost::shared_ptr<Image> _image; + ContentTime _video_position; }; diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc index 12fe2b8a6..4cba788bd 100644 --- a/src/lib/image_examiner.cc +++ b/src/lib/image_examiner.cc @@ -39,21 +39,20 @@ using boost::bad_lexical_cast; ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const ImageContent> content, shared_ptr<Job>) : _film (film) , _image_content (content) - , _video_length (0) { using namespace MagickCore; Magick::Image* image = new Magick::Image (content->path(0).string()); - _video_size = libdcp::Size (image->columns(), image->rows()); + _video_size = dcp::Size (image->columns(), image->rows()); delete image; if (content->still ()) { - _video_length = Config::instance()->default_still_length() * video_frame_rate(); + _video_length = ContentTime (Config::instance()->default_still_length()); } else { - _video_length = _image_content->number_of_paths (); + _video_length = ContentTime (double (_image_content->number_of_paths ()) / video_frame_rate ()); } } -libdcp::Size +dcp::Size ImageExaminer::video_size () const { return _video_size.get (); diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h index 8887f0d3d..6ae0422cb 100644 --- a/src/lib/image_examiner.h +++ b/src/lib/image_examiner.h @@ -31,14 +31,14 @@ public: ImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>, boost::shared_ptr<Job>); float video_frame_rate () const; - libdcp::Size video_size () const; - VideoContent::Frame video_length () const { + dcp::Size video_size () const; + ContentTime video_length () const { return _video_length; } private: boost::weak_ptr<const Film> _film; boost::shared_ptr<const ImageContent> _image_content; - boost::optional<libdcp::Size> _video_size; - VideoContent::Frame _video_length; + boost::optional<dcp::Size> _video_size; + ContentTime _video_length; }; diff --git a/src/lib/job.cc b/src/lib/job.cc index 52499b433..e48daa410 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -23,7 +23,7 @@ #include <boost/thread.hpp> #include <boost/filesystem.hpp> -#include <libdcp/exceptions.h> +#include <dcp/exceptions.h> #include "job.h" #include "util.h" #include "cross.h" @@ -66,7 +66,7 @@ Job::run_wrapper () run (); - } catch (libdcp::FileError& e) { + } catch (dcp::FileError& e) { string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf()); @@ -204,7 +204,7 @@ Job::set_state (State s) } } -/** @return Time (in seconds) that this sub-job has been running */ +/** @return DCPTime (in seconds) that this sub-job has been running */ int Job::elapsed_time () const { diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc index cf551285b..00d2f0537 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -21,7 +21,7 @@ #include <boost/shared_ptr.hpp> #include <quickmail.h> #include <zip.h> -#include <libdcp/kdm.h> +#include <dcp/kdm.h> #include "kdm.h" #include "cinema.h" #include "exceptions.h" @@ -36,13 +36,13 @@ using boost::shared_ptr; struct ScreenKDM { - ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k) + ScreenKDM (shared_ptr<Screen> s, dcp::KDM k) : screen (s) , kdm (k) {} shared_ptr<Screen> screen; - libdcp::KDM kdm; + dcp::KDM kdm; }; static string @@ -107,12 +107,12 @@ make_screen_kdms ( boost::posix_time::ptime to ) { - list<libdcp::KDM> kdms = film->make_kdms (screens, dcp, from, to); + list<dcp::KDM> kdms = film->make_kdms (screens, dcp, from, to); list<ScreenKDM> screen_kdms; list<shared_ptr<Screen> >::iterator i = screens.begin (); - list<libdcp::KDM>::iterator j = kdms.begin (); + list<dcp::KDM>::iterator j = kdms.begin (); while (i != screens.end() && j != kdms.end ()) { screen_kdms.push_back (ScreenKDM (*i, *j)); ++i; diff --git a/src/lib/player.cc b/src/lib/player.cc index d05597dd5..f83e4ed26 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -18,22 +18,26 @@ */ #include <stdint.h> +#include <algorithm> #include "player.h" #include "film.h" #include "ffmpeg_decoder.h" +#include "audio_buffers.h" #include "ffmpeg_content.h" #include "image_decoder.h" #include "image_content.h" #include "sndfile_decoder.h" #include "sndfile_content.h" #include "subtitle_content.h" +#include "subrip_decoder.h" +#include "subrip_content.h" #include "playlist.h" #include "job.h" #include "image.h" #include "ratio.h" -#include "resampler.h" #include "log.h" #include "scaler.h" +#include "render_subtitles.h" using std::list; using std::cout; @@ -45,71 +49,20 @@ using std::map; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; +using boost::optional; class Piece { public: - Piece (shared_ptr<Content> c) - : content (c) - , video_position (c->position ()) - , audio_position (c->position ()) - , repeat_to_do (0) - , repeat_done (0) - {} - - Piece (shared_ptr<Content> c, shared_ptr<Decoder> d) + Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f) : content (c) , decoder (d) - , video_position (c->position ()) - , audio_position (c->position ()) - , repeat_to_do (0) - , repeat_done (0) + , frc (f) {} - /** Set this piece to repeat a video frame a given number of times */ - void set_repeat (IncomingVideo video, int num) - { - repeat_video = video; - repeat_to_do = num; - repeat_done = 0; - } - - void reset_repeat () - { - repeat_video.image.reset (); - repeat_to_do = 0; - repeat_done = 0; - } - - bool repeating () const - { - return repeat_done != repeat_to_do; - } - - void repeat (Player* player) - { - player->process_video ( - repeat_video.weak_piece, - repeat_video.image, - repeat_video.eyes, - repeat_done > 0, - repeat_video.frame, - (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ()) - ); - - ++repeat_done; - } - shared_ptr<Content> content; shared_ptr<Decoder> decoder; - /** Time of the last video we emitted relative to the start of the DCP */ - Time video_position; - /** Time of the last audio we emitted relative to the start of the DCP */ - Time audio_position; - - IncomingVideo repeat_video; - int repeat_to_do; - int repeat_done; + FrameRateChange frc; }; Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) @@ -120,8 +73,10 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) , _have_valid_pieces (false) , _video_position (0) , _audio_position (0) - , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1)) + , _audio_merger (f->audio_channels(), f->audio_frame_rate ()) , _last_emit_was_black (false) + , _just_did_inaccurate_seek (false) + , _approximate_size (false) { _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3)); @@ -148,111 +103,165 @@ Player::pass () setup_pieces (); } - Time earliest_t = TIME_MAX; - shared_ptr<Piece> earliest; - enum { - VIDEO, - AUDIO - } type = VIDEO; + /* Interrogate all our pieces to find the one with the earliest decoded data */ + + shared_ptr<Piece> earliest_piece; + shared_ptr<Decoded> earliest_decoded; + DCPTime earliest_time = DCPTime::max (); + DCPTime earliest_audio = DCPTime::max (); for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done ()) { - continue; - } - shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder); - shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); + DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start(); + + bool done = false; + shared_ptr<Decoded> dec; + while (!done) { + dec = (*i)->decoder->peek (); + if (!dec) { + /* Decoder has nothing else to give us */ + break; + } - if (_video && vd) { - if ((*i)->video_position < earliest_t) { - earliest_t = (*i)->video_position; - earliest = *i; - type = VIDEO; + dec->set_dcp_times ((*i)->frc, offset); + DCPTime const t = dec->dcp_time - offset; + if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) { + /* In the end-trimmed part; decoder has nothing else to give us */ + dec.reset (); + done = true; + } else if (t >= (*i)->content->trim_start ()) { + /* Within the un-trimmed part; everything's ok */ + done = true; + } else { + /* Within the start-trimmed part; get something else */ + (*i)->decoder->consume (); } } - if (_audio && ad && ad->has_audio ()) { - if ((*i)->audio_position < earliest_t) { - earliest_t = (*i)->audio_position; - earliest = *i; - type = AUDIO; - } + if (!dec) { + continue; } - } - if (!earliest) { + if (dec->dcp_time < earliest_time) { + earliest_piece = *i; + earliest_decoded = dec; + earliest_time = dec->dcp_time; + } + + if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) { + earliest_audio = dec->dcp_time; + } + } + + if (!earliest_piece) { flush (); return true; } - switch (type) { - case VIDEO: - if (earliest_t > _video_position) { - emit_black (); - } else { - if (earliest->repeating ()) { - earliest->repeat (this); - } else { - earliest->decoder->pass (); - } - } - break; - - case AUDIO: - if (earliest_t > _audio_position) { - emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position)); - } else { - earliest->decoder->pass (); - - if (earliest->decoder->done()) { - shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content); - assert (ac); - shared_ptr<Resampler> re = resampler (ac, false); - if (re) { - shared_ptr<const AudioBuffers> b = re->flush (); - if (b->frames ()) { - process_audio (earliest, b, ac->audio_length ()); - } - } - } + if (earliest_audio != DCPTime::max ()) { + if (earliest_audio.get() < 0) { + earliest_audio = DCPTime (); } - break; + TimedAudioBuffers tb = _audio_merger.pull (earliest_audio); + Audio (tb.audio, tb.time); + /* This assumes that the audio-frames-to-time conversion is exact + so that there are no accumulated errors caused by rounding. + */ + _audio_position += DCPTime::from_frames (tb.audio->frames(), _film->audio_frame_rate ()); } - if (_audio) { - boost::optional<Time> audio_done_up_to; - for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done ()) { - continue; + /* Emit the earliest thing */ + + shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded); + shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded); + shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded); + shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded); + + /* Will be set to false if we shouldn't consume the peeked DecodedThing */ + bool consume = true; + + if (dv && _video) { + + if (_just_did_inaccurate_seek) { + + /* Just emit; no subtlety */ + emit_video (earliest_piece, dv); + step_video_position (dv); + + } else if (dv->dcp_time > _video_position) { + + /* Too far ahead */ + + list<shared_ptr<Piece> >::iterator i = _pieces.begin(); + while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) { + ++i; } - shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); - if (ad && ad->has_audio ()) { - audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position); + if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) { + /* We're outside all video content */ + emit_black (); + _statistics.video.black++; + } else { + /* We're inside some video; repeat the frame */ + _last_incoming_video.video->dcp_time = _video_position; + emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video); + step_video_position (_last_incoming_video.video); + _statistics.video.repeat++; } + + consume = false; + + } else if (dv->dcp_time == _video_position) { + /* We're ok */ + emit_video (earliest_piece, dv); + step_video_position (dv); + _statistics.video.good++; + } else { + /* Too far behind: skip */ + _statistics.video.skip++; } - if (audio_done_up_to) { - TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ()); - Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + _just_did_inaccurate_seek = false; + + } else if (da && _audio) { + + if (da->dcp_time > _audio_position) { + /* Too far ahead */ + emit_silence (da->dcp_time - _audio_position); + consume = false; + _statistics.audio.silence += (da->dcp_time - _audio_position); + } else if (da->dcp_time == _audio_position) { + /* We're ok */ + emit_audio (earliest_piece, da); + _statistics.audio.good += da->data->frames(); + } else { + /* Too far behind: skip */ + _statistics.audio.skip += da->data->frames(); } - } + } else if (dis && _video) { + _image_subtitle.piece = earliest_piece; + _image_subtitle.subtitle = dis; + update_subtitle_from_image (); + } else if (dts && _video) { + _text_subtitle.piece = earliest_piece; + _text_subtitle.subtitle = dts; + update_subtitle_from_text (); + } + + if (consume) { + earliest_piece->decoder->consume (); + } + return false; } -/** @param extra Amount of extra time to add to the content frame's time (for repeat) */ void -Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra) +Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video) { /* Keep a note of what came in so that we can repeat it if required */ _last_incoming_video.weak_piece = weak_piece; - _last_incoming_video.image = image; - _last_incoming_video.eyes = eyes; - _last_incoming_video.same = same; - _last_incoming_video.frame = frame; - _last_incoming_video.extra = extra; + _last_incoming_video.video = video; shared_ptr<Piece> piece = weak_piece.lock (); if (!piece) { @@ -262,22 +271,17 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content); assert (content); - FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate()); - if (frc.skip && (frame % 2) == 1) { - return; - } + FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate()); - Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate()); - if (content->trimmed (relative_time)) { - return; + dcp::Size image_size = content->scale().size (content, _video_container_size); + if (_approximate_size) { + image_size.width &= ~3; + image_size.height &= ~3; } - Time const time = content->position() + relative_time + extra - content->trim_start (); - libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ()); - shared_ptr<PlayerImage> pi ( new PlayerImage ( - image, + video->image, content->crop(), image_size, _video_container_size, @@ -285,11 +289,15 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image ) ); - if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) { + if ( + _film->with_subtitles () && + _out_subtitle.image && + video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to + ) { Position<int> const container_offset ( (_video_container_size.width - image_size.width) / 2, - (_video_container_size.height - image_size.width) / 2 + (_video_container_size.height - image_size.height) / 2 ); pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset); @@ -300,18 +308,25 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image _last_video = piece->content; #endif - Video (pi, eyes, content->colour_conversion(), same, time); - + Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time); + _last_emit_was_black = false; - _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate()); +} - if (frc.repeat > 1 && !piece->repeating ()) { - piece->set_repeat (_last_incoming_video, frc.repeat - 1); +void +Player::step_video_position (shared_ptr<DecodedVideo> video) +{ + /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */ + if (video->eyes != EYES_LEFT) { + /* This assumes that the video-frames-to-time conversion is exact + so that there are no accumulated errors caused by rounding. + */ + _video_position += DCPTime::from_frames (1, _film->video_frame_rate ()); } } void -Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame) +Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio) { shared_ptr<Piece> piece = weak_piece.lock (); if (!piece) { @@ -323,72 +338,55 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers /* Gain */ if (content->audio_gain() != 0) { - shared_ptr<AudioBuffers> gain (new AudioBuffers (audio)); + shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data)); gain->apply_gain (content->audio_gain ()); - audio = gain; - } - - /* Resample */ - if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) { - shared_ptr<Resampler> r = resampler (content, true); - pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame); - audio = ro.first; - frame = ro.second; + audio->data = gain; } - - Time const relative_time = _film->audio_frames_to_time (frame); - if (content->trimmed (relative_time)) { - return; - } - - Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start (); - /* Remap channels */ - shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames())); + shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames())); dcp_mapped->make_silent (); - AudioMapping map = content->audio_mapping (); for (int i = 0; i < map.content_channels(); ++i) { for (int j = 0; j < _film->audio_channels(); ++j) { - if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) { + if (map.get (i, static_cast<dcp::Channel> (j)) > 0) { dcp_mapped->accumulate_channel ( - audio.get(), + audio->data.get(), i, - static_cast<libdcp::Channel> (j), - map.get (i, static_cast<libdcp::Channel> (j)) + static_cast<dcp::Channel> (j), + map.get (i, static_cast<dcp::Channel> (j)) ); } } } - audio = dcp_mapped; + audio->data = dcp_mapped; - /* We must cut off anything that comes before the start of all time */ - if (time < 0) { - int const frames = - time * _film->audio_frame_rate() / TIME_HZ; - if (frames >= audio->frames ()) { + /* Delay */ + audio->dcp_time += DCPTime::from_seconds (content->audio_delay() / 1000.0); + if (audio->dcp_time < DCPTime (0)) { + int const frames = - audio->dcp_time.frames (_film->audio_frame_rate()); + if (frames >= audio->data->frames ()) { return; } - shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames)); - trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0); + shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames)); + trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0); - audio = trimmed; - time = 0; + audio->data = trimmed; + audio->dcp_time = DCPTime (); } - _audio_merger.push (audio, time); - piece->audio_position += _film->audio_frames_to_time (audio->frames ()); + _audio_merger.push (audio->data, audio->dcp_time); } void Player::flush () { - TimedAudioBuffers<Time> tb = _audio_merger.flush (); + TimedAudioBuffers tb = _audio_merger.flush (); if (_audio && tb.audio) { Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + _audio_position += DCPTime::from_frames (tb.audio->frames (), _film->audio_frame_rate ()); } while (_video && _video_position < _audio_position) { @@ -396,9 +394,8 @@ Player::flush () } while (_audio && _audio_position < _video_position) { - emit_silence (_film->time_to_audio_frames (_video_position - _audio_position)); + emit_silence (_video_position - _audio_position); } - } /** Seek so that the next pass() will yield (approximately) the requested frame. @@ -406,7 +403,7 @@ Player::flush () * @return true on error */ void -Player::seek (Time t, bool accurate) +Player::seek (DCPTime t, bool accurate) { if (!_have_valid_pieces) { setup_pieces (); @@ -417,96 +414,127 @@ Player::seek (Time t, bool accurate) } for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content); - if (!vc) { - continue; - } - /* s is the offset of t from the start position of this content */ - Time s = t - vc->position (); - s = max (static_cast<Time> (0), s); - s = min (vc->length_after_trim(), s); + DCPTime s = t - (*i)->content->position (); + s = max (static_cast<DCPTime> (0), s); + s = min ((*i)->content->length_after_trim(), s); - /* Hence set the piece positions to the `global' time */ - (*i)->video_position = (*i)->audio_position = vc->position() + s; + /* Convert this to the content time */ + ContentTime ct (s + (*i)->content->trim_start(), (*i)->frc); /* And seek the decoder */ - dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek ( - vc->time_to_content_video_frames (s + vc->trim_start ()), accurate - ); - - (*i)->reset_repeat (); + (*i)->decoder->seek (ct, accurate); } - _video_position = _audio_position = t; + _video_position = t.round_up (_film->video_frame_rate()); + _audio_position = t.round_up (_film->audio_frame_rate()); - /* XXX: don't seek audio because we don't need to... */ + _audio_merger.clear (_audio_position); + + if (!accurate) { + /* We just did an inaccurate seek, so it's likely that the next thing seen + out of pass() will be a fair distance from _{video,audio}_position. Setting + this flag stops pass() from trying to fix that: we assume that if it + was an inaccurate seek then the caller does not care too much about + inserting black/silence to keep the time tidy. + */ + _just_did_inaccurate_seek = true; + } } void Player::setup_pieces () { list<shared_ptr<Piece> > old_pieces = _pieces; - _pieces.clear (); ContentList content = _playlist->content (); - sort (content.begin(), content.end(), ContentSorter ()); for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { if (!(*i)->paths_valid ()) { continue; } + + shared_ptr<Decoder> decoder; + optional<FrameRateChange> frc; + + /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */ + DCPTime best_overlap_t; + shared_ptr<VideoContent> best_overlap; + for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j); + if (!vc) { + continue; + } + + DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end()); + if (overlap > best_overlap_t) { + best_overlap = vc; + best_overlap_t = overlap; + } + } - shared_ptr<Piece> piece (new Piece (*i)); - - /* XXX: into content? */ + optional<FrameRateChange> best_overlap_frc; + if (best_overlap) { + best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ()); + } else { + /* No video overlap; e.g. if the DCP is just audio */ + best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ()); + } + /* FFmpeg */ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); if (fc) { - shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio)); - - fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0)); - fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2)); - fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4)); - - fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true); - piece->decoder = fd; + decoder.reset (new FFmpegDecoder (fc, _film->log(), _video, _audio, _film->with_subtitles ())); + frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } - + + /* ImageContent */ shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i); if (ic) { - bool reusing = false; - /* See if we can re-use an old ImageDecoder */ for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) { shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder); if (imd && imd->content() == ic) { - piece = *j; - reusing = true; + decoder = imd; } } - if (!reusing) { - shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic)); - id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0)); - piece->decoder = id; + if (!decoder) { + decoder.reset (new ImageDecoder (ic)); } + + frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate()); } + /* SndfileContent */ shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); if (sc) { - shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc)); - sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2)); + decoder.reset (new SndfileDecoder (sc)); + frc = best_overlap_frc; + } - piece->decoder = sd; + /* SubRipContent */ + shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i); + if (rc) { + decoder.reset (new SubRipDecoder (rc)); + frc = best_overlap_frc; } - _pieces.push_back (piece); + ContentTime st ((*i)->trim_start(), frc.get ()); + decoder->seek (st, true); + + _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ()))); } _have_valid_pieces = true; + + /* The Piece for the _last_incoming_video will no longer be valid */ + _last_incoming_video.video.reset (); + + _video_position = DCPTime (); + _audio_position = DCPTime (); } void @@ -532,7 +560,8 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent) property == SubtitleContentProperty::SUBTITLE_SCALE ) { - update_subtitle (); + update_subtitle_from_image (); + update_subtitle_from_text (); Changed (frequent); } else if ( @@ -557,7 +586,7 @@ Player::playlist_changed () } void -Player::set_video_container_size (libdcp::Size s) +Player::set_video_container_size (dcp::Size s) { _video_container_size = s; @@ -575,29 +604,6 @@ Player::set_video_container_size (libdcp::Size s) ); } -shared_ptr<Resampler> -Player::resampler (shared_ptr<AudioContent> c, bool create) -{ - map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c); - if (i != _resamplers.end ()) { - return i->second; - } - - if (!create) { - return shared_ptr<Resampler> (); - } - - _film->log()->log ( - String::compose ( - "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels() - ) - ); - - shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); - _resamplers[c] = r; - return r; -} - void Player::emit_black () { @@ -606,22 +612,23 @@ Player::emit_black () #endif Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position); - _video_position += _film->video_frames_to_time (1); + _video_position += DCPTime::from_frames (1, _film->video_frame_rate ()); _last_emit_was_black = true; } void -Player::emit_silence (OutputAudioFrame most) +Player::emit_silence (DCPTime most) { - if (most == 0) { + if (most == DCPTime ()) { return; } - OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2); - shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N)); + DCPTime t = min (most, DCPTime::from_seconds (0.5)); + shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t.frames (_film->audio_frame_rate()))); silence->make_silent (); Audio (silence, _audio_position); - _audio_position += _film->audio_frames_to_time (N); + + _audio_position += t; } void @@ -638,26 +645,14 @@ Player::film_changed (Film::Property p) } void -Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to) +Player::update_subtitle_from_image () { - _in_subtitle.piece = weak_piece; - _in_subtitle.image = image; - _in_subtitle.rect = rect; - _in_subtitle.from = from; - _in_subtitle.to = to; - - update_subtitle (); -} - -void -Player::update_subtitle () -{ - shared_ptr<Piece> piece = _in_subtitle.piece.lock (); + shared_ptr<Piece> piece = _image_subtitle.piece.lock (); if (!piece) { return; } - if (!_in_subtitle.image) { + if (!_image_subtitle.subtitle->image) { _out_subtitle.image.reset (); return; } @@ -665,8 +660,8 @@ Player::update_subtitle () shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content); assert (sc); - dcpomatic::Rect<double> in_rect = _in_subtitle.rect; - libdcp::Size scaled_size; + dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect; + dcp::Size scaled_size; in_rect.x += sc->subtitle_x_offset (); in_rect.y += sc->subtitle_y_offset (); @@ -690,24 +685,15 @@ Player::update_subtitle () _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2))); _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2))); - _out_subtitle.image = _in_subtitle.image->scale ( + _out_subtitle.image = _image_subtitle.subtitle->image->scale ( scaled_size, Scaler::from_id ("bicubic"), - _in_subtitle.image->pixel_format (), + _image_subtitle.subtitle->image->pixel_format (), true ); - - /* XXX: hack */ - Time from = _in_subtitle.from; - Time to = _in_subtitle.to; - shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content); - if (vc) { - from = rint (from * vc->video_frame_rate() / _film->video_frame_rate()); - to = rint (to * vc->video_frame_rate() / _film->video_frame_rate()); - } - _out_subtitle.from = from + piece->content->position (); - _out_subtitle.to = to + piece->content->position (); + _out_subtitle.from = _image_subtitle.subtitle->dcp_time + piece->content->position (); + _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to + piece->content->position (); } /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles. @@ -716,27 +702,40 @@ Player::update_subtitle () bool Player::repeat_last_video () { - if (!_last_incoming_video.image || !_have_valid_pieces) { + if (!_last_incoming_video.video || !_have_valid_pieces) { return false; } - process_video ( + emit_video ( _last_incoming_video.weak_piece, - _last_incoming_video.image, - _last_incoming_video.eyes, - _last_incoming_video.same, - _last_incoming_video.frame, - _last_incoming_video.extra + _last_incoming_video.video ); return true; } +void +Player::update_subtitle_from_text () +{ + if (_text_subtitle.subtitle->subs.empty ()) { + _out_subtitle.image.reset (); + return; + } + + render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position); +} + +void +Player::set_approximate_size () +{ + _approximate_size = true; +} + PlayerImage::PlayerImage ( shared_ptr<const Image> in, Crop crop, - libdcp::Size inter_size, - libdcp::Size out_size, + dcp::Size inter_size, + dcp::Size out_size, Scaler const * scaler ) : _in (in) @@ -756,10 +755,10 @@ PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos) } shared_ptr<Image> -PlayerImage::image () +PlayerImage::image (AVPixelFormat format, bool aligned) { - shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false); - + shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned); + Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2); if (_subtitle_image) { @@ -768,3 +767,16 @@ PlayerImage::image () return out; } + +void +PlayerStatistics::dump (shared_ptr<Log> log) const +{ + log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat)); + log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds())); +} + +PlayerStatistics const & +Player::statistics () const +{ + return _statistics; +} diff --git a/src/lib/player.h b/src/lib/player.h index 11cc99e77..6860807b3 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2014 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 @@ -29,6 +29,7 @@ #include "rect.h" #include "audio_merger.h" #include "audio_content.h" +#include "decoded.h" class Job; class Film; @@ -36,45 +37,66 @@ class Playlist; class AudioContent; class Piece; class Image; -class Resampler; -/** @class Player - * @brief A class which can `play' a Playlist; emitting its audio and video. - */ - -struct IncomingVideo -{ -public: - boost::weak_ptr<Piece> weak_piece; - boost::shared_ptr<const Image> image; - Eyes eyes; - bool same; - VideoContent::Frame frame; - Time extra; -}; - -/** A wrapper for an Image which contains some pending operations; these may +/** @class PlayerImage + * @brief A wrapper for an Image which contains some pending operations; these may * not be necessary if the receiver of the PlayerImage throws it away. */ class PlayerImage { public: - PlayerImage (boost::shared_ptr<const Image>, Crop, libdcp::Size, libdcp::Size, Scaler const *); + PlayerImage (boost::shared_ptr<const Image>, Crop, dcp::Size, dcp::Size, Scaler const *); void set_subtitle (boost::shared_ptr<const Image>, Position<int>); - boost::shared_ptr<Image> image (); + boost::shared_ptr<Image> image (AVPixelFormat, bool); private: boost::shared_ptr<const Image> _in; Crop _crop; - libdcp::Size _inter_size; - libdcp::Size _out_size; + dcp::Size _inter_size; + dcp::Size _out_size; Scaler const * _scaler; boost::shared_ptr<const Image> _subtitle_image; Position<int> _subtitle_position; }; + +class PlayerStatistics +{ +public: + struct Video { + Video () + : black (0) + , repeat (0) + , good (0) + , skip (0) + {} + + int black; + int repeat; + int good; + int skip; + } video; + + struct Audio { + Audio () + : silence (0) + , good (0) + , skip (0) + {} + + DCPTime silence; + int64_t good; + int64_t skip; + } audio; + + void dump (boost::shared_ptr<Log>) const; +}; +/** @class Player + * @brief A class which can `play' a Playlist; emitting its audio and video. + */ + class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable { public: @@ -84,16 +106,19 @@ public: void disable_audio (); bool pass (); - void seek (Time, bool); + void seek (DCPTime, bool); - Time video_position () const { + DCPTime video_position () const { return _video_position; } - void set_video_container_size (libdcp::Size); + void set_video_container_size (dcp::Size); + void set_approximate_size (); bool repeat_last_video (); + PlayerStatistics const & statistics () const; + /** Emitted when a video frame is ready. * First parameter is the video image. * Second parameter is the eye(s) that should see this image. @@ -101,10 +126,10 @@ public: * Fourth parameter is true if the image is the same as the last one that was emitted. * Fifth parameter is the time. */ - boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video; + boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime)> Video; /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio; + boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, DCPTime)> Audio; /** Emitted when something has changed such that if we went back and emitted * the last frame again it would look different. This is not emitted after @@ -118,19 +143,19 @@ private: friend class PlayerWrapper; friend class Piece; - void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time); - void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); - void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time); void setup_pieces (); void playlist_changed (); void content_changed (boost::weak_ptr<Content>, int, bool); - void do_seek (Time, bool); + void do_seek (DCPTime, bool); void flush (); void emit_black (); - void emit_silence (OutputAudioFrame); - boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool); + void emit_silence (DCPTime); void film_changed (Film::Property); - void update_subtitle (); + void update_subtitle_from_image (); + void update_subtitle_from_text (); + void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>); + void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>); + void step_video_position (boost::shared_ptr<DecodedVideo>); boost::shared_ptr<const Film> _film; boost::shared_ptr<const Playlist> _playlist; @@ -143,29 +168,30 @@ private: std::list<boost::shared_ptr<Piece> > _pieces; /** The time after the last video that we emitted */ - Time _video_position; + DCPTime _video_position; /** The time after the last audio that we emitted */ - Time _audio_position; + DCPTime _audio_position; - AudioMerger<Time, AudioContent::Frame> _audio_merger; + AudioMerger _audio_merger; - libdcp::Size _video_container_size; + dcp::Size _video_container_size; boost::shared_ptr<PlayerImage> _black_frame; - std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers; struct { boost::weak_ptr<Piece> piece; - boost::shared_ptr<Image> image; - dcpomatic::Rect<double> rect; - Time from; - Time to; - } _in_subtitle; + boost::shared_ptr<DecodedImageSubtitle> subtitle; + } _image_subtitle; struct { - boost::shared_ptr<Image> image; + boost::weak_ptr<Piece> piece; + boost::shared_ptr<DecodedTextSubtitle> subtitle; + } _text_subtitle; + + struct { Position<int> position; - Time from; - Time to; + boost::shared_ptr<Image> image; + DCPTime from; + DCPTime to; } _out_subtitle; #ifdef DCPOMATIC_DEBUG @@ -174,7 +200,15 @@ private: bool _last_emit_was_black; - IncomingVideo _last_incoming_video; + struct { + boost::weak_ptr<Piece> weak_piece; + boost::shared_ptr<DecodedVideo> video; + } _last_incoming_video; + + bool _just_did_inaccurate_seek; + bool _approximate_size; + + PlayerStatistics _statistics; boost::signals2::scoped_connection _playlist_changed_connection; boost::signals2::scoped_connection _playlist_content_changed_connection; diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index 608323e3b..c46e65d8b 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -81,14 +81,14 @@ Playlist::maybe_sequence_video () _sequencing_video = true; ContentList cl = _content; - Time next = 0; + DCPTime next; for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { if (!dynamic_pointer_cast<VideoContent> (*i)) { continue; } (*i)->set_position (next); - next = (*i)->end() + 1; + next = (*i)->end() + DCPTime::delta (); } /* This won't change order, so it does not need a sort */ @@ -254,12 +254,12 @@ Playlist::best_dcp_frame_rate () const return best->dcp; } -Time +DCPTime Playlist::length () const { - Time len = 0; + DCPTime len; for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { - len = max (len, (*i)->end() + 1); + len = max (len, (*i)->end() + DCPTime::delta ()); } return len; @@ -279,10 +279,10 @@ Playlist::reconnect () } } -Time +DCPTime Playlist::video_end () const { - Time end = 0; + DCPTime end; for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { if (dynamic_pointer_cast<const VideoContent> (*i)) { end = max (end, (*i)->end ()); @@ -292,6 +292,23 @@ Playlist::video_end () const return end; } +FrameRateChange +Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const +{ + for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { + shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i); + if (!vc) { + break; + } + + if (vc->position() >= t && t < vc->end()) { + return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate); + } + } + + return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate); +} + void Playlist::set_sequence_video (bool s) { @@ -314,7 +331,7 @@ Playlist::content () const void Playlist::repeat (ContentList c, int n) { - pair<Time, Time> range (TIME_MAX, 0); + pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ()); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { range.first = min (range.first, (*i)->position ()); range.second = max (range.second, (*i)->position ()); @@ -322,7 +339,7 @@ Playlist::repeat (ContentList c, int n) range.second = max (range.second, (*i)->end ()); } - Time pos = range.second; + DCPTime pos = range.second; for (int i = 0; i < n; ++i) { for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { shared_ptr<Content> copy = (*i)->clone (); @@ -355,7 +372,7 @@ Playlist::move_earlier (shared_ptr<Content> c) return; } - Time const p = (*previous)->position (); + DCPTime const p = (*previous)->position (); (*previous)->set_position (p + c->length_after_trim ()); c->set_position (p); sort (_content.begin(), _content.end(), ContentSorter ()); @@ -382,7 +399,7 @@ Playlist::move_later (shared_ptr<Content> c) return; } - Time const p = (*next)->position (); + DCPTime const p = (*next)->position (); (*next)->set_position (c->position ()); c->set_position (p + c->length_after_trim ()); sort (_content.begin(), _content.end(), ContentSorter ()); diff --git a/src/lib/playlist.h b/src/lib/playlist.h index 394023f5c..444eb9ae5 100644 --- a/src/lib/playlist.h +++ b/src/lib/playlist.h @@ -25,6 +25,7 @@ #include <boost/enable_shared_from_this.hpp> #include "ffmpeg_content.h" #include "audio_mapping.h" +#include "util.h" class Content; class FFmpegContent; @@ -70,10 +71,11 @@ public: std::string video_identifier () const; - Time length () const; + DCPTime length () const; int best_dcp_frame_rate () const; - Time video_end () const; + DCPTime video_end () const; + FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const; void set_sequence_video (bool); void maybe_sequence_video (); diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc index a47b2101e..275f4ef15 100644 --- a/src/lib/ratio.cc +++ b/src/lib/ratio.cc @@ -17,7 +17,7 @@ */ -#include <libdcp/types.h> +#include <dcp/types.h> #include "ratio.h" #include "util.h" diff --git a/src/lib/ratio.h b/src/lib/ratio.h index f3354f1b6..cd7d0d6e4 100644 --- a/src/lib/ratio.h +++ b/src/lib/ratio.h @@ -22,7 +22,7 @@ #include <vector> #include <boost/utility.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> class Ratio : public boost::noncopyable { diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc new file mode 100644 index 000000000..c18cb4272 --- /dev/null +++ b/src/lib/render_subtitles.cc @@ -0,0 +1,150 @@ +/* + Copyright (C) 2014 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 <cairomm/cairomm.h> +#include <pangomm.h> +#include "render_subtitles.h" +#include "types.h" +#include "image.h" + +using std::list; +using std::cout; +using std::string; +using std::min; +using std::max; +using boost::shared_ptr; +using boost::optional; + +static int +calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset) +{ + switch (v_align) { + case dcp::TOP: + return (v_position / 100) * target_height - offset; + case dcp::CENTER: + return (0.5 + v_position / 100) * target_height - offset; + case dcp::BOTTOM: + return (1.0 - v_position / 100) * target_height - offset; + } + + return 0; +} + +void +render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target, shared_ptr<Image>& image, Position<int>& position) +{ + if (subtitles.empty ()) { + image.reset (); + return; + } + + /* Estimate height that the subtitle image needs to be */ + optional<int> top; + optional<int> bottom; + for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) { + int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0); + int const t = b - i->size() * target.height / (11 * 72); + + top = min (top.get_value_or (t), t); + bottom = max (bottom.get_value_or (b), b); + } + + top = top.get() - 32; + bottom = bottom.get() + 32; + + image.reset (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false)); + image->make_black (); + + Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create ( + image->data()[0], + Cairo::FORMAT_ARGB32, + image->size().width, + image->size().height, + Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width) + ); + + Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface); + Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context); + + layout->set_width (image->size().width * PANGO_SCALE); + layout->set_alignment (Pango::ALIGN_CENTER); + + context->set_line_width (1); + + for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) { + string f = i->font (); + if (f.empty ()) { + f = "Arial"; + } + Pango::FontDescription font (f); + font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE); + if (i->italic ()) { + font.set_style (Pango::STYLE_ITALIC); + } + layout->set_font_description (font); + layout->set_text (i->text ()); + + /* Compute fade factor */ + /* XXX */ + float fade_factor = 1; +#if 0 + dcp::Time now (time * 1000 / (4 * TIME_HZ)); + dcp::Time end_fade_up = i->in() + i->fade_up_time (); + dcp::Time start_fade_down = i->out() - i->fade_down_time (); + if (now < end_fade_up) { + fade_factor = (now - i->in()) / i->fade_up_time(); + } else if (now > start_fade_down) { + fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ()); + } +#endif + + layout->update_from_cairo_context (context); + + /* Work out position */ + + int const x = 0; + int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ()); + + if (i->effect() == dcp::SHADOW) { + /* Drop-shadow effect */ + dcp::Color const ec = i->effect_color (); + context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor); + context->move_to (x + 4, y + 4); + layout->add_to_cairo_context (context); + context->fill (); + } + + /* The actual subtitle */ + context->move_to (x, y); + dcp::Color const c = i->color (); + context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor); + layout->add_to_cairo_context (context); + context->fill (); + + if (i->effect() == dcp::BORDER) { + /* Border effect */ + context->move_to (x, y); + dcp::Color ec = i->effect_color (); + context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor); + layout->add_to_cairo_context (context); + context->stroke (); + } + } +} + diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h new file mode 100644 index 000000000..743339baf --- /dev/null +++ b/src/lib/render_subtitles.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2014 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 <dcp/subtitle_string.h> +#include <dcp/util.h> +#include "position.h" + +class Image; + +void +render_subtitles (std::list<dcp::SubtitleString>, dcp::Size, boost::shared_ptr<Image> &, Position<int> &); diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc index d897bf562..00121384d 100644 --- a/src/lib/resampler.cc +++ b/src/lib/resampler.cc @@ -64,11 +64,9 @@ Resampler::~Resampler () swr_free (&_swr_context); } -pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> -Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame) +shared_ptr<const AudioBuffers> +Resampler::run (shared_ptr<const AudioBuffers> in) { - AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate; - /* Compute the resampled frames count and add 32 for luck */ int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32; shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames)); @@ -84,7 +82,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame) } resampled->set_frames (resampled_frames); - return make_pair (resampled, resamp_time); + return resampled; } shared_ptr<const AudioBuffers> diff --git a/src/lib/resampler.h b/src/lib/resampler.h index 69ec83ba9..4ee11a7f0 100644 --- a/src/lib/resampler.h +++ b/src/lib/resampler.h @@ -33,7 +33,7 @@ public: Resampler (int, int, int); ~Resampler (); - std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); + boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>); boost::shared_ptr<const AudioBuffers> flush (); private: diff --git a/src/lib/server.cc b/src/lib/server.cc index 1f3f61e42..bf7541c33 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -57,7 +57,7 @@ using boost::bind; using boost::scoped_array; using boost::optional; using boost::lexical_cast; -using libdcp::Size; +using dcp::Size; Server::Server (shared_ptr<Log> log, bool verbose) : _log (log) @@ -85,7 +85,7 @@ Server::process (shared_ptr<Socket> socket, struct timeval& after_read, struct t return -1; } - libdcp::Size size ( + dcp::Size size ( xml->number_child<int> ("Width"), xml->number_child<int> ("Height") ); diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index 98171a843..71b549d51 100644 --- a/src/lib/sndfile_content.cc +++ b/src/lib/sndfile_content.cc @@ -49,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml: , _audio_mapping (node->node_child ("AudioMapping"), version) { _audio_channels = node->number_child<int> ("AudioChannels"); - _audio_length = node->number_child<AudioContent::Frame> ("AudioLength"); + _audio_length = ContentTime (node->number_child<int64_t> ("AudioLength")); _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); } @@ -81,7 +81,7 @@ SndfileContent::information () const _("%1 channels, %2kHz, %3 samples"), audio_channels(), content_audio_frame_rate() / 1000.0, - audio_length() + audio_length().frames (content_audio_frame_rate ()) ); return s.str (); @@ -102,10 +102,7 @@ SndfileContent::examine (shared_ptr<Job> job) job->set_progress_unknown (); Content::examine (job); - shared_ptr<const Film> film = _film.lock (); - assert (film); - - SndfileDecoder dec (film, shared_from_this()); + SndfileDecoder dec (shared_from_this()); { boost::mutex::scoped_lock lm (_mutex); @@ -136,26 +133,17 @@ SndfileContent::as_xml (xmlpp::Node* node) const AudioContent::as_xml (node); node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (audio_channels ())); - node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length ())); + node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length().get ())); node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (content_audio_frame_rate ())); _audio_mapping.as_xml (node->add_child("AudioMapping")); } -Time +DCPTime SndfileContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); - - OutputAudioFrame const len = divide_with_round (audio_length() * output_audio_frame_rate(), content_audio_frame_rate ()); - - /* XXX: this depends on whether, alongside this audio, we are running video slower or faster than - it should be. The calculation above works out the output audio frames assuming that we are just - resampling the audio: it would be incomplete if, for example, we were running this audio alongside - 25fps video that was being run at 24fps. - */ - - return film->audio_frames_to_time (len); + return DCPTime (audio_length(), film->active_frame_rate_change (position ())); } int diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index 701ff16b2..04d157131 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -44,7 +44,7 @@ public: std::string technical_summary () const; std::string information () const; void as_xml (xmlpp::Node *) const; - Time full_length () const; + DCPTime full_length () const; /* AudioContent */ int audio_channels () const { @@ -52,7 +52,7 @@ public: return _audio_channels; } - AudioContent::Frame audio_length () const { + ContentTime audio_length () const { boost::mutex::scoped_lock lm (_mutex); return _audio_length; } @@ -75,7 +75,7 @@ public: private: int _audio_channels; - AudioContent::Frame _audio_length; + ContentTime _audio_length; int _audio_frame_rate; AudioMapping _audio_mapping; }; diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index f66a7c7dc..67bb25e0d 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -25,7 +25,6 @@ #include <sndfile.h> #include "sndfile_content.h" #include "sndfile_decoder.h" -#include "film.h" #include "exceptions.h" #include "audio_buffers.h" @@ -37,9 +36,8 @@ using std::min; using std::cout; using boost::shared_ptr; -SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c) - : Decoder (f) - , AudioDecoder (f, c) +SndfileDecoder::SndfileDecoder (shared_ptr<const SndfileContent> c) + : AudioDecoder (c) , _sndfile_content (c) , _deinterleave_buffer (0) { @@ -66,9 +64,13 @@ SndfileDecoder::~SndfileDecoder () delete[] _deinterleave_buffer; } -void +bool SndfileDecoder::pass () { + if (_remaining == 0) { + 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. */ @@ -101,9 +103,11 @@ SndfileDecoder::pass () } data->set_frames (this_time); - audio (data, _done); + audio (data, ContentTime::from_frames (_done, audio_frame_rate ())); _done += this_time; _remaining -= this_time; + + return _remaining == 0; } int @@ -112,10 +116,10 @@ SndfileDecoder::audio_channels () const return _info.channels; } -AudioContent::Frame +ContentTime SndfileDecoder::audio_length () const { - return _info.frames; + return ContentTime::from_frames (_info.frames, audio_frame_rate ()); } int @@ -124,8 +128,12 @@ SndfileDecoder::audio_frame_rate () const return _info.samplerate; } -bool -SndfileDecoder::done () const +void +SndfileDecoder::seek (ContentTime t, bool accurate) { - return _audio_position >= _sndfile_content->audio_length (); + Decoder::seek (t, accurate); + AudioDecoder::seek (t, accurate); + + _done = t.frames (audio_frame_rate ()); + _remaining = _info.frames - _done; } diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 77fa6d177..1de123917 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -26,21 +26,22 @@ class SndfileContent; class SndfileDecoder : public AudioDecoder { public: - SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>); + SndfileDecoder (boost::shared_ptr<const SndfileContent>); ~SndfileDecoder (); - void pass (); - bool done () const; + void seek (ContentTime, bool); int audio_channels () const; - AudioContent::Frame audio_length () const; + ContentTime audio_length () const; int audio_frame_rate () const; private: + bool pass (); + boost::shared_ptr<const SndfileContent> _sndfile_content; SNDFILE* _sndfile; SF_INFO _info; - AudioContent::Frame _done; - AudioContent::Frame _remaining; + int64_t _done; + int64_t _remaining; float* _deinterleave_buffer; }; diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc new file mode 100644 index 000000000..3eac98e63 --- /dev/null +++ b/src/lib/subrip.cc @@ -0,0 +1,236 @@ +/* + Copyright (C) 2014 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/algorithm/string.hpp> +#include "subrip.h" +#include "subrip_content.h" +#include "subrip_subtitle.h" +#include "cross.h" +#include "exceptions.h" + +#include "i18n.h" + +using std::string; +using std::list; +using std::vector; +using std::cout; +using boost::shared_ptr; +using boost::lexical_cast; +using boost::algorithm::trim; + +SubRip::SubRip (shared_ptr<const SubRipContent> content) +{ + FILE* f = fopen_boost (content->path (0), "r"); + if (!f) { + throw OpenFileError (content->path (0)); + } + + enum { + COUNTER, + METADATA, + CONTENT + } state = COUNTER; + + char buffer[256]; + int next_count = 1; + + boost::optional<SubRipSubtitle> current; + list<string> lines; + + while (!feof (f)) { + fgets (buffer, sizeof (buffer), f); + if (feof (f)) { + break; + } + + string line (buffer); + trim_right_if (line, boost::is_any_of ("\n\r")); + + switch (state) { + case COUNTER: + { + int x = 0; + try { + x = lexical_cast<int> (line); + } catch (...) { + + } + + if (x == next_count) { + state = METADATA; + ++next_count; + current = SubRipSubtitle (); + } else { + throw SubRipError (line, _("a subtitle count"), content->path (0)); + } + } + break; + case METADATA: + { + vector<string> p; + boost::algorithm::split (p, line, boost::algorithm::is_any_of (" ")); + if (p.size() != 3 && p.size() != 7) { + throw SubRipError (line, _("a time/position line"), content->path (0)); + } + + current->from = convert_time (p[0]); + current->to = convert_time (p[2]); + + if (p.size() > 3) { + current->x1 = convert_coordinate (p[3]); + current->x2 = convert_coordinate (p[4]); + current->y1 = convert_coordinate (p[5]); + current->y2 = convert_coordinate (p[6]); + } + state = CONTENT; + break; + } + case CONTENT: + if (line.empty ()) { + state = COUNTER; + current->pieces = convert_content (lines); + _subtitles.push_back (current.get ()); + current.reset (); + lines.clear (); + } else { + lines.push_back (line); + } + break; + } + } + + if (state == CONTENT) { + current->pieces = convert_content (lines); + _subtitles.push_back (current.get ()); + } + + fclose (f); +} + +ContentTime +SubRip::convert_time (string t) +{ + ContentTime r; + + vector<string> a; + boost::algorithm::split (a, t, boost::is_any_of (":")); + assert (a.size() == 3); + r += ContentTime::from_seconds (lexical_cast<int> (a[0]) * 60 * 60); + r += ContentTime::from_seconds (lexical_cast<int> (a[1]) * 60); + + vector<string> b; + boost::algorithm::split (b, a[2], boost::is_any_of (",")); + r += ContentTime::from_seconds (lexical_cast<int> (b[0])); + r += ContentTime::from_seconds (lexical_cast<float> (b[1]) / 1000); + + return r; +} + +int +SubRip::convert_coordinate (string t) +{ + vector<string> a; + boost::algorithm::split (a, t, boost::is_any_of (":")); + assert (a.size() == 2); + return lexical_cast<int> (a[1]); +} + +void +SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p) +{ + if (!p.text.empty ()) { + pieces.push_back (p); + p.text.clear (); + } +} + +list<SubRipSubtitlePiece> +SubRip::convert_content (list<string> t) +{ + list<SubRipSubtitlePiece> pieces; + + SubRipSubtitlePiece p; + + enum { + TEXT, + TAG + } state = TEXT; + + string tag; + + /* XXX: missing <font> support */ + /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might + not work, I think. + */ + + for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) { + for (size_t j = 0; j < i->size(); ++j) { + switch (state) { + case TEXT: + if ((*i)[j] == '<' || (*i)[j] == '{') { + state = TAG; + } else { + p.text += (*i)[j]; + } + break; + case TAG: + if ((*i)[j] == '>' || (*i)[j] == '}') { + if (tag == "b") { + maybe_content (pieces, p); + p.bold = true; + } else if (tag == "/b") { + maybe_content (pieces, p); + p.bold = false; + } else if (tag == "i") { + maybe_content (pieces, p); + p.italic = true; + } else if (tag == "/i") { + maybe_content (pieces, p); + p.italic = false; + } else if (tag == "u") { + maybe_content (pieces, p); + p.underline = true; + } else if (tag == "/u") { + maybe_content (pieces, p); + p.underline = false; + } + tag.clear (); + state = TEXT; + } else { + tag += (*i)[j]; + } + break; + } + } + } + + maybe_content (pieces, p); + + return pieces; +} + +ContentTime +SubRip::length () const +{ + if (_subtitles.empty ()) { + return ContentTime (); + } + + return _subtitles.back().to; +} diff --git a/src/lib/subrip.h b/src/lib/subrip.h new file mode 100644 index 000000000..e7d21675f --- /dev/null +++ b/src/lib/subrip.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2014 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_SUBRIP_H +#define DCPOMATIC_SUBRIP_H + +#include "subrip_subtitle.h" + +class SubRipContent; +class subrip_time_test; +class subrip_coordinate_test; +class subrip_content_test; +class subrip_parse_test; + +class SubRip +{ +public: + SubRip (boost::shared_ptr<const SubRipContent>); + + ContentTime length () const; + +protected: + std::vector<SubRipSubtitle> _subtitles; + +private: + friend class subrip_time_test; + friend class subrip_coordinate_test; + friend class subrip_content_test; + friend class subrip_parse_test; + + static ContentTime convert_time (std::string); + static int convert_coordinate (std::string); + static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>); + static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &); +}; + +#endif diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc new file mode 100644 index 000000000..9524cf96b --- /dev/null +++ b/src/lib/subrip_content.cc @@ -0,0 +1,110 @@ +/* + Copyright (C) 2014 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 "subrip_content.h" +#include "util.h" +#include "subrip.h" +#include "film.h" + +#include "i18n.h" + +using std::stringstream; +using std::string; +using std::cout; +using boost::shared_ptr; +using boost::lexical_cast; + +SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path) + : Content (film, path) + , SubtitleContent (film, path) +{ + +} + +SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version) + : Content (film, node) + , SubtitleContent (film, node, version) + , _length (node->number_child<int64_t> ("Length")) +{ + +} + +void +SubRipContent::examine (boost::shared_ptr<Job> job) +{ + Content::examine (job); + SubRip s (shared_from_this ()); + shared_ptr<const Film> film = _film.lock (); + DCPTime len (s.length (), film->active_frame_rate_change (position ())); + + boost::mutex::scoped_lock lm (_mutex); + _length = len; +} + +string +SubRipContent::summary () const +{ + return path_summary() + " " + _("[subtitles]"); +} + +string +SubRipContent::technical_summary () const +{ + return Content::technical_summary() + " - " + _("SubRip subtitles"); +} + +string +SubRipContent::information () const +{ + +} + +void +SubRipContent::as_xml (xmlpp::Node* node) const +{ + LocaleGuard lg; + + node->add_child("Type")->add_child_text ("SubRip"); + Content::as_xml (node); + SubtitleContent::as_xml (node); + node->add_child("Length")->add_child_text (lexical_cast<string> (_length.get ())); +} + +DCPTime +SubRipContent::full_length () const +{ + /* XXX: this assumes that the timing of the SubRip file is appropriate + for the DCP's frame rate. + */ + return _length; +} + +string +SubRipContent::identifier () const +{ + LocaleGuard lg; + + stringstream s; + s << Content::identifier() + << "_" << subtitle_scale() + << "_" << subtitle_x_offset() + << "_" << subtitle_y_offset(); + + return s.str (); +} diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h new file mode 100644 index 000000000..3a8380cec --- /dev/null +++ b/src/lib/subrip_content.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2014 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 "subtitle_content.h" + +class SubRipContent : public SubtitleContent +{ +public: + SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path); + SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int); + + boost::shared_ptr<SubRipContent> shared_from_this () { + return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ()); + } + + void examine (boost::shared_ptr<Job>); + std::string summary () const; + std::string technical_summary () const; + std::string information () const; + void as_xml (xmlpp::Node *) const; + DCPTime full_length () const; + std::string identifier () const; + +private: + DCPTime _length; +}; diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc new file mode 100644 index 000000000..47b6ea044 --- /dev/null +++ b/src/lib/subrip_decoder.cc @@ -0,0 +1,64 @@ +/* + Copyright (C) 2014 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 <dcp/subtitle_string.h> +#include "subrip_decoder.h" + +using std::list; +using boost::shared_ptr; + +SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content) + : SubRip (content) + , _next (0) +{ + +} + +bool +SubRipDecoder::pass () +{ + if (_next >= _subtitles.size ()) { + return true; + } + + list<dcp::SubtitleString> out; + for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) { + out.push_back ( + dcp::SubtitleString ( + "Arial", + i->italic, + dcp::Color (255, 255, 255), + 72, + dcp::Time (rint (_subtitles[_next].from.seconds() * 250)), + dcp::Time (rint (_subtitles[_next].to.seconds() * 250)), + 0.9, + dcp::BOTTOM, + i->text, + dcp::NONE, + dcp::Color (255, 255, 255), + 0, + 0 + ) + ); + } + + text_subtitle (out); + _next++; + return false; +} diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h new file mode 100644 index 000000000..6025c90e2 --- /dev/null +++ b/src/lib/subrip_decoder.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2014 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_SUBRIP_DECODER_H +#define DCPOMATIC_SUBRIP_DECODER_H + +#include "subtitle_decoder.h" +#include "subrip.h" + +class SubRipContent; + +class SubRipDecoder : public SubtitleDecoder, public SubRip +{ +public: + SubRipDecoder (boost::shared_ptr<const SubRipContent>); + +protected: + bool pass (); + +private: + size_t _next; +}; + +#endif diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h new file mode 100644 index 000000000..dd46b6c64 --- /dev/null +++ b/src/lib/subrip_subtitle.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2014 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_SUBRIP_SUBTITLE_H +#define DCPOMATIC_SUBRIP_SUBTITLE_H + +#include <boost/optional.hpp> +#include <dcp/types.h> +#include "types.h" +#include "dcpomatic_time.h" + +struct SubRipSubtitlePiece +{ + SubRipSubtitlePiece () + : bold (false) + , italic (false) + , underline (false) + {} + + std::string text; + bool bold; + bool italic; + bool underline; + dcp::Color color; +}; + +struct SubRipSubtitle +{ + SubRipSubtitle () + : from (0) + , to (0) + {} + + ContentTime from; + ContentTime to; + boost::optional<int> x1; + boost::optional<int> x2; + boost::optional<int> y1; + boost::optional<int> y2; + std::list<SubRipSubtitlePiece> pieces; +}; + +#endif diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc index 8f88574e5..4c6e60192 100644 --- a/src/lib/subtitle_content.cc +++ b/src/lib/subtitle_content.cc @@ -26,6 +26,7 @@ using std::string; using std::vector; +using std::cout; using boost::shared_ptr; using boost::lexical_cast; using boost::dynamic_pointer_cast; diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index c06f3d718..a9674fe92 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -20,20 +20,27 @@ #include <boost/shared_ptr.hpp> #include "subtitle_decoder.h" +using std::list; using boost::shared_ptr; +using boost::optional; -SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f) - : Decoder (f) +SubtitleDecoder::SubtitleDecoder () { } -/** Called by subclasses when a subtitle is ready. +/** Called by subclasses when an image subtitle is ready. * Image may be 0 to say that there is no current subtitle. */ void -SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to) +SubtitleDecoder::image_subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, ContentTime from, ContentTime to) { - Subtitle (image, rect, from, to); + _pending.push_back (shared_ptr<DecodedImageSubtitle> (new DecodedImageSubtitle (from, to, image, rect))); +} + +void +SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s) +{ + _pending.push_back (shared_ptr<DecodedTextSubtitle> (new DecodedTextSubtitle (s))); } diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index eeeadbd3f..78ee6801e 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -17,22 +17,28 @@ */ +#ifndef DCPOMATIC_SUBTITLE_DECODER_H +#define DCPOMATIC_SUBTITLE_DECODER_H + #include <boost/signals2.hpp> +#include <dcp/subtitle_string.h> #include "decoder.h" #include "rect.h" #include "types.h" +#include "decoded.h" class Film; -class TimedSubtitle; +class DCPTimedSubtitle; class Image; class SubtitleDecoder : public virtual Decoder { public: - SubtitleDecoder (boost::shared_ptr<const Film>); - - boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle; + SubtitleDecoder (); protected: - void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time); + void image_subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, ContentTime, ContentTime); + void text_subtitle (std::list<dcp::SubtitleString>); }; + +#endif diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 7b304cb35..fd4dfcef6 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -103,6 +103,7 @@ TranscodeJob::status () const return s.str (); } +/** @return Approximate remaining time in seconds */ int TranscodeJob::remaining_time () const { @@ -120,6 +121,5 @@ TranscodeJob::remaining_time () const } /* Compute approximate proposed length here, as it's only here that we need it */ - OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out(); - return left / fps; + return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps; } diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index 1c8f7e3eb..ba4d3b040 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -62,7 +62,8 @@ audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio) * @param e Encoder to use. */ Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j) - : _player (f->make_player ()) + : _film (f) + , _player (f->make_player ()) , _encoder (new Encoder (f, j)) , _finishing (false) { @@ -78,6 +79,8 @@ Transcoder::go () _finishing = true; _encoder->process_end (); + + _player->statistics().dump (_film->log ()); } float diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index d7736d4e8..25b2ef908 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -42,6 +42,7 @@ public: } private: + boost::shared_ptr<const Film> _film; boost::shared_ptr<Player> _player; boost::shared_ptr<Encoder> _encoder; bool _finishing; diff --git a/src/lib/types.h b/src/lib/types.h index 96b993a8e..dafea92f8 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -23,7 +23,8 @@ #include <vector> #include <stdint.h> #include <boost/shared_ptr.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> +#include "dcpomatic_time.h" class Content; class VideoContent; @@ -38,31 +39,25 @@ class AudioBuffers; */ #define SERVER_LINK_VERSION 1 -typedef int64_t Time; -#define TIME_MAX INT64_MAX -#define TIME_HZ ((Time) 96000) -typedef int64_t OutputAudioFrame; -typedef int OutputVideoFrame; typedef std::vector<boost::shared_ptr<Content> > ContentList; typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList; typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList; typedef std::vector<boost::shared_ptr<SubtitleContent> > SubtitleContentList; typedef std::vector<boost::shared_ptr<FFmpegContent> > FFmpegContentList; -template<class T> struct TimedAudioBuffers { TimedAudioBuffers () : time (0) {} - TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t) + TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, DCPTime t) : audio (a) , time (t) {} boost::shared_ptr<AudioBuffers> audio; - T time; + DCPTime time; }; enum VideoFrameType @@ -97,7 +92,7 @@ struct Crop /** Number of pixels to remove from the bottom */ int bottom; - libdcp::Size apply (libdcp::Size s, int minimum = 4) const { + dcp::Size apply (dcp::Size s, int minimum = 4) const { s.width -= left + right; s.height -= top + bottom; diff --git a/src/lib/util.cc b/src/lib/util.cc index 35385ab40..15109eee0 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -48,10 +48,11 @@ #include <openssl/md5.h> #include <magick/MagickCore.h> #include <magick/version.h> -#include <libdcp/version.h> -#include <libdcp/util.h> -#include <libdcp/signer_chain.h> -#include <libdcp/signer.h> +#include <pangomm/init.h> +#include <dcp/version.h> +#include <dcp/util.h> +#include <dcp/signer_chain.h> +#include <dcp/signer.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> @@ -102,7 +103,7 @@ using boost::shared_ptr; using boost::thread; using boost::lexical_cast; using boost::optional; -using libdcp::Size; +using dcp::Size; static boost::thread::id ui_thread; static boost::filesystem::path backtrace_file; @@ -252,7 +253,7 @@ dependency_version_summary () << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ") << MagickVersion << N_(", ") << N_("libssh ") << ssh_version (0) << N_(", ") - << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit; + << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit; return s.str (); } @@ -343,7 +344,8 @@ dcpomatic_setup () set_terminate (terminate); - libdcp::init (); + Pango::init (); + dcp::init (); Ratio::setup_ratios (); VideoContentScale::setup_scales (); @@ -492,33 +494,6 @@ md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job) return s.str (); } -static bool -about_equal (float a, float b) -{ - /* A film of F seconds at f FPS will be Ff frames; - Consider some delta FPS d, so if we run the same - film at (f + d) FPS it will last F(f + d) seconds. - - Hence the difference in length over the length of the film will - be F(f + d) - Ff frames - = Ff + Fd - Ff frames - = Fd frames - = Fd/f seconds - - So if we accept a difference of 1 frame, ie 1/f seconds, we can - say that - - 1/f = Fd/f - ie 1 = Fd - ie d = 1/F - - So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable - FPS error is 1/F ~= 0.0001 ~= 10-e4 - */ - - return (fabs (a - b) < 1e-4); -} - /** @param An arbitrary audio frame rate. * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ @@ -778,17 +753,6 @@ ensure_ui_thread () assert (boost::this_thread::get_id() == ui_thread); } -/** @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 (VideoContent::Frame v, float audio_sample_rate, float frames_per_second) -{ - return ((int64_t) v * audio_sample_rate / frames_per_second); -} - string audio_channel_name (int c) { @@ -816,44 +780,6 @@ audio_channel_name (int c) return channels[c]; } -FrameRateConversion::FrameRateConversion (float source, int dcp) - : skip (false) - , repeat (1) - , change_speed (false) -{ - if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) { - /* The difference between source and DCP frame rate will be lower - (i.e. better) if we skip. - */ - skip = true; - } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { - /* The difference between source and DCP frame rate would be better - if we repeated each frame once; it may be better still if we - repeated more than once. Work out the required repeat. - */ - repeat = round (dcp / source); - } - - change_speed = !about_equal (source * factor(), dcp); - - if (!skip && repeat == 1 && !change_speed) { - description = _("Content and DCP have the same rate.\n"); - } else { - if (skip) { - description = _("DCP will use every other frame of the content.\n"); - } else if (repeat == 2) { - description = _("Each content frame will be doubled in the DCP.\n"); - } else if (repeat > 2) { - description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1); - } - - if (change_speed) { - float const pc = dcp * 100 / (source * factor()); - description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc); - } - } -} - LocaleGuard::LocaleGuard () : _old (0) { @@ -896,7 +822,7 @@ tidy_for_filename (string f) return t; } -shared_ptr<const libdcp::Signer> +shared_ptr<const dcp::Signer> make_signer () { boost::filesystem::path const sd = Config::instance()->signer_chain_directory (); @@ -916,37 +842,37 @@ make_signer () if (!boost::filesystem::exists (p)) { boost::filesystem::remove_all (sd); boost::filesystem::create_directories (sd); - libdcp::make_signer_chain (sd, openssl_path ()); + dcp::make_signer_chain (sd, openssl_path ()); break; } ++i; } - libdcp::CertificateChain chain; + dcp::CertificateChain chain; { boost::filesystem::path p (sd); p /= "ca.self-signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); + chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p))); } { boost::filesystem::path p (sd); p /= "intermediate.signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); + chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p))); } { boost::filesystem::path p (sd); p /= "leaf.signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); + chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p))); } boost::filesystem::path signer_key (sd); signer_key /= "leaf.key"; - return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key)); + return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key)); } map<string, string> @@ -995,14 +921,14 @@ split_get_request (string url) return r; } -libdcp::Size -fit_ratio_within (float ratio, libdcp::Size full_frame) +dcp::Size +fit_ratio_within (float ratio, dcp::Size full_frame) { if (ratio < full_frame.ratio ()) { - return libdcp::Size (rint (full_frame.height * ratio), full_frame.height); + return dcp::Size (rint (full_frame.height * ratio), full_frame.height); } - return libdcp::Size (full_frame.width, rint (full_frame.width / ratio)); + return dcp::Size (full_frame.width, rint (full_frame.width / ratio)); } void * diff --git a/src/lib/util.h b/src/lib/util.h index c05680859..2ae97814c 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -31,7 +31,8 @@ #include <boost/asio.hpp> #include <boost/optional.hpp> #include <boost/filesystem.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> +#include <dcp/signer.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> @@ -76,44 +77,10 @@ extern bool valid_image_file (boost::filesystem::path); extern boost::filesystem::path mo_path (); #endif extern std::string tidy_for_filename (std::string); -extern boost::shared_ptr<const libdcp::Signer> make_signer (); -extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size); +extern boost::shared_ptr<const dcp::Signer> make_signer (); +extern dcp::Size fit_ratio_within (float ratio, dcp::Size); extern std::string entities_to_text (std::string e); extern std::map<std::string, std::string> split_get_request (std::string url); - -struct FrameRateConversion -{ - FrameRateConversion (float, int); - - /** @return factor by which to multiply a source frame rate - to get the effective rate after any skip or repeat has happened. - */ - float factor () const { - if (skip) { - return 0.5; - } - - return repeat; - } - - /** true to skip every other frame */ - bool skip; - /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */ - int repeat; - /** true if this DCP will run its video faster or slower than the source - * without taking into account `repeat' nor `skip'. - * (e.g. change_speed will be true if - * source is 29.97fps, DCP is 30fps - * source is 14.50fps, DCP is 30fps - * but not if - * source is 15.00fps, DCP is 30fps - * source is 12.50fps, DCP is 25fps) - */ - bool change_speed; - - std::string description; -}; - extern int dcp_audio_frame_rate (int); extern int stride_round_up (int, int const *, int); extern std::multimap<std::string, std::string> read_key_value (std::istream& s); @@ -164,8 +131,6 @@ private: int _timeout; }; -extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second); - class LocaleGuard { public: diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 966b8e8b3..c3aea2b0f 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -19,7 +19,7 @@ #include <iomanip> #include <libcxml/cxml.h> -#include <libdcp/colour_matrix.h> +#include <dcp/colour_matrix.h> #include "video_content.h" #include "video_examiner.h" #include "compose.hpp" @@ -61,7 +61,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f) setup_default_colour_conversion (); } -VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len) +VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len) : Content (f, s) , _video_length (len) , _video_frame_rate (0) @@ -84,7 +84,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p) VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version) : Content (f, node) { - _video_length = node->number_child<VideoContent::Frame> ("VideoLength"); + _video_length = ContentTime (node->number_child<int64_t> ("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"); @@ -155,7 +155,7 @@ 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("VideoLength")->add_child_text (lexical_cast<string> (_video_length.get ())); 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)); @@ -171,14 +171,14 @@ VideoContent::as_xml (xmlpp::Node* node) const void VideoContent::setup_default_colour_conversion () { - _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion; + _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion; } void VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d) { /* These examiner calls could call other content methods which take a lock on the mutex */ - libdcp::Size const vs = d->video_size (); + dcp::Size const vs = d->video_size (); float const vfr = d->video_frame_rate (); { @@ -317,20 +317,26 @@ VideoContent::set_video_frame_type (VideoFrameType t) string VideoContent::technical_summary () const { - return String::compose ("video: length %1, size %2x%3, rate %4", video_length(), video_size().width, video_size().height, video_frame_rate()); + return String::compose ( + "video: length %1, size %2x%3, rate %4", + video_length().seconds(), + video_size().width, + video_size().height, + video_frame_rate() + ); } -libdcp::Size +dcp::Size VideoContent::video_size_after_3d_split () const { - libdcp::Size const s = video_size (); + dcp::Size const s = video_size (); switch (video_frame_type ()) { case VIDEO_FRAME_TYPE_2D: return s; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: - return libdcp::Size (s.width / 2, s.height); + return dcp::Size (s.width / 2, s.height); case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: - return libdcp::Size (s.width, s.height / 2); + return dcp::Size (s.width, s.height / 2); } assert (false); @@ -348,28 +354,21 @@ VideoContent::set_colour_conversion (ColourConversion c) } /** @return Video size after 3D split and crop */ -libdcp::Size +dcp::Size VideoContent::video_size_after_crop () const { return crop().apply (video_size_after_3d_split ()); } /** @param t A time offset from the start of this piece of content. - * @return Corresponding frame index. + * @return Corresponding time with respect to the content. */ -VideoContent::Frame -VideoContent::time_to_content_video_frames (Time t) const +ContentTime +VideoContent::dcp_time_to_content_time (DCPTime t) const { shared_ptr<const Film> film = _film.lock (); assert (film); - - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate()); - - /* Here we are converting from time (in the DCP) to a frame number in the content. - Hence we need to use the DCP's frame rate and the double/skip correction, not - the source's rate. - */ - return t * film->video_frame_rate() / (frc.factor() * TIME_HZ); + return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate())); } VideoContentScale::VideoContentScale (Ratio const * r) @@ -446,8 +445,8 @@ VideoContentScale::name () const /** @param display_container Size of the container that we are displaying this content in. * @param film_container The size of the film's image. */ -libdcp::Size -VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const +dcp::Size +VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const { if (_ratio) { return fit_ratio_within (_ratio->ratio (), display_container); diff --git a/src/lib/video_content.h b/src/lib/video_content.h index ea4676cec..d2b19480f 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -45,7 +45,7 @@ public: VideoContentScale (bool); VideoContentScale (boost::shared_ptr<cxml::Node>); - libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const; + dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const; std::string id () const; std::string name () const; void as_xml (xmlpp::Node *) const; @@ -81,7 +81,7 @@ public: typedef int Frame; VideoContent (boost::shared_ptr<const Film>); - VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame); + VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime); VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path); VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int); VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); @@ -91,12 +91,12 @@ public: virtual std::string information () const; virtual std::string identifier () const; - VideoContent::Frame video_length () const { + ContentTime video_length () const { boost::mutex::scoped_lock lm (_mutex); return _video_length; } - libdcp::Size video_size () const { + dcp::Size video_size () const { boost::mutex::scoped_lock lm (_mutex); return _video_size; } @@ -157,15 +157,15 @@ public: return _colour_conversion; } - libdcp::Size video_size_after_3d_split () const; - libdcp::Size video_size_after_crop () const; + dcp::Size video_size_after_3d_split () const; + dcp::Size video_size_after_crop () const; - VideoContent::Frame time_to_content_video_frames (Time) const; + ContentTime dcp_time_to_content_time (DCPTime) const; protected: void take_from_video_examiner (boost::shared_ptr<VideoExaminer>); - VideoContent::Frame _video_length; + ContentTime _video_length; float _video_frame_rate; private: @@ -176,7 +176,7 @@ private: void setup_default_colour_conversion (); - libdcp::Size _video_size; + dcp::Size _video_size; VideoFrameType _video_frame_type; Crop _crop; VideoContentScale _scale; diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index e7ddec5e6..15f91b892 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -24,38 +24,37 @@ using std::cout; using boost::shared_ptr; +using boost::optional; -VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c) - : Decoder (f) - , _video_content (c) - , _video_position (0) +VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c) + : _video_content (c) { } +/** Called by subclasses when they have a video frame ready */ void -VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame) +VideoDecoder::video (shared_ptr<const Image> image, bool same, ContentTime time) { switch (_video_content->video_frame_type ()) { case VIDEO_FRAME_TYPE_2D: - Video (image, EYES_BOTH, same, frame); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image, EYES_BOTH, same))); break; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: { int const half = image->size().width / 2; - Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame); - Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same))); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same))); break; } case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: { int const half = image->size().height / 2; - Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame); - Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same))); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same))); break; } + default: + assert (false); } - - _video_position = frame + 1; } - diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 142320a04..c3228e88d 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -25,6 +25,7 @@ #include "decoder.h" #include "video_content.h" #include "util.h" +#include "decoded.h" class VideoContent; class Image; @@ -32,26 +33,16 @@ class Image; class VideoDecoder : public virtual Decoder { public: - VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>); - - /** Seek so that the next pass() will yield (approximately) the requested frame. - * Pass accurate = true to try harder to get close to the request. - */ - virtual void seek (VideoContent::Frame frame, bool accurate) = 0; - - /** Emitted when a video frame is ready. - * First parameter is the video image. - * Second parameter is the eye(s) which should see this image. - * Third parameter is true if the image is the same as the last one that was emitted for this Eyes value. - * Fourth parameter is the frame within our source. - */ - boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video; - + VideoDecoder (boost::shared_ptr<const VideoContent>); + + boost::shared_ptr<const VideoContent> video_content () const { + return _video_content; + } + protected: - void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame); + void video (boost::shared_ptr<const Image>, bool, ContentTime); boost::shared_ptr<const VideoContent> _video_content; - VideoContent::Frame _video_position; }; #endif diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h index 039c494b5..d4897213b 100644 --- a/src/lib/video_examiner.h +++ b/src/lib/video_examiner.h @@ -17,7 +17,7 @@ */ -#include <libdcp/types.h> +#include <dcp/types.h> #include "types.h" #include "video_content.h" @@ -26,6 +26,6 @@ class VideoExaminer public: virtual ~VideoExaminer () {} virtual float video_frame_rate () const = 0; - virtual libdcp::Size video_size () const = 0; - virtual VideoContent::Frame video_length () const = 0; + virtual dcp::Size video_size () const = 0; + virtual ContentTime video_length () const = 0; }; diff --git a/src/lib/writer.cc b/src/lib/writer.cc index 23f8bee97..125efd644 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -19,12 +19,16 @@ #include <fstream> #include <cerrno> -#include <libdcp/mono_picture_asset.h> -#include <libdcp/stereo_picture_asset.h> -#include <libdcp/sound_asset.h> -#include <libdcp/reel.h> -#include <libdcp/dcp.h> -#include <libdcp/cpl.h> +#include <dcp/mono_picture_mxf.h> +#include <dcp/stereo_picture_mxf.h> +#include <dcp/sound_mxf.h> +#include <dcp/sound_mxf_writer.h> +#include <dcp/reel.h> +#include <dcp/reel_mono_picture_asset.h> +#include <dcp/reel_stereo_picture_asset.h> +#include <dcp/reel_sound_asset.h> +#include <dcp/dcp.h> +#include <dcp/cpl.h> #include "writer.h" #include "compose.hpp" #include "film.h" @@ -37,6 +41,7 @@ #include "config.h" #include "job.h" #include "cross.h" +#include "audio_buffers.h" #include "i18n.h" @@ -51,6 +56,7 @@ using std::cout; using std::stringstream; using boost::shared_ptr; using boost::weak_ptr; +using boost::dynamic_pointer_cast; int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4; @@ -83,35 +89,33 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j) */ if (_film->three_d ()) { - _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ())); + _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1))); } else { - _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ())); + _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1))); } - _picture_asset->set_edit_rate (_film->video_frame_rate ()); - _picture_asset->set_size (_film->frame_size ()); - _picture_asset->set_interop (_film->interop ()); + _picture_mxf->set_size (_film->frame_size ()); if (_film->encrypted ()) { - _picture_asset->set_key (_film->key ()); + _picture_mxf->set_key (_film->key ()); } - _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0); + _picture_mxf_writer = _picture_mxf->start_write ( + _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(), + _film->interop() ? dcp::INTEROP : dcp::SMPTE, + _first_nonexistant_frame > 0 + ); - _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ())); - _sound_asset->set_edit_rate (_film->video_frame_rate ()); - _sound_asset->set_channels (_film->audio_channels ()); - _sound_asset->set_sampling_rate (_film->audio_frame_rate ()); - _sound_asset->set_interop (_film->interop ()); + _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ())); if (_film->encrypted ()) { - _sound_asset->set_key (_film->key ()); + _sound_mxf->set_key (_film->key ()); } - /* Write the sound asset into the film directory so that we leave the creation + /* Write the sound MXF into the film directory so that we leave the creation of the DCP directory until the last minute. */ - _sound_asset_writer = _sound_asset->start_write (); + _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE); _thread = new boost::thread (boost::bind (&Writer::thread, this)); @@ -164,7 +168,7 @@ Writer::fake_write (int frame, Eyes eyes) } FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r"); - libdcp::FrameInfo info (ifi); + dcp::FrameInfo info (ifi); fclose (ifi); QueueItem qi; @@ -188,7 +192,7 @@ Writer::fake_write (int frame, Eyes eyes) void Writer::write (shared_ptr<const AudioBuffers> audio) { - _sound_asset_writer->write (audio->data(), audio->frames()); + _sound_mxf_writer->write (audio->data(), audio->frames()); } /** This must be called from Writer::thread() with an appropriate lock held */ @@ -261,7 +265,7 @@ try qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false))); } - libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size()); + dcp::FrameInfo fin = _picture_mxf_writer->write (qi.encoded->data(), qi.encoded->size()); qi.encoded->write_info (_film, qi.frame, qi.eyes, fin); _last_written[qi.eyes] = qi.encoded; ++_full_written; @@ -269,14 +273,14 @@ try } case QueueItem::FAKE: _film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame)); - _picture_asset_writer->fake_write (qi.size); + _picture_mxf_writer->fake_write (qi.size); _last_written[qi.eyes].reset (); ++_fake_written; break; case QueueItem::REPEAT: { _film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame)); - libdcp::FrameInfo fin = _picture_asset_writer->write ( + dcp::FrameInfo fin = _picture_mxf_writer->write ( _last_written[qi.eyes]->data(), _last_written[qi.eyes]->size() ); @@ -291,16 +295,16 @@ try _last_written_frame = qi.frame; _last_written_eyes = qi.eyes; - if (_film->length()) { - shared_ptr<Job> job = _job.lock (); - assert (job); - int total = _film->time_to_video_frames (_film->length ()); - if (_film->three_d ()) { - /* _full_written and so on are incremented for each eye, so we need to double the total - frames to get the correct progress. - */ - total *= 2; - } + shared_ptr<Job> job = _job.lock (); + assert (job); + int64_t total = _film->length().frames (_film->video_frame_rate ()); + if (_film->three_d ()) { + /* _full_written and so on are incremented for each eye, so we need to double the total + frames to get the correct progress. + */ + total *= 2; + } + if (total) { job->set_progress (float (_full_written + _fake_written + _repeat_written) / total); } } @@ -376,13 +380,9 @@ Writer::finish () terminate_thread (true); - _picture_asset_writer->finalize (); - _sound_asset_writer->finalize (); + _picture_mxf_writer->finalize (); + _sound_mxf_writer->finalize (); - int const frames = _last_written_frame + 1; - - _picture_asset->set_duration (frames); - /* Hard-link the video MXF into the DCP */ boost::filesystem::path video_from; video_from /= _film->internal_video_mxf_dir(); @@ -400,11 +400,6 @@ Writer::finish () _film->log()->log ("Hard-link failed; fell back to copying"); } - /* And update the asset */ - - _picture_asset->set_directory (_film->dir (_film->dcp_name ())); - _picture_asset->set_file_name (_film->video_mxf_filename ()); - /* Move the audio MXF into the DCP */ boost::filesystem::path audio_to; @@ -418,42 +413,45 @@ Writer::finish () ); } - _sound_asset->set_directory (_film->dir (_film->dcp_name ())); - _sound_asset->set_duration (frames); - - libdcp::DCP dcp (_film->dir (_film->dcp_name())); + dcp::DCP dcp (_film->dir (_film->dcp_name())); - shared_ptr<libdcp::CPL> cpl ( - new libdcp::CPL ( - _film->dir (_film->dcp_name()), + shared_ptr<dcp::CPL> cpl ( + new dcp::CPL ( _film->dcp_name(), - _film->dcp_content_type()->libdcp_kind (), - frames, - _film->video_frame_rate () + _film->dcp_content_type()->libdcp_kind () ) ); - dcp.add_cpl (cpl); + dcp.add (cpl); + + shared_ptr<dcp::Reel> reel (new dcp::Reel ()); + + shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf); + if (mono) { + reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0))); + } - cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel ( - _picture_asset, - _sound_asset, - shared_ptr<libdcp::SubtitleAsset> () - ) - )); + shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf); + if (stereo) { + reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0))); + } + + reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0))); + + cpl->add (reel); shared_ptr<Job> job = _job.lock (); assert (job); job->sub (_("Computing image digest")); - _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false)); + _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false)); job->sub (_("Computing audio digest")); - _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false)); + _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false)); - libdcp::XMLMetadata meta = Config::instance()->dcp_metadata (); + dcp::XMLMetadata meta = Config::instance()->dcp_metadata (); meta.set_issue_date_now (); - dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ()); + dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ()); _film->log()->log ( String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk) @@ -496,7 +494,7 @@ Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes) return false; } - libdcp::FrameInfo info (ifi); + dcp::FrameInfo info (ifi); fclose (ifi); if (info.size == 0) { _film->log()->log (String::compose ("Existing frame %1 has no info file", f)); diff --git a/src/lib/writer.h b/src/lib/writer.h index 7af79a417..9e95c0531 100644 --- a/src/lib/writer.h +++ b/src/lib/writer.h @@ -17,6 +17,10 @@ */ +/** @file src/lib/writer.h + * @brief Writer class. + */ + #include <list> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> @@ -29,15 +33,15 @@ class EncodedData; class AudioBuffers; class Job; -namespace libdcp { - class MonoPictureAsset; - class MonoPictureAssetWriter; - class StereoPictureAsset; - class StereoPictureAssetWriter; - class PictureAsset; - class PictureAssetWriter; - class SoundAsset; - class SoundAssetWriter; +namespace dcp { + class MonoPictureMXF; + class MonoPictureMXFWriter; + class StereoPictureMXF; + class StereoPictureMXFWriter; + class PictureMXF; + class PictureMXFWriter; + class SoundMXF; + class SoundMXFWriter; } struct QueueItem @@ -67,6 +71,17 @@ public: bool operator< (QueueItem const & a, QueueItem const & b); bool operator== (QueueItem const & a, QueueItem const & b); +/** @class Writer + * @brief Class to manage writing JPEG2000 and audio data to MXFs on disk. + * + * This class creates sound and picture MXFs, then takes EncodedData + * or AudioBuffers objects (containing image or sound data respectively) + * and writes them to the MXFs. + * + * ::write() for EncodedData can be called out of order, and the Writer + * will sort it out. write() for AudioBuffers must be called in order. + */ + class Writer : public ExceptionStore, public boost::noncopyable { public: @@ -130,8 +145,8 @@ private: */ int _pushed_to_disk; - boost::shared_ptr<libdcp::PictureAsset> _picture_asset; - boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer; - boost::shared_ptr<libdcp::SoundAsset> _sound_asset; - boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer; + boost::shared_ptr<dcp::PictureMXF> _picture_mxf; + boost::shared_ptr<dcp::PictureMXFWriter> _picture_mxf_writer; + boost::shared_ptr<dcp::SoundMXF> _sound_mxf; + boost::shared_ptr<dcp::SoundMXFWriter> _sound_mxf_writer; }; diff --git a/src/lib/wscript b/src/lib/wscript index 7c9712ff8..5d27cfe45 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -8,6 +8,7 @@ sources = """ audio_content.cc audio_decoder.cc audio_mapping.cc + audio_merger.cc cinema.cc colour_conversion.cc config.cc @@ -17,6 +18,7 @@ sources = """ dci_metadata.cc dcp_content_type.cc dcp_video_frame.cc + dcpomatic_time.cc decoder.cc dolby_cp750.cc encoder.cc @@ -30,6 +32,7 @@ sources = """ ffmpeg_examiner.cc film.cc filter.cc + frame_rate_change.cc image.cc image_content.cc image_decoder.cc @@ -42,6 +45,7 @@ sources = """ player.cc playlist.cc ratio.cc + render_subtitles.cc resampler.cc scp_dcp_job.cc scaler.cc @@ -51,6 +55,9 @@ sources = """ sndfile_content.cc sndfile_decoder.cc sound_processor.cc + subrip.cc + subrip_content.cc + subrip_decoder.cc subtitle_content.cc subtitle_decoder.cc timer.cc @@ -77,7 +84,7 @@ def build(bld): AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++ - CURL ZIP QUICKMAIL + CURL ZIP QUICKMAIL PANGOMM CAIROMM """ if bld.env.TARGET_OSX: diff --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc index a74ece149..d95b2ed99 100644 --- a/src/tools/dcpomatic_cli.cc +++ b/src/tools/dcpomatic_cli.cc @@ -20,7 +20,7 @@ #include <iostream> #include <iomanip> #include <getopt.h> -#include <libdcp/version.h> +#include <dcp/version.h> #include "lib/film.h" #include "lib/filter.h" #include "lib/transcode_job.h" diff --git a/src/tools/dcpomatic_create.cc b/src/tools/dcpomatic_create.cc index a49780276..c75f8f953 100644 --- a/src/tools/dcpomatic_create.cc +++ b/src/tools/dcpomatic_create.cc @@ -190,7 +190,7 @@ main (int argc, char* argv[]) for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i); if (ic) { - ic->set_video_length (still_length * 24); + ic->set_video_length (ContentTime::from_seconds (still_length)); } } diff --git a/src/tools/dcpomatic_kdm.cc b/src/tools/dcpomatic_kdm.cc index 3a2068d2b..820dc6ae6 100644 --- a/src/tools/dcpomatic_kdm.cc +++ b/src/tools/dcpomatic_kdm.cc @@ -18,7 +18,7 @@ */ #include <getopt.h> -#include <libdcp/certificates.h> +#include <dcp/certificates.h> #include "lib/film.h" #include "lib/cinema.h" #include "lib/kdm.h" @@ -234,8 +234,8 @@ int main (int argc, char* argv[]) error ("you must specify --output"); } - shared_ptr<libdcp::Certificate> certificate (new libdcp::Certificate (boost::filesystem::path (certificate_file))); - libdcp::KDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get()); + shared_ptr<dcp::Certificate> certificate (new dcp::Certificate (boost::filesystem::path (certificate_file))); + dcp::KDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get()); kdm.as_xml (output); if (verbose) { cout << "Generated KDM " << output << " for certificate.\n"; diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index 039088862..9b725cb86 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -47,10 +47,15 @@ static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log")); static int frame = 0; void -process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, Time) +process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, DCPTime) { - shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)); - shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)); + shared_ptr<DCPVideoFrame> local ( + new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_) + ); + + shared_ptr<DCPVideoFrame> remote ( + new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_) + ); cout << "Frame " << frame << ": "; cout.flush (); diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc index 7fdecb8d5..52303ac14 100644 --- a/src/wx/audio_mapping_view.cc +++ b/src/wx/audio_mapping_view.cc @@ -20,7 +20,7 @@ #include <wx/wx.h> #include <wx/renderer.h> #include <wx/grid.h> -#include <libdcp/types.h> +#include <dcp/types.h> #include "lib/audio_mapping.h" #include "lib/util.h" #include "audio_mapping_view.h" @@ -156,7 +156,7 @@ AudioMappingView::left_click (wxGridEvent& ev) return; } - libdcp::Channel d = static_cast<libdcp::Channel> (ev.GetCol() - 1); + dcp::Channel d = static_cast<dcp::Channel> (ev.GetCol() - 1); if (_map.get (ev.GetRow(), d) > 0) { _map.set (ev.GetRow(), d, 0); @@ -182,28 +182,28 @@ AudioMappingView::right_click (wxGridEvent& ev) void AudioMappingView::off () { - _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 0); + _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 0); map_changed (); } void AudioMappingView::full () { - _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1); + _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1); map_changed (); } void AudioMappingView::minus3dB () { - _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1 / sqrt (2)); + _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1 / sqrt (2)); map_changed (); } void AudioMappingView::edit () { - libdcp::Channel d = static_cast<libdcp::Channel> (_menu_column - 1); + dcp::Channel d = static_cast<dcp::Channel> (_menu_column - 1); AudioGainDialog* dialog = new AudioGainDialog (this, _menu_row, _menu_column - 1, _map.get (_menu_row, d)); if (dialog->ShowModal () == wxID_OK) { @@ -242,7 +242,7 @@ AudioMappingView::update_cells () _grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1)); for (int j = 1; j < _grid->GetNumberCols(); ++j) { - _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<libdcp::Channel> (j - 1))))); + _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1))))); } } @@ -346,7 +346,7 @@ AudioMappingView::mouse_moved (wxMouseEvent& ev) if (row != _last_tooltip_row || column != _last_tooltip_column) { wxString s; - float const gain = _map.get (row, static_cast<libdcp::Channel> (column - 1)); + float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1)); if (gain == 0) { s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column); } else if (gain == 1) { diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index 8938c84f9..44745006c 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -28,7 +28,7 @@ #include <wx/preferences.h> #include <wx/filepicker.h> #include <wx/spinctrl.h> -#include <libdcp/colour_matrix.h> +#include <dcp/colour_matrix.h> #include "lib/config.h" #include "lib/ratio.h" #include "lib/scaler.h" diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 061305436..78a5c440c 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -841,7 +841,7 @@ FilmEditor::setup_content_sensitivity () _video_panel->Enable (video_selection.size() > 0 && _generally_sensitive); _audio_panel->Enable (audio_selection.size() > 0 && _generally_sensitive); - _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive); + _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive); _timing_panel->Enable (selection.size() == 1 && _generally_sensitive); } diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 45372d811..7e1b61811 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -36,6 +36,7 @@ #include "lib/player.h" #include "lib/video_content.h" #include "lib/video_decoder.h" +#include "lib/timer.h" #include "film_viewer.h" #include "wx_util.h" @@ -50,7 +51,7 @@ using std::make_pair; using boost::shared_ptr; using boost::dynamic_pointer_cast; using boost::weak_ptr; -using libdcp::Size; +using dcp::Size; FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p) : wxPanel (p) @@ -121,7 +122,7 @@ FilmViewer::set_film (shared_ptr<Film> f) _frame.reset (); _slider->SetValue (0); - set_position_text (0); + set_position_text (DCPTime ()); if (!_film) { return; @@ -136,6 +137,7 @@ FilmViewer::set_film (shared_ptr<Film> f) } _player->disable_audio (); + _player->set_approximate_size (); _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5)); _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1)); @@ -172,10 +174,10 @@ FilmViewer::timer () fetch_next_frame (); - Time const len = _film->length (); + DCPTime const len = _film->length (); - if (len) { - int const new_slider_position = 4096 * _player->video_position() / len; + if (len.get ()) { + int const new_slider_position = 4096 * _player->video_position().get() / len.get(); if (new_slider_position != _slider->GetValue()) { _slider->SetValue (new_slider_position); } @@ -219,8 +221,14 @@ void FilmViewer::slider_moved () { if (_film && _player) { - _player->seek (_slider->GetValue() * _film->length() / 4096, false); - fetch_next_frame (); + try { + _player->seek (DCPTime (_film->length().get() * _slider->GetValue() / 4096), false); + fetch_next_frame (); + } catch (OpenFileError& e) { + /* There was a problem opening a content file; we'll let this slide as it + probably means a missing content file, which we're already taking care of. + */ + } } } @@ -259,6 +267,13 @@ FilmViewer::calculate_sizes () _out_size.width = max (64, _out_size.width); _out_size.height = max (64, _out_size.height); + /* The player will round its image down to the nearest 4 pixels + to speed up its scale, so do similar here to avoid black borders + around things. This is a bit of a hack. + */ + _out_size.width &= ~3; + _out_size.height &= ~3; + _player->set_video_container_size (_out_size); } @@ -283,20 +298,24 @@ FilmViewer::check_play_state () } void -FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t) +FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, DCPTime t) { if (eyes == EYES_RIGHT) { return; } - - _frame = image->image (); + + /* Going via BGRA here makes the scaler faster then using RGB24 directly (about + twice on x86 Linux). + */ + shared_ptr<Image> im = image->image (PIX_FMT_BGRA, true); + _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false); _got_frame = true; set_position_text (t); } void -FilmViewer::set_position_text (Time t) +FilmViewer::set_position_text (DCPTime t) { if (!_film) { _frame_number->SetLabel ("0"); @@ -306,9 +325,9 @@ FilmViewer::set_position_text (Time t) double const fps = _film->video_frame_rate (); /* Count frame number from 1 ... not sure if this is the best idea */ - _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1)); + _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t.seconds() * fps)) + 1)); - double w = static_cast<double>(t) / TIME_HZ; + double w = t.seconds (); int const h = (w / 3600); w -= h * 3600; int const m = (w / 60); @@ -379,13 +398,19 @@ FilmViewer::back_clicked () We want to see the one before it, so we need to go back 2. */ - Time p = _player->video_position() - _film->video_frames_to_time (2); - if (p < 0) { - p = 0; + DCPTime p = _player->video_position() - DCPTime::from_frames (2, _film->video_frame_rate ()); + if (p < DCPTime ()) { + p = DCPTime (); } - _player->seek (p, true); - fetch_next_frame (); + try { + _player->seek (p, true); + fetch_next_frame (); + } catch (OpenFileError& e) { + /* There was a problem opening a content file; we'll let this slide as it + probably means a missing content file, which we're already taking care of. + */ + } } void diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index c99c73440..0a535df9f 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -59,7 +59,7 @@ private: void slider_moved (); void play_clicked (); void timer (); - void process_video (boost::shared_ptr<PlayerImage>, Eyes, Time); + void process_video (boost::shared_ptr<PlayerImage>, Eyes, DCPTime); void calculate_sizes (); void check_play_state (); void fetch_current_frame_again (); @@ -68,7 +68,7 @@ private: void back_clicked (); void forward_clicked (); void player_changed (bool); - void set_position_text (Time); + void set_position_text (DCPTime); boost::shared_ptr<Film> _film; boost::shared_ptr<Player> _player; @@ -87,7 +87,7 @@ private: bool _got_frame; /** Size of our output (including padding if we have any) */ - libdcp::Size _out_size; + dcp::Size _out_size; /** Size of the panel that we have available */ - libdcp::Size _panel_size; + dcp::Size _panel_size; }; diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index 11510cd0f..8c976f53a 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -51,7 +51,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) _encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this)); _table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL); - _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length())))); + _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().frames (_film->video_frame_rate ())))); double const disk = double (_film->required_disk_space()) / 1073741824.0f; stringstream s; s << fixed << setprecision (1) << disk << wx_to_std (_("Gb")); @@ -85,10 +85,11 @@ PropertiesDialog::frames_already_encoded () const } catch (boost::thread_interrupted &) { return ""; } - - if (_film->length()) { + + uint64_t const frames = _film->length().frames (_film->video_frame_rate ()); + if (frames) { /* XXX: encoded_frames() should check which frames have been encoded */ - u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)"; + u << " (" << (_film->encoded_frames() * 100 / frames) << "%)"; } return u.str (); } diff --git a/src/wx/screen_dialog.cc b/src/wx/screen_dialog.cc index 32a0bce43..f94400be7 100644 --- a/src/wx/screen_dialog.cc +++ b/src/wx/screen_dialog.cc @@ -19,7 +19,7 @@ #include <wx/filepicker.h> #include <wx/validate.h> -#include <libdcp/exceptions.h> +#include <dcp/exceptions.h> #include "lib/compose.hpp" #include "screen_dialog.h" #include "wx_util.h" @@ -28,7 +28,7 @@ using std::string; using std::cout; using boost::shared_ptr; -ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<libdcp::Certificate> certificate) +ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<dcp::Certificate> certificate) : wxDialog (parent, wxID_ANY, std_to_wx (title)) , _certificate (certificate) { @@ -76,7 +76,7 @@ ScreenDialog::name () const return wx_to_std (_name->GetValue()); } -shared_ptr<libdcp::Certificate> +shared_ptr<dcp::Certificate> ScreenDialog::certificate () const { return _certificate; @@ -89,9 +89,9 @@ ScreenDialog::load_certificate () if (d->ShowModal () == wxID_OK) { try { - _certificate.reset (new libdcp::Certificate (boost::filesystem::path (wx_to_std (d->GetPath ())))); + _certificate.reset (new dcp::Certificate (boost::filesystem::path (wx_to_std (d->GetPath ())))); _certificate_text->SetValue (_certificate->certificate ()); - } catch (libdcp::MiscError& e) { + } catch (dcp::MiscError& e) { error_dialog (this, String::compose ("Could not read certificate file (%1)", e.what())); } } diff --git a/src/wx/screen_dialog.h b/src/wx/screen_dialog.h index 271ae2055..0cd7d3c49 100644 --- a/src/wx/screen_dialog.h +++ b/src/wx/screen_dialog.h @@ -19,15 +19,15 @@ #include <wx/wx.h> #include <boost/shared_ptr.hpp> -#include <libdcp/certificates.h> +#include <dcp/certificates.h> class ScreenDialog : public wxDialog { public: - ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<libdcp::Certificate> c = boost::shared_ptr<libdcp::Certificate> ()); + ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<dcp::Certificate> c = boost::shared_ptr<dcp::Certificate> ()); std::string name () const; - boost::shared_ptr<libdcp::Certificate> certificate () const; + boost::shared_ptr<dcp::Certificate> certificate () const; private: void load_certificate (); @@ -37,5 +37,5 @@ private: wxButton* _certificate_load; wxTextCtrl* _certificate_text; - boost::shared_ptr<libdcp::Certificate> _certificate; + boost::shared_ptr<dcp::Certificate> _certificate; }; diff --git a/src/wx/subtitle_panel.cc b/src/wx/subtitle_panel.cc index 63a18b0ce..b4b5a7b42 100644 --- a/src/wx/subtitle_panel.cc +++ b/src/wx/subtitle_panel.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,9 +20,11 @@ #include <boost/lexical_cast.hpp> #include <wx/spinctrl.h> #include "lib/ffmpeg_content.h" +#include "lib/subrip_content.h" #include "subtitle_panel.h" #include "film_editor.h" #include "wx_util.h" +#include "subtitle_view.h" using std::vector; using std::string; @@ -32,6 +34,7 @@ using boost::dynamic_pointer_cast; SubtitlePanel::SubtitlePanel (FilmEditor* e) : FilmEditorPanel (e, _("Subtitles")) + , _view (0) { wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); _sizer->Add (grid, 0, wxALL, 8); @@ -70,6 +73,9 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e) add_label_to_sizer (grid, this, _("Subtitle Stream"), true); _stream = new wxChoice (this, wxID_ANY); grid->Add (_stream, 1, wxEXPAND); + + _view_button = new wxButton (this, wxID_ANY, _("View...")); + grid->Add (_view_button); _x_offset->SetRange (-100, 100); _y_offset->SetRange (-100, 100); @@ -81,6 +87,7 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e) _y_offset->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_offset_changed, this)); _scale->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::scale_changed, this)); _stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&SubtitlePanel::stream_changed, this)); + _view_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&SubtitlePanel::view_clicked, this)); } void @@ -164,6 +171,9 @@ SubtitlePanel::setup_sensitivity () _y_offset->Enable (j); _scale->Enable (j); _stream->Enable (j); + + SubtitleContentList c = _editor->selected_subtitle_content (); + _view_button->Enable (c.size() == 1); } void @@ -223,3 +233,21 @@ SubtitlePanel::content_selection_changed () film_content_changed (SubtitleContentProperty::SUBTITLE_Y_OFFSET); film_content_changed (SubtitleContentProperty::SUBTITLE_SCALE); } + +void +SubtitlePanel::view_clicked () +{ + if (_view) { + _view->Destroy (); + _view = 0; + } + + SubtitleContentList c = _editor->selected_subtitle_content (); + assert (c.size() == 1); + shared_ptr<SubRipContent> sr = dynamic_pointer_cast<SubRipContent> (c.front ()); + if (sr) { + _view = new SubtitleView (this, sr); + } + + _view->Show (); +} diff --git a/src/wx/subtitle_panel.h b/src/wx/subtitle_panel.h index 20d7c40c2..1ee775025 100644 --- a/src/wx/subtitle_panel.h +++ b/src/wx/subtitle_panel.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ class wxCheckBox; class wxSpinCtrl; +class SubtitleView; class SubtitlePanel : public FilmEditorPanel { @@ -37,6 +38,7 @@ private: void y_offset_changed (); void scale_changed (); void stream_changed (); + void view_clicked (); void setup_sensitivity (); @@ -45,4 +47,6 @@ private: wxSpinCtrl* _y_offset; wxSpinCtrl* _scale; wxChoice* _stream; + wxButton* _view_button; + SubtitleView* _view; }; diff --git a/src/wx/subtitle_view.cc b/src/wx/subtitle_view.cc new file mode 100644 index 000000000..f6fbd9a9a --- /dev/null +++ b/src/wx/subtitle_view.cc @@ -0,0 +1,89 @@ +/* + Copyright (C) 2014 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 "lib/subrip_decoder.h" +#include "lib/decoded.h" +#include "subtitle_view.h" + +using std::list; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +SubtitleView::SubtitleView (wxWindow* parent, shared_ptr<SubRipContent> content) + : wxDialog (parent, wxID_ANY, _("Subtitles")) +{ + _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL); + + { + wxListItem ip; + ip.SetId (0); + ip.SetText (_("Start")); + ip.SetWidth (100); + _list->InsertColumn (0, ip); + } + + { + wxListItem ip; + ip.SetId (1); + ip.SetText (_("End")); + ip.SetWidth (100); + _list->InsertColumn (1, ip); + } + + { + wxListItem ip; + ip.SetId (2); + ip.SetText (_("Subtitle")); + ip.SetWidth (640); + _list->InsertColumn (2, ip); + } + + wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); + sizer->Add (_list, 1, wxEXPAND); + + wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); + if (buttons) { + sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content)); + int n = 0; + while (1) { + shared_ptr<Decoded> dec = decoder->peek (); + if (!dec) { + break; + } + + shared_ptr<DecodedTextSubtitle> sub = dynamic_pointer_cast<DecodedTextSubtitle> (dec); + assert (sub); + + for (list<dcp::SubtitleString>::const_iterator i = sub->subs.begin(); i != sub->subs.end(); ++i) { + wxListItem list_item; + list_item.SetId (n); + _list->InsertItem (list_item); + _list->SetItem (n, 2, i->text ()); + ++n; + } + + decoder->consume (); + } + + SetSizerAndFit (sizer); +} + diff --git a/src/wx/subtitle_view.h b/src/wx/subtitle_view.h new file mode 100644 index 000000000..94a25b70c --- /dev/null +++ b/src/wx/subtitle_view.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2014 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 <wx/wx.h> +#include <wx/listctrl.h> + +class SubRipContent; + +class SubtitleView : public wxDialog +{ +public: + SubtitleView (wxWindow *, boost::shared_ptr<SubRipContent>); + +private: + wxListCtrl* _list; +}; diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc index ac4fd46c4..045327254 100644 --- a/src/wx/timecode.cc +++ b/src/wx/timecode.cc @@ -83,15 +83,15 @@ Timecode::Timecode (wxWindow* parent) } void -Timecode::set (Time t, int fps) +Timecode::set (DCPTime t, int fps) { - int const h = t / (3600 * TIME_HZ); - t -= h * 3600 * TIME_HZ; - int const m = t / (60 * TIME_HZ); - t -= m * 60 * TIME_HZ; - int const s = t / TIME_HZ; - t -= s * TIME_HZ; - int const f = divide_with_round (t * fps, TIME_HZ); + int const h = t.seconds() / 3600; + t -= DCPTime::from_seconds (h * 3600); + int const m = t.seconds() / 60; + t -= DCPTime::from_seconds (m * 60); + int const s = t.seconds(); + t -= DCPTime::from_seconds (s); + int const f = rint (t.seconds() * fps); checked_set (_hours, lexical_cast<string> (h)); checked_set (_minutes, lexical_cast<string> (m)); @@ -101,18 +101,18 @@ Timecode::set (Time t, int fps) _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02d", h, m, s, f)); } -Time +DCPTime Timecode::get (int fps) const { - Time t = 0; + DCPTime t; string const h = wx_to_std (_hours->GetValue ()); - t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ; + t += DCPTime::from_seconds (lexical_cast<int> (h.empty() ? "0" : h) * 3600); string const m = wx_to_std (_minutes->GetValue()); - t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ; + t += DCPTime::from_seconds (lexical_cast<int> (m.empty() ? "0" : m) * 60); string const s = wx_to_std (_seconds->GetValue()); - t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ; + t += DCPTime::from_seconds (lexical_cast<int> (s.empty() ? "0" : s)); string const f = wx_to_std (_frames->GetValue()); - t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps; + t += DCPTime::from_seconds (lexical_cast<double> (f.empty() ? "0" : f) / fps); return t; } diff --git a/src/wx/timecode.h b/src/wx/timecode.h index 880b44a31..b13e8c3c0 100644 --- a/src/wx/timecode.h +++ b/src/wx/timecode.h @@ -26,8 +26,8 @@ class Timecode : public wxPanel public: Timecode (wxWindow *); - void set (Time, int); - Time get (int) const; + void set (DCPTime, int); + DCPTime get (int) const; void set_editable (bool); diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index 4e306c499..ac03c75da 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -64,9 +64,9 @@ public: protected: virtual void do_paint (wxGraphicsContext *) = 0; - int time_x (Time t) const + int time_x (DCPTime t) const { - return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit(); + return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second (); } Timeline& _timeline; @@ -100,7 +100,7 @@ public: return dcpomatic::Rect<int> ( time_x (content->position ()) - 8, y_pos (_track) - 8, - content->length_after_trim () * _timeline.pixels_per_time_unit() + 16, + content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16, _timeline.track_height() + 16 ); } @@ -139,8 +139,8 @@ private: return; } - Time const position = cont->position (); - Time const len = cont->length_after_trim (); + DCPTime const position = cont->position (); + DCPTime const len = cont->length_after_trim (); wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2); @@ -169,7 +169,7 @@ private: wxDouble name_leading; gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading); - gc->Clip (wxRegion (time_x (position), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height())); + gc->Clip (wxRegion (time_x (position), y_pos (_track), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height())); gc->DrawText (name, time_x (position) + 12, y_pos (_track + 1) - name_height - 4); gc->ResetClip (); } @@ -188,7 +188,7 @@ private: } if (!frequent) { - _timeline.setup_pixels_per_time_unit (); + _timeline.setup_pixels_per_second (); _timeline.Refresh (); } } @@ -268,18 +268,18 @@ private: { gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID)); - int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ())); + double mark_interval = rint (128 / _timeline.pixels_per_second ()); if (mark_interval > 5) { - mark_interval -= mark_interval % 5; + mark_interval -= int (rint (mark_interval)) % 5; } if (mark_interval > 10) { - mark_interval -= mark_interval % 10; + mark_interval -= int (rint (mark_interval)) % 10; } if (mark_interval > 60) { - mark_interval -= mark_interval % 60; + mark_interval -= int (rint (mark_interval)) % 60; } if (mark_interval > 3600) { - mark_interval -= mark_interval % 3600; + mark_interval -= int (rint (mark_interval)) % 3600; } if (mark_interval < 1) { @@ -291,14 +291,15 @@ private: path.AddLineToPoint (_timeline.width(), _y); gc->StrokePath (path); - Time t = 0; - while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) { + /* Time in seconds */ + DCPTime t; + while ((t.seconds() * _timeline.pixels_per_second()) < _timeline.width()) { wxGraphicsPath path = gc->CreatePath (); path.MoveToPoint (time_x (t), _y - 4); path.AddLineToPoint (time_x (t), _y + 4); gc->StrokePath (path); - int tc = t / TIME_HZ; + double tc = t.seconds (); int const h = tc / 3600; tc -= h * 3600; int const m = tc / 60; @@ -312,12 +313,12 @@ private: wxDouble str_leading; gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading); - int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit(); + int const tx = _timeline.x_offset() + t.seconds() * _timeline.pixels_per_second(); if ((tx + str_width) < _timeline.width()) { gc->DrawText (str, time_x (t), _y + 16); } - t += mark_interval * TIME_HZ; + t += DCPTime::from_seconds (mark_interval); } } @@ -332,7 +333,7 @@ Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film) , _film (film) , _time_axis_view (new TimeAxisView (*this, 32)) , _tracks (0) - , _pixels_per_time_unit (0) + , _pixels_per_second (0) , _left_down (false) , _down_view_position (0) , _first_move (false) @@ -401,7 +402,7 @@ Timeline::playlist_changed () } assign_tracks (); - setup_pixels_per_time_unit (); + setup_pixels_per_second (); Refresh (); } @@ -471,14 +472,14 @@ Timeline::tracks () const } void -Timeline::setup_pixels_per_time_unit () +Timeline::setup_pixels_per_second () { shared_ptr<const Film> film = _film.lock (); - if (!film || film->length() == 0) { + if (!film || film->length() == DCPTime ()) { return; } - _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length (); + _pixels_per_second = static_cast<double>(width() - x_offset() * 2) / film->length().seconds (); } shared_ptr<View> @@ -597,13 +598,13 @@ Timeline::set_position_from_event (wxMouseEvent& ev) return; } - Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit; + DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / _pixels_per_second); if (_snap) { bool first = true; - Time nearest_distance = TIME_MAX; - Time nearest_new_position = TIME_MAX; + DCPTime nearest_distance = DCPTime::max (); + DCPTime nearest_new_position = DCPTime::max (); /* Find the nearest content edge; this is inefficient */ for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { @@ -614,7 +615,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev) { /* Snap starts to ends */ - Time const d = abs (cv->content()->end() - new_position); + DCPTime const d = DCPTime (cv->content()->end() - new_position).abs (); if (first || d < nearest_distance) { nearest_distance = d; nearest_new_position = cv->content()->end(); @@ -623,7 +624,10 @@ Timeline::set_position_from_event (wxMouseEvent& ev) { /* Snap ends to starts */ - Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim())); + DCPTime const d = DCPTime ( + cv->content()->position() - (new_position + _down_view->content()->length_after_trim()) + ).abs (); + if (d < nearest_distance) { nearest_distance = d; nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim (); @@ -635,14 +639,14 @@ Timeline::set_position_from_event (wxMouseEvent& ev) if (!first) { /* Snap if it's close; `close' means within a proportion of the time on the timeline */ - if (nearest_distance < (width() / pixels_per_time_unit()) / 32) { + if (nearest_distance < DCPTime::from_seconds ((width() / pixels_per_second()) / 32)) { new_position = nearest_new_position; } } } - if (new_position < 0) { - new_position = 0; + if (new_position < DCPTime ()) { + new_position = DCPTime (); } _down_view->content()->set_position (new_position); @@ -667,7 +671,7 @@ Timeline::film () const void Timeline::resized () { - setup_pixels_per_time_unit (); + setup_pixels_per_second (); } void diff --git a/src/wx/timeline.h b/src/wx/timeline.h index ef1d10797..35153dd17 100644 --- a/src/wx/timeline.h +++ b/src/wx/timeline.h @@ -52,8 +52,8 @@ public: return 48; } - double pixels_per_time_unit () const { - return _pixels_per_time_unit; + double pixels_per_second () const { + return _pixels_per_second; } Position<int> tracks_position () const { @@ -62,7 +62,7 @@ public: int tracks () const; - void setup_pixels_per_time_unit (); + void setup_pixels_per_second (); void set_snap (bool s) { _snap = s; @@ -96,11 +96,11 @@ private: ViewList _views; boost::shared_ptr<TimeAxisView> _time_axis_view; int _tracks; - double _pixels_per_time_unit; + double _pixels_per_second; bool _left_down; wxPoint _down_point; boost::shared_ptr<ContentView> _down_view; - Time _down_view_position; + DCPTime _down_view_position; bool _first_move; ContentMenu _menu; bool _snap; diff --git a/src/wx/timing_panel.cc b/src/wx/timing_panel.cc index 34702463c..b8b923a6b 100644 --- a/src/wx/timing_panel.cc +++ b/src/wx/timing_panel.cc @@ -85,31 +85,31 @@ TimingPanel::film_content_changed (int property) if (content) { _position->set (content->position (), _editor->film()->video_frame_rate ()); } else { - _position->set (0, 24); + _position->set (DCPTime () , 24); } } else if (property == ContentProperty::LENGTH || property == VideoContentProperty::VIDEO_FRAME_RATE) { if (content) { _full_length->set (content->full_length (), _editor->film()->video_frame_rate ()); _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ()); } else { - _full_length->set (0, 24); - _play_length->set (0, 24); + _full_length->set (DCPTime (), 24); + _play_length->set (DCPTime (), 24); } } else if (property == ContentProperty::TRIM_START) { if (content) { _trim_start->set (content->trim_start (), _editor->film()->video_frame_rate ()); _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ()); } else { - _trim_start->set (0, 24); - _play_length->set (0, 24); + _trim_start->set (DCPTime (), 24); + _play_length->set (DCPTime (), 24); } } else if (property == ContentProperty::TRIM_END) { if (content) { _trim_end->set (content->trim_end (), _editor->film()->video_frame_rate ()); _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ()); } else { - _trim_end->set (0, 24); - _play_length->set (0, 24); + _trim_end->set (DCPTime (), 24); + _play_length->set (DCPTime (), 24); } } @@ -149,7 +149,8 @@ TimingPanel::full_length_changed () if (c.size() == 1) { shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ()); if (ic && ic->still ()) { - ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ)); + /* XXX: No effective FRC here... is this right? */ + ic->set_video_length (ContentTime (_full_length->get (_editor->film()->video_frame_rate()), FrameRateChange (1, 1))); } } } diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc index 38604248c..533545f64 100644 --- a/src/wx/video_panel.cc +++ b/src/wx/video_panel.cc @@ -294,8 +294,8 @@ VideoPanel::setup_description () } Crop const crop = vcs->crop (); - if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) { - libdcp::Size cropped = vcs->video_size_after_crop (); + if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) { + dcp::Size cropped = vcs->video_size_after_crop (); d << wxString::Format ( _("Cropped to %dx%d (%.2f:1)\n"), cropped.width, cropped.height, @@ -304,8 +304,8 @@ VideoPanel::setup_description () ++lines; } - libdcp::Size const container_size = _editor->film()->frame_size (); - libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size); + dcp::Size const container_size = _editor->film()->frame_size (); + dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size); if (scaled != vcs->video_size_after_crop ()) { d << wxString::Format ( @@ -327,7 +327,7 @@ VideoPanel::setup_description () d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ()); ++lines; - FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); + FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); d << std_to_wx (frc.description) << "\n"; ++lines; diff --git a/src/wx/wscript b/src/wx/wscript index 1ffaa6097..45a5bc96d 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -35,6 +35,7 @@ sources = """ server_dialog.cc servers_list_dialog.cc subtitle_panel.cc + subtitle_view.cc timecode.cc timeline.cc timeline_dialog.cc |
