diff options
Diffstat (limited to 'src/lib')
169 files changed, 7648 insertions, 3259 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index ab985bdf7..60b10e7b6 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" @@ -59,19 +60,18 @@ AnalyseAudioJob::run () shared_ptr<Playlist> playlist (new Playlist); playlist->add (content); shared_ptr<Player> player (new Player (_film, playlist)); - player->disable_video (); - 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); + DCPTime const block = DCPTime::from_seconds (1.0 / 8); + for (DCPTime t; t < _film->length(); t += block) { + analyse (player->get_audio (t, block, false)); + set_progress (t.seconds() / _film->length().seconds()); } _analysis->write (content->audio_analysis_path ()); @@ -81,7 +81,7 @@ AnalyseAudioJob::run () } void -AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time) +AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b) { 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 3d4881983..a218cb340 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -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 @@ -17,13 +17,25 @@ */ +/** @file src/lib/analyse_audio_job.h + * @brief AnalyseAudioJob class. + */ + #include "job.h" #include "audio_analysis.h" #include "types.h" +#include "dcpomatic_time.h" class AudioBuffers; class AudioContent; +/** @class AnalyseAudioJob + * @brief A job to analyse the audio of a piece of AudioContent and make a note of its + * broad peak and RMS levels. + * + * After computing the peak and RMS levels over the length of the content, the job + * will write a file to Content::audio_analysis_path. + */ class AnalyseAudioJob : public Job { public: @@ -33,10 +45,10 @@ public: void run (); private: - void audio (boost::shared_ptr<const AudioBuffers>, Time); + void analyse (boost::shared_ptr<const AudioBuffers>); 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_analysis.h b/src/lib/audio_analysis.h index 824472dda..b91a1cf51 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/audio_analysis.h + * @brief AudioAnalysis and AudioPoint classes. + */ + #ifndef DCPOMATIC_AUDIO_ANALYSIS_H #define DCPOMATIC_AUDIO_ANALYSIS_H @@ -24,6 +28,9 @@ #include <list> #include <boost/filesystem.hpp> +/** @class AudioPoint + * @brief A single point of an audio analysis for one portion of one channel. + */ class AudioPoint { public: @@ -48,6 +55,14 @@ private: float _data[COUNT]; }; +/** @class AudioAnalysis + * @brief An analysis of the audio data in a piece of AudioContent. + * + * This is a set of AudioPoints for each channel. The AudioPoints + * each represent some measurement of the audio over a portion of the + * content. For example each AudioPoint may give the RMS level of + * a 1-minute portion of the audio. + */ class AudioAnalysis : public boost::noncopyable { public: diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc index a1c9b81ac..56ca7a94b 100644 --- a/src/lib/audio_buffers.cc +++ b/src/lib/audio_buffers.cc @@ -73,6 +73,9 @@ AudioBuffers::~AudioBuffers () void AudioBuffers::allocate (int channels, int frames) { + assert (frames >= 0); + assert (channels >= 0); + _channels = channels; _frames = frames; _allocated_frames = frames; @@ -172,6 +175,11 @@ AudioBuffers::make_silent (int from, int frames) void AudioBuffers::copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset) { + if (frames_to_copy == 0) { + /* Prevent the asserts from firing if there is nothing to do */ + return; + } + assert (from->channels() == channels()); assert (from); @@ -255,6 +263,8 @@ void AudioBuffers::accumulate_frames (AudioBuffers const * from, int read_offset, int write_offset, int frames) { assert (_channels == from->channels ()); + assert (read_offset >= 0); + assert (write_offset >= 0); for (int i = 0; i < _channels; ++i) { for (int j = 0; j < frames; ++j) { @@ -275,3 +285,29 @@ AudioBuffers::apply_gain (float dB) } } } + +/** @param c Channel index. + * @return AudioBuffers object containing only channel `c' from this AudioBuffers. + */ +shared_ptr<AudioBuffers> +AudioBuffers::channel (int c) const +{ + shared_ptr<AudioBuffers> o (new AudioBuffers (1, frames ())); + o->copy_channel_from (this, c, 0); + return o; +} + +void +AudioBuffers::copy_channel_from (AudioBuffers const * from, int from_channel, int to_channel) +{ + assert (from->frames() == frames()); + memcpy (data(to_channel), from->data(from_channel), frames() * sizeof (float)); +} + +shared_ptr<AudioBuffers> +AudioBuffers::clone () const +{ + shared_ptr<AudioBuffers> b (new AudioBuffers (channels (), frames ())); + b->copy_from (this, frames (), 0, 0); + return b; +} diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h index c9030dbfa..8cd67aaa7 100644 --- a/src/lib/audio_buffers.h +++ b/src/lib/audio_buffers.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 @@ -17,8 +17,12 @@ */ -#ifndef DVDOMATIC_AUDIO_BUFFERS_H -#define DVDOMATIC_AUDIO_BUFFERS_H +/** @file src/lib/audio_buffers.h + * @brief AudioBuffers class. + */ + +#ifndef DCPOMATIC_AUDIO_BUFFERS_H +#define DCPOMATIC_AUDIO_BUFFERS_H #include <boost/shared_ptr.hpp> @@ -35,6 +39,9 @@ public: AudioBuffers & operator= (AudioBuffers const &); + boost::shared_ptr<AudioBuffers> clone () const; + boost::shared_ptr<AudioBuffers> channel (int) const; + void ensure_size (int); float** data () const { @@ -60,8 +67,9 @@ public: void apply_gain (float); void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset); + void copy_channel_from (AudioBuffers const * from, int from_channel, int to_channel); void move (int from, int to, int frames); - void accumulate_channel (AudioBuffers const *, int, int, float gain = 1); + void accumulate_channel (AudioBuffers const * from, int from_channel, int to_channel, float gain = 1); void accumulate_frames (AudioBuffers const *, int read_offset, int write_offset, int frames); private: diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 29d159a29..d02728b00 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -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 @@ -18,7 +18,7 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "audio_content.h" #include "analyse_audio_job.h" #include "job_manager.h" @@ -26,6 +26,7 @@ #include "exceptions.h" #include "config.h" #include "frame_rate_change.h" +#include "audio_processor.h" #include "i18n.h" @@ -34,7 +35,7 @@ using std::cout; using std::vector; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; int const AudioContentProperty::AUDIO_CHANNELS = 200; int const AudioContentProperty::AUDIO_LENGTH = 201; @@ -42,11 +43,22 @@ int const AudioContentProperty::AUDIO_FRAME_RATE = 202; int const AudioContentProperty::AUDIO_GAIN = 203; int const AudioContentProperty::AUDIO_DELAY = 204; int const AudioContentProperty::AUDIO_MAPPING = 205; +int const AudioContentProperty::AUDIO_PROCESSOR = 206; -AudioContent::AudioContent (shared_ptr<const Film> f, Time s) +AudioContent::AudioContent (shared_ptr<const Film> f) + : Content (f) + , _audio_gain (0) + , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) +{ + +} + +AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s) : Content (f, s) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } @@ -55,15 +67,20 @@ AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } -AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) +AudioContent::AudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node) : Content (f, node) + , _audio_processor (0) { _audio_gain = node->number_child<float> ("AudioGain"); _audio_delay = node->number_child<int> ("AudioDelay"); + if (node->optional_string_child ("AudioProcessor")) { + _audio_processor = AudioProcessor::from_id (node->string_child ("AudioProcessor")); + } } AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c) @@ -86,6 +103,7 @@ AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> _audio_gain = ref->audio_gain (); _audio_delay = ref->audio_delay (); + _audio_processor = ref->audio_processor (); } void @@ -94,6 +112,9 @@ AudioContent::as_xml (xmlpp::Node* node) const boost::mutex::scoped_lock lm (_mutex); node->add_child("AudioGain")->add_child_text (raw_convert<string> (_audio_gain)); node->add_child("AudioDelay")->add_child_text (raw_convert<string> (_audio_delay)); + if (_audio_processor) { + node->add_child("AudioProcessor")->add_child_text (_audio_processor->id ()); + } } @@ -119,6 +140,22 @@ AudioContent::set_audio_delay (int d) signal_changed (AudioContentProperty::AUDIO_DELAY); } +void +AudioContent::set_audio_processor (AudioProcessor const * p) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_processor = p; + } + + /* The channel count might have changed, so reset the mapping */ + AudioMapping m (processed_audio_channels ()); + m.make_default (); + set_audio_mapping (m); + + signal_changed (AudioContentProperty::AUDIO_PROCESSOR); +} + boost::signals2::connection AudioContent::analyse_audio (boost::function<void()> finished) { @@ -148,24 +185,38 @@ 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, content rate %3, resampled rate %4", + audio_channels(), + audio_length().seconds(), + audio_frame_rate(), + resampled_audio_frame_rate() + ); +} + +void +AudioContent::set_audio_mapping (AudioMapping) +{ + signal_changed (AudioContentProperty::AUDIO_MAPPING); } +/** @return the frame rate that this content should be resampled to in order + * that it is in sync with the active video content at its start time. + */ int -AudioContent::output_audio_frame_rate () const +AudioContent::resampled_audio_frame_rate () const { shared_ptr<const Film> film = _film.lock (); assert (film); /* Resample to a DCI-approved sample rate */ - double t = dcp_audio_frame_rate (content_audio_frame_rate ()); + double t = dcp_audio_frame_rate (audio_frame_rate ()); FrameRateChange frc = film->active_frame_rate_change (position ()); /* 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) { @@ -175,3 +226,13 @@ AudioContent::output_audio_frame_rate () const return rint (t); } +int +AudioContent::processed_audio_channels () const +{ + if (!audio_processor ()) { + return audio_channels (); + } + + return audio_processor()->out_channels (audio_channels ()); +} + diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 2c324a3a4..57085a765 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.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,6 +17,10 @@ */ +/** @file src/lib/audio_content.h + * @brief AudioContent and AudioContentProperty classes. + */ + #ifndef DCPOMATIC_AUDIO_CONTENT_H #define DCPOMATIC_AUDIO_CONTENT_H @@ -27,6 +31,11 @@ namespace cxml { class Node; } +class AudioProcessor; + +/** @class AudioContentProperty + * @brief Names for properties of AudioContent. + */ class AudioContentProperty { public: @@ -36,34 +45,44 @@ public: static int const AUDIO_GAIN; static int const AUDIO_DELAY; static int const AUDIO_MAPPING; + static int const AUDIO_PROCESSOR; }; +/** @class AudioContent + * @brief Parent class for content which may contain audio data. + */ class AudioContent : public virtual Content { public: typedef int64_t Frame; - AudioContent (boost::shared_ptr<const Film>, Time); + AudioContent (boost::shared_ptr<const Film>); + 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>, cxml::ConstNodePtr); AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); void as_xml (xmlpp::Node *) const; std::string technical_summary () const; + /** @return number of audio channels in the content */ virtual int audio_channels () const = 0; - virtual AudioContent::Frame audio_length () const = 0; - virtual int content_audio_frame_rate () const = 0; + /** @return the length of the audio in the content */ + virtual ContentTime audio_length () const = 0; + /** @return the frame rate of the content */ + virtual int audio_frame_rate () const = 0; virtual AudioMapping audio_mapping () const = 0; - virtual void set_audio_mapping (AudioMapping) = 0; + virtual void set_audio_mapping (AudioMapping); virtual boost::filesystem::path audio_analysis_path () const; - int output_audio_frame_rate () const; - + int resampled_audio_frame_rate () const; + int processed_audio_channels () const; + boost::signals2::connection analyse_audio (boost::function<void()>); void set_audio_gain (double); void set_audio_delay (int); + void set_audio_processor (AudioProcessor const *); double audio_gain () const { boost::mutex::scoped_lock lm (_mutex); @@ -75,11 +94,17 @@ public: return _audio_delay; } + AudioProcessor const * audio_processor () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_processor; + } + private: /** Gain to apply to audio in dB */ double _audio_gain; /** Delay to apply to audio (positive moves audio later) in milliseconds */ int _audio_delay; + AudioProcessor const * _audio_processor; }; #endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 30bd2540a..f3251f306 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.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 @@ -20,39 +20,217 @@ #include <iostream> #include "audio_decoder.h" #include "audio_buffers.h" -#include "exceptions.h" -#include "log.h" +#include "audio_processor.h" #include "resampler.h" +#include "util.h" #include "i18n.h" using std::list; using std::pair; using std::cout; +using std::min; +using std::max; 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->resampled_audio_frame_rate() != content->audio_frame_rate() && content->audio_channels ()) { + _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ())); + } + if (content->audio_processor ()) { + _processor = content->audio_processor()->clone (content->resampled_audio_frame_rate ()); + } + + reset_decoded_audio (); } void -AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame) +AudioDecoder::reset_decoded_audio () { - Audio (data, frame); - _audio_position = frame + data->frames (); + _decoded_audio = ContentAudio (shared_ptr<AudioBuffers> (new AudioBuffers (_audio_content->processed_audio_channels(), 0)), 0); } -/** 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. +shared_ptr<ContentAudio> +AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate) +{ + shared_ptr<ContentAudio> dec; + + AudioFrame const end = frame + length - 1; + + if (frame < _decoded_audio.frame || end > (_decoded_audio.frame + length * 4)) { + /* Either we have no decoded data, or what we do have is a long way from what we want: seek */ + seek (ContentTime::from_frames (frame, _audio_content->audio_frame_rate()), accurate); + } + + /* Offset of the data that we want from the start of _decoded_audio.audio + (to be set up shortly) + */ + AudioFrame decoded_offset = 0; + + /* Now enough pass() calls will either: + * (a) give us what we want, or + * (b) hit the end of the decoder. + * + * If we are being accurate, we want the right frames, + * otherwise any frames will do. + */ + if (accurate) { + /* Keep stuffing data into _decoded_audio until we have enough data, or the subclass does not want to give us any more */ + while ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass ()) {} + decoded_offset = frame - _decoded_audio.frame; + } else { + while (_decoded_audio.audio->frames() < length && !pass ()) {} + /* Use decoded_offset of 0, as we don't really care what frames we return */ + } + + /* The amount of data available in _decoded_audio.audio starting from `frame'. This could be -ve + if pass() returned true before we got enough data. + */ + AudioFrame const available = _decoded_audio.audio->frames() - decoded_offset; + + /* We will return either that, or the requested amount, whichever is smaller */ + AudioFrame const to_return = max ((AudioFrame) 0, min (available, length)); + + /* Copy our data to the output */ + shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), to_return)); + out->copy_from (_decoded_audio.audio.get(), to_return, decoded_offset, 0); + + AudioFrame const remaining = max ((AudioFrame) 0, available - to_return); + + /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */ + _decoded_audio.audio->move (decoded_offset + to_return, 0, remaining); + /* And set up the number of frames we have left */ + _decoded_audio.audio->set_frames (remaining); + /* Also bump where those frames are in terms of the content */ + _decoded_audio.frame += decoded_offset + to_return; + + return shared_ptr<ContentAudio> (new ContentAudio (out, frame)); +} + +/** Called by subclasses when audio data is ready. + * + * Audio timestamping is made hard by many factors, but perhaps the most entertaining 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. */ -bool -AudioDecoder::has_audio () const +void +AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time) +{ + if (_resampler) { + data = _resampler->run (data); + } + + if (_processor) { + data = _processor->run (data); + } + + AudioFrame const frame_rate = _audio_content->resampled_audio_frame_rate (); + + if (_seek_reference) { + /* We've had an accurate seek and now we're seeing some data */ + ContentTime const delta = time - _seek_reference.get (); + AudioFrame const delta_frames = delta.frames (frame_rate); + if (delta_frames > 0) { + /* This data comes after the seek time. Pad the data with some silence. */ + shared_ptr<AudioBuffers> padded (new AudioBuffers (data->channels(), data->frames() + delta_frames)); + padded->make_silent (); + padded->copy_from (data.get(), data->frames(), 0, delta_frames); + data = padded; + time -= delta; + } else if (delta_frames < 0) { + /* This data comes before the seek time. Throw some data away */ + AudioFrame const to_discard = min (-delta_frames, static_cast<AudioFrame> (data->frames())); + AudioFrame const to_keep = data->frames() - to_discard; + if (to_keep == 0) { + /* We have to throw all this data away, so keep _seek_reference and + try again next time some data arrives. + */ + return; + } + shared_ptr<AudioBuffers> trimmed (new AudioBuffers (data->channels(), to_keep)); + trimmed->copy_from (data.get(), to_keep, to_discard, 0); + data = trimmed; + time += ContentTime::from_frames (to_discard, frame_rate); + } + _seek_reference = optional<ContentTime> (); + } + + if (!_audio_position) { + _audio_position = time.frames (frame_rate); + } + + assert (_audio_position.get() >= (_decoded_audio.frame + _decoded_audio.audio->frames())); + + add (data); +} + +void +AudioDecoder::add (shared_ptr<const AudioBuffers> data) +{ + if (!_audio_position) { + /* This should only happen when there is a seek followed by a flush, but + we need to cope with it. + */ + return; + } + + /* Resize _decoded_audio to fit the new data */ + int new_size = 0; + if (_decoded_audio.audio->frames() == 0) { + /* There's nothing in there, so just store the new data */ + new_size = data->frames (); + _decoded_audio.frame = _audio_position.get (); + } else { + /* Otherwise we need to extend _decoded_audio to include the new stuff */ + new_size = _audio_position.get() + data->frames() - _decoded_audio.frame; + } + + _decoded_audio.audio->ensure_size (new_size); + _decoded_audio.audio->set_frames (new_size); + + /* Copy new data in */ + _decoded_audio.audio->copy_from (data.get(), data->frames(), 0, _audio_position.get() - _decoded_audio.frame); + _audio_position = _audio_position.get() + data->frames (); + + /* Limit the amount of data we keep in case nobody is asking for it */ + int const max_frames = _audio_content->resampled_audio_frame_rate () * 10; + if (_decoded_audio.audio->frames() > max_frames) { + int const to_remove = _decoded_audio.audio->frames() - max_frames; + _decoded_audio.frame += to_remove; + _decoded_audio.audio->move (to_remove, 0, max_frames); + _decoded_audio.audio->set_frames (max_frames); + } +} + +void +AudioDecoder::flush () +{ + if (!_resampler) { + return; + } + + shared_ptr<const AudioBuffers> b = _resampler->flush (); + if (b) { + add (b); + } +} + +void +AudioDecoder::seek (ContentTime t, bool accurate) { - return _audio_content->audio_channels () > 0; + _audio_position.reset (); + reset_decoded_audio (); + if (accurate) { + _seek_reference = t; + } + if (_processor) { + _processor->flush (); + } } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index ab6c4b8a9..f8438df52 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -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 @@ -27,8 +27,10 @@ #include "decoder.h" #include "content.h" #include "audio_content.h" +#include "content_audio.h" class AudioBuffers; +class Resampler; /** @class AudioDecoder. * @brief Parent class for audio decoders. @@ -36,18 +38,38 @@ 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; + } + /** Try to fetch some audio from a specific place in this content. + * @param frame Frame to start from (after resampling, if applicable) + * @param length Frames to get (after resampling, if applicable) + * @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames. + * @return Time-stamped audio data which may or may not be from the location (and of the length) requested. + */ + boost::shared_ptr<ContentAudio> get_audio (AudioFrame time, AudioFrame length, bool accurate); + protected: - void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); + void seek (ContentTime time, bool accurate); + void audio (boost::shared_ptr<const AudioBuffers>, ContentTime); + void flush (); + void reset_decoded_audio (); + void add (boost::shared_ptr<const AudioBuffers>); + boost::shared_ptr<const AudioContent> _audio_content; - AudioContent::Frame _audio_position; + boost::shared_ptr<Resampler> _resampler; + boost::shared_ptr<AudioProcessor> _processor; + boost::optional<AudioFrame> _audio_position; + /** Currently-available decoded audio data */ + ContentAudio _decoded_audio; + /** The time of an accurate seek after which we have not yet received any actual + data at the seek time. + */ + boost::optional<ContentTime> _seek_reference; }; #endif diff --git a/src/lib/audio_examiner.h b/src/lib/audio_examiner.h new file mode 100644 index 000000000..d6d4dbe97 --- /dev/null +++ b/src/lib/audio_examiner.h @@ -0,0 +1,35 @@ +/* + 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. + +*/ + +/** @file src/lib/audio_examiner.h + * @brief AudioExaminer class. + */ + +/** @class AudioExaminer + * @brief Parent for classes which examine AudioContent for their pertinent details. + */ +class AudioExaminer +{ +public: + virtual ~AudioExaminer () {} + + virtual int audio_channels () const = 0; + virtual ContentTime audio_length () const = 0; + virtual int audio_frame_rate () const = 0; +}; diff --git a/src/lib/audio_filter.cc b/src/lib/audio_filter.cc new file mode 100644 index 000000000..59b5684ea --- /dev/null +++ b/src/lib/audio_filter.cc @@ -0,0 +1,139 @@ +/* + 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 <cmath> +#include "audio_filter.h" +#include "audio_buffers.h" + +using std::vector; +using std::min; +using boost::shared_ptr; + +vector<float> +AudioFilter::sinc_blackman (float cutoff, bool invert) const +{ + vector<float> ir (_M + 1); + + /* Impulse response */ + + for (int i = 0; i <= _M; ++i) { + if (i == (_M / 2)) { + ir[i] = 2 * M_PI * cutoff; + } else { + /* sinc */ + ir[i] = sin (2 * M_PI * cutoff * (i - _M / 2)) / (i - _M / 2); + /* Blackman window */ + ir[i] *= (0.42 - 0.5 * cos (2 * M_PI * i / _M) + 0.08 * cos (4 * M_PI * i / _M)); + } + } + + /* Normalise */ + + float sum = 0; + for (int i = 0; i <= _M; ++i) { + sum += ir[i]; + } + + for (int i = 0; i <= _M; ++i) { + ir[i] /= sum; + } + + /* Frequency inversion (swapping low-pass for high-pass, or whatever) */ + + if (invert) { + for (int i = 0; i <= _M; ++i) { + ir[i] = -ir[i]; + } + ir[_M / 2] += 1; + } + + return ir; +} + +shared_ptr<AudioBuffers> +AudioFilter::run (shared_ptr<AudioBuffers> in) +{ + shared_ptr<AudioBuffers> out (new AudioBuffers (in->channels(), in->frames())); + + if (!_tail) { + _tail.reset (new AudioBuffers (in->channels(), _M + 1)); + _tail->make_silent (); + } + + for (int i = 0; i < in->channels(); ++i) { + for (int j = 0; j < in->frames(); ++j) { + float s = 0; + for (int k = 0; k <= _M; ++k) { + if ((j - k) < 0) { + s += _tail->data(i)[j - k + _M + 1] * _ir[k]; + } else { + s += in->data(i)[j - k] * _ir[k]; + } + } + + out->data(i)[j] = s; + } + } + + int const amount = min (in->frames(), _tail->frames()); + if (amount < _tail->frames ()) { + _tail->move (amount, 0, _tail->frames() - amount); + } + _tail->copy_from (in.get(), amount, in->frames() - amount, _tail->frames () - amount); + + return out; +} + +void +AudioFilter::flush () +{ + _tail.reset (); +} + +LowPassAudioFilter::LowPassAudioFilter (float transition_bandwidth, float cutoff) + : AudioFilter (transition_bandwidth) +{ + _ir = sinc_blackman (cutoff, false); +} + + +HighPassAudioFilter::HighPassAudioFilter (float transition_bandwidth, float cutoff) + : AudioFilter (transition_bandwidth) +{ + _ir = sinc_blackman (cutoff, true); +} + +BandPassAudioFilter::BandPassAudioFilter (float transition_bandwidth, float lower, float higher) + : AudioFilter (transition_bandwidth) +{ + vector<float> lpf = sinc_blackman (lower, false); + vector<float> hpf = sinc_blackman (higher, true); + + _ir.resize (_M + 1); + for (int i = 0; i <= _M; ++i) { + _ir[i] = lpf[i] + hpf[i]; + } + + /* We now have a band-stop, so invert for band-pass */ + for (int i = 0; i <= _M; ++i) { + _ir[i] = -_ir[i]; + } + + _ir[_M / 2] += 1; +} diff --git a/src/lib/audio_filter.h b/src/lib/audio_filter.h new file mode 100644 index 000000000..9fc69daad --- /dev/null +++ b/src/lib/audio_filter.h @@ -0,0 +1,82 @@ +/* + 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 <vector> +#include <boost/shared_ptr.hpp> + +class AudioBuffers; +class audio_filter_impulse_kernel_test; +struct audio_filter_impulse_input_test; + +class AudioFilter +{ +public: + AudioFilter (float transition_bandwidth) + { + _M = 4 / transition_bandwidth; + if (_M % 2) { + ++_M; + } + } + + boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<AudioBuffers> in); + + void flush (); + +protected: + friend struct audio_filter_impulse_kernel_test; + friend struct audio_filter_impulse_input_test; + + std::vector<float> sinc_blackman (float cutoff, bool invert) const; + + std::vector<float> _ir; + int _M; + boost::shared_ptr<AudioBuffers> _tail; +}; + +class LowPassAudioFilter : public AudioFilter +{ +public: + /** Construct a windowed-sinc low-pass filter using the Blackman window. + * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate. + * @param cutoff Cutoff frequency as a fraction of the sampling rate. + */ + LowPassAudioFilter (float transition_bandwidth, float cutoff); +}; + +class HighPassAudioFilter : public AudioFilter +{ +public: + /** Construct a windowed-sinc high-pass filter using the Blackman window. + * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate. + * @param cutoff Cutoff frequency as a fraction of the sampling rate. + */ + HighPassAudioFilter (float transition_bandwidth, float cutoff); +}; + +class BandPassAudioFilter : public AudioFilter +{ +public: + /** Construct a windowed-sinc band-pass filter using the Blackman window. + * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate. + * @param lower Lower cutoff frequency as a fraction of the sampling rate. + * @param higher Higher cutoff frequency as a fraction of the sampling rate. + */ + BandPassAudioFilter (float transition_bandwidth, float lower, float higher); +}; diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index e35c1ae94..e86e2e2ac 100644 --- a/src/lib/audio_mapping.cc +++ b/src/lib/audio_mapping.cc @@ -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 @@ -19,7 +19,7 @@ #include <libxml++/libxml++.h> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "audio_mapping.h" #include "util.h" #include "md5_digester.h" @@ -32,7 +32,7 @@ using std::string; using std::min; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; AudioMapping::AudioMapping () : _content_channels (0) @@ -40,12 +40,12 @@ AudioMapping::AudioMapping () } -/** Create a default AudioMapping for a given channel count. - * @param c Number of channels. +/** Create an empty AudioMapping for a given channel count. + * @param channels Number of channels. */ -AudioMapping::AudioMapping (int c) +AudioMapping::AudioMapping (int channels) { - setup (c); + setup (channels); } void @@ -70,16 +70,16 @@ 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 < min (_content_channels, MAX_DCP_AUDIO_CHANNELS); ++i) { - set (i, static_cast<libdcp::Channel> (i), 1); + set (i, static_cast<dcp::Channel> (i), 1); } } } -AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version) +AudioMapping::AudioMapping (cxml::ConstNodePtr node, int state_version) { setup (node->number_child<int> ("ContentChannels")); @@ -87,14 +87,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")), raw_convert<float> ((*i)->content ()) ); } @@ -102,13 +102,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]; } @@ -123,7 +123,7 @@ AudioMapping::as_xml (xmlpp::Node* node) const xmlpp::Element* t = node->add_child ("Gain"); t->set_attribute ("Content", raw_convert<string> (c)); t->set_attribute ("DCP", raw_convert<string> (d)); - t->add_child_text (raw_convert<string> (get (c, static_cast<libdcp::Channel> (d)))); + t->add_child_text (raw_convert<string> (get (c, static_cast<dcp::Channel> (d)))); } } } diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h index b0b75ac06..a76d83a37 100644 --- a/src/lib/audio_mapping.h +++ b/src/lib/audio_mapping.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,12 +17,17 @@ */ +/** @file src/lib/audio_mapping.h + * @brief AudioMapping class. + */ + #ifndef DCPOMATIC_AUDIO_MAPPING_H #define DCPOMATIC_AUDIO_MAPPING_H #include <vector> -#include <libdcp/types.h> #include <boost/shared_ptr.hpp> +#include <dcp/types.h> +#include <libcxml/cxml.h> namespace xmlpp { class Node; @@ -32,7 +37,9 @@ namespace cxml { class Node; } -/** A many-to-many mapping from some content channels to DCP channels. +/** @class AudioMapping. + * @brief A many-to-many mapping from some content channels to DCP channels. + * * The number of content channels is set on construction and fixed, * and then each of those content channels are mapped to each DCP channel * by a linear gain. @@ -41,8 +48,8 @@ class AudioMapping { public: AudioMapping (); - AudioMapping (int); - AudioMapping (boost::shared_ptr<const cxml::Node>, int); + AudioMapping (int channels); + AudioMapping (cxml::ConstNodePtr, int); /* Default copy constructor is fine */ @@ -50,8 +57,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.h b/src/lib/audio_merger.h deleted file mode 100644 index 226601e0e..000000000 --- a/src/lib/audio_merger.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include "audio_buffers.h" -#include "util.h" - -template <class T, class F> -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) - {} - - /** 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); - } - -private: - boost::shared_ptr<AudioBuffers> _buffers; - T _last_pull; - boost::function<F (T)> _t_to_f; - boost::function<T (F)> _f_to_t; -}; diff --git a/src/lib/audio_processor.cc b/src/lib/audio_processor.cc new file mode 100644 index 000000000..f350cc2aa --- /dev/null +++ b/src/lib/audio_processor.cc @@ -0,0 +1,52 @@ +/* + 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 "audio_processor.h" +#include "mid_side_decoder.h" +#include "upmixer_a.h" + +using std::string; +using std::list; + +list<AudioProcessor const *> AudioProcessor::_all; + +void +AudioProcessor::setup_audio_processors () +{ + _all.push_back (new MidSideDecoder ()); + _all.push_back (new UpmixerA (48000)); +} + +AudioProcessor const * +AudioProcessor::from_id (string id) +{ + for (list<AudioProcessor const *>::const_iterator i = _all.begin(); i != _all.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + + return 0; +} + +list<AudioProcessor const *> +AudioProcessor::all () +{ + return _all; +} diff --git a/src/lib/audio_processor.h b/src/lib/audio_processor.h new file mode 100644 index 000000000..9b332e7fe --- /dev/null +++ b/src/lib/audio_processor.h @@ -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. + +*/ + +#ifndef DCPOMATIC_AUDIO_PROCESSOR_H +#define DCPOMATIC_AUDIO_PROCESSOR_H + +#include <list> +#include <string> +#include <boost/shared_ptr.hpp> +#include "channel_count.h" + +class AudioBuffers; + +class AudioProcessor +{ +public: + virtual ~AudioProcessor () {} + + virtual std::string name () const = 0; + virtual std::string id () const = 0; + virtual ChannelCount in_channels () const = 0; + virtual int out_channels (int) const = 0; + virtual boost::shared_ptr<AudioProcessor> clone (int sampling_rate) const = 0; + virtual boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<const AudioBuffers>) = 0; + virtual void flush () {} + + static std::list<AudioProcessor const *> all (); + static void setup_audio_processors (); + static AudioProcessor const * from_id (std::string); + +private: + static std::list<AudioProcessor const *> _all; +}; + +#endif diff --git a/src/lib/channel_count.h b/src/lib/channel_count.h new file mode 100644 index 000000000..4247fc063 --- /dev/null +++ b/src/lib/channel_count.h @@ -0,0 +1,49 @@ +/* + 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_CHANNEL_COUNT_H +#define DCPOMATIC_CHANNEL_COUNT_H + +class ChannelCount +{ +public: + ChannelCount () + : min (0) + , max (0) + {} + + ChannelCount (int n) + : min (n) + , max (n) + {} + + ChannelCount (int min_, int max_) + : min (min_) + , max (max_) + {} + + bool includes (int c) { + return min <= c && c <= max; + } + + int min; + int max; +}; + +#endif diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc index fca6b6afd..620236186 100644 --- a/src/lib/cinema.cc +++ b/src/lib/cinema.cc @@ -24,7 +24,7 @@ using std::list; using boost::shared_ptr; -Cinema::Cinema (shared_ptr<const cxml::Node> node) +Cinema::Cinema (cxml::ConstNodePtr node) : name (node->string_child ("Name")) , email (node->string_child ("Email")) { @@ -35,7 +35,7 @@ Cinema::Cinema (shared_ptr<const cxml::Node> node) a constructor) */ void -Cinema::read_screens (shared_ptr<const cxml::Node> node) +Cinema::read_screens (cxml::ConstNodePtr node) { list<cxml::NodePtr> s = node->node_children ("Screen"); for (list<cxml::NodePtr>::iterator i = s.begin(); i != s.end(); ++i) { @@ -67,17 +67,21 @@ Cinema::remove_screen (shared_ptr<Screen> s) _screens.remove (s); } -Screen::Screen (shared_ptr<const cxml::Node> node) +Screen::Screen (cxml::ConstNodePtr node) { name = node->string_child ("Name"); - certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate"))); + if (node->optional_string_child ("Certificate")) { + certificate = dcp::Certificate (node->string_child ("Certificate")); + } } void Screen::as_xml (xmlpp::Element* parent) const { parent->add_child("Name")->add_child_text (name); - parent->add_child("Certificate")->add_child_text (certificate->certificate (true)); + if (certificate) { + parent->add_child("Certificate")->add_child_text (certificate->certificate (true)); + } } diff --git a/src/lib/cinema.h b/src/lib/cinema.h index 40dc15ae0..8421f4687 100644 --- a/src/lib/cinema.h +++ b/src/lib/cinema.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,32 +17,45 @@ */ +/** @file src/lib/cinema.h + * @brief Screen and Cinema classes. + */ + #include <boost/enable_shared_from_this.hpp> -#include <libdcp/certificates.h> +#include <dcp/certificates.h> +#include <libcxml/cxml.h> class Cinema; -namespace cxml { - class Node; -} - +/** @class Screen + * @brief A representation of a Screen for KDM generation. + * + * This is the name of the screen and the certificate of its + * server. + */ class Screen { public: - Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert) + Screen (std::string const & n, boost::optional<dcp::Certificate> cert) : name (n) , certificate (cert) {} - Screen (boost::shared_ptr<const cxml::Node>); + Screen (cxml::ConstNodePtr); void as_xml (xmlpp::Element *) const; boost::shared_ptr<Cinema> cinema; std::string name; - boost::shared_ptr<libdcp::Certificate> certificate; + boost::optional<dcp::Certificate> certificate; }; +/** @class Cinema + * @brief A description of a Cinema for KDM generation. + * + * This is a cinema name, contact email address and a list of + * Screen objects. + */ class Cinema : public boost::enable_shared_from_this<Cinema> { public: @@ -51,9 +64,9 @@ public: , email (e) {} - Cinema (boost::shared_ptr<const cxml::Node>); + Cinema (cxml::ConstNodePtr); - void read_screens (boost::shared_ptr<const cxml::Node>); + void read_screens (cxml::ConstNodePtr); void as_xml (xmlpp::Element *) const; diff --git a/src/lib/sound_processor.cc b/src/lib/cinema_sound_processor.cc index 9be6621cc..6a7905114 100644 --- a/src/lib/sound_processor.cc +++ b/src/lib/cinema_sound_processor.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 @@ -18,22 +18,22 @@ */ /** @file src/sound_processor.cc - * @brief A class to describe a sound processor. + * @brief CinemaSoundProcessor class. */ #include <iostream> #include <cassert> -#include "sound_processor.h" +#include "cinema_sound_processor.h" #include "dolby_cp750.h" using namespace std; -vector<SoundProcessor const *> SoundProcessor::_sound_processors; +vector<CinemaSoundProcessor const *> CinemaSoundProcessor::_cinema_sound_processors; /** @param i Our id. * @param n User-visible name. */ -SoundProcessor::SoundProcessor (string i, string n) +CinemaSoundProcessor::CinemaSoundProcessor (string i, string n) : _id (i) , _name (n) { @@ -41,33 +41,33 @@ SoundProcessor::SoundProcessor (string i, string n) } /** @return All available sound processors */ -vector<SoundProcessor const *> -SoundProcessor::all () +vector<CinemaSoundProcessor const *> +CinemaSoundProcessor::all () { - return _sound_processors; + return _cinema_sound_processors; } /** Set up the static _sound_processors vector; must be called before from_* * methods are used. */ void -SoundProcessor::setup_sound_processors () +CinemaSoundProcessor::setup_cinema_sound_processors () { - _sound_processors.push_back (new DolbyCP750); + _cinema_sound_processors.push_back (new DolbyCP750); } /** @param id One of our ids. * @return Corresponding sound processor, or 0. */ -SoundProcessor const * -SoundProcessor::from_id (string id) +CinemaSoundProcessor const * +CinemaSoundProcessor::from_id (string id) { - vector<SoundProcessor const *>::iterator i = _sound_processors.begin (); - while (i != _sound_processors.end() && (*i)->id() != id) { + vector<CinemaSoundProcessor const *>::iterator i = _cinema_sound_processors.begin (); + while (i != _cinema_sound_processors.end() && (*i)->id() != id) { ++i; } - if (i == _sound_processors.end ()) { + if (i == _cinema_sound_processors.end ()) { return 0; } @@ -78,14 +78,14 @@ SoundProcessor::from_id (string id) * @return Index of the sound processor with the list, or -1. */ int -SoundProcessor::as_index (SoundProcessor const * s) +CinemaSoundProcessor::as_index (CinemaSoundProcessor const * s) { - vector<SoundProcessor*>::size_type i = 0; - while (i < _sound_processors.size() && _sound_processors[i] != s) { + vector<CinemaSoundProcessor*>::size_type i = 0; + while (i < _cinema_sound_processors.size() && _cinema_sound_processors[i] != s) { ++i; } - if (i == _sound_processors.size ()) { + if (i == _cinema_sound_processors.size ()) { return -1; } @@ -95,9 +95,9 @@ SoundProcessor::as_index (SoundProcessor const * s) /** @param i An index returned from as_index(). * @return Corresponding sound processor. */ -SoundProcessor const * -SoundProcessor::from_index (int i) +CinemaSoundProcessor const * +CinemaSoundProcessor::from_index (int i) { - assert (i <= int(_sound_processors.size ())); - return _sound_processors[i]; + assert (i <= int(_cinema_sound_processors.size ())); + return _cinema_sound_processors[i]; } diff --git a/src/lib/sound_processor.h b/src/lib/cinema_sound_processor.h index 8f2652243..f735b1227 100644 --- a/src/lib/sound_processor.h +++ b/src/lib/cinema_sound_processor.h @@ -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 @@ -17,24 +17,27 @@ */ -/** @file src/sound_processor.h - * @brief A class to describe a sound processor. +/** @file src/cinema_sound_processor.h + * @brief CinemaSoundProcessor class */ -#ifndef DCPOMATIC_SOUND_PROCESSOR_H -#define DCPOMATIC_SOUND_PROCESSOR_H +#ifndef DCPOMATIC_CINEMA_SOUND_PROCESSOR_H +#define DCPOMATIC_CINEMA_SOUND_PROCESSOR_H #include <string> #include <vector> #include <boost/utility.hpp> -/** @class SoundProcessor - * @brief Class to describe a sound processor. +/** @class CinemaSoundProcessor + * @brief Class to describe a cimema's sound processor. + * + * In other words, the box in the rack that handles sound decoding and processing + * in a cinema. */ -class SoundProcessor : public boost::noncopyable +class CinemaSoundProcessor : public boost::noncopyable { public: - SoundProcessor (std::string i, std::string n); + CinemaSoundProcessor (std::string i, std::string n); virtual float db_for_fader_change (float from, float to) const = 0; @@ -48,11 +51,11 @@ public: return _name; } - static std::vector<SoundProcessor const *> all (); - static void setup_sound_processors (); - static SoundProcessor const * from_id (std::string id); - static SoundProcessor const * from_index (int); - static int as_index (SoundProcessor const *); + static std::vector<CinemaSoundProcessor const *> all (); + static void setup_cinema_sound_processors (); + static CinemaSoundProcessor const * from_id (std::string id); + static CinemaSoundProcessor const * from_index (int); + static int as_index (CinemaSoundProcessor const *); private: /** id for our use */ @@ -60,8 +63,8 @@ private: /** user-visible name for this sound processor */ std::string _name; - /** sll available sound processors */ - static std::vector<SoundProcessor const *> _sound_processors; + /** sll available cinema sound processors */ + static std::vector<CinemaSoundProcessor const *> _cinema_sound_processors; }; #endif diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc index e5b1104ff..c836cc271 100644 --- a/src/lib/colour_conversion.cc +++ b/src/lib/colour_conversion.cc @@ -18,8 +18,8 @@ */ #include <libxml++/libxml++.h> -#include <libdcp/colour_matrix.h> -#include <libdcp/raw_convert.h> +#include <dcp/colour_matrix.h> +#include <dcp/raw_convert.h> #include <libcxml/cxml.h> #include "config.h" #include "colour_conversion.h" @@ -34,7 +34,7 @@ using std::cout; using std::vector; using boost::shared_ptr; using boost::optional; -using libdcp::raw_convert; +using dcp::raw_convert; ColourConversion::ColourConversion () : input_gamma (2.4) @@ -44,7 +44,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 878fedaa4..2b7b81cfe 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -22,8 +22,10 @@ #include <glib.h> #include <boost/filesystem.hpp> #include <boost/algorithm/string.hpp> -#include <libdcp/colour_matrix.h> -#include <libdcp/raw_convert.h> +#include <dcp/colour_matrix.h> +#include <dcp/raw_convert.h> +#include <dcp/signer.h> +#include <dcp/certificate_chain.h> #include <libcxml/cxml.h> #include "config.h" #include "server.h" @@ -31,10 +33,11 @@ #include "filter.h" #include "ratio.h" #include "dcp_content_type.h" -#include "sound_processor.h" +#include "cinema_sound_processor.h" #include "colour_conversion.h" #include "cinema.h" #include "util.h" +#include "cross.h" #include "i18n.h" @@ -51,7 +54,7 @@ using boost::shared_ptr; using boost::optional; using boost::algorithm::is_any_of; using boost::algorithm::split; -using libdcp::raw_convert; +using dcp::raw_convert; Config* Config::_instance = 0; @@ -61,7 +64,7 @@ Config::Config () , _server_port_base (6192) , _use_any_servers (true) , _tms_path (".") - , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750"))) + , _cinema_sound_processor (CinemaSoundProcessor::from_id (N_("dolby_cp750"))) , _allow_any_dcp_frame_rate (false) , _default_still_length (10) , _default_scale (VideoContentScale (Ratio::from_id ("185"))) @@ -73,6 +76,9 @@ Config::Config () , _check_for_test_updates (false) , _maximum_j2k_bandwidth (250000000) , _log_types (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR) +#ifdef DCPOMATIC_WINDOWS + , _win32_console (false) +#endif { _allowed_dcp_frame_rates.push_back (24); _allowed_dcp_frame_rates.push_back (25); @@ -81,9 +87,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)); reset_kdm_email (); } @@ -91,13 +97,16 @@ Config::Config () void Config::read () { - if (!boost::filesystem::exists (file (false))) { - read_old_metadata (); + if (!boost::filesystem::exists (file ())) { + /* Make a new set of signing certificates and key */ + _signer.reset (new dcp::Signer (openssl_path ())); + /* And decryption keys */ + make_decryption_keys (); return; } cxml::Document f ("Config"); - f.read_file (file (false)); + f.read_file (file ()); optional<string> c; optional<int> version = f.optional_number_child<int> ("Version"); @@ -130,7 +139,11 @@ Config::read () c = f.optional_string_child ("SoundProcessor"); if (c) { - _sound_processor = SoundProcessor::from_id (c.get ()); + _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ()); + } + c = f.optional_string_child ("CinemaSoundProcessor"); + if (c) { + _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ()); } _language = f.optional_string_child ("Language"); @@ -180,7 +193,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"); @@ -209,99 +222,64 @@ Config::read () _allow_any_dcp_frame_rate = f.optional_bool_child ("AllowAnyDCPFrameRate"); _log_types = f.optional_number_child<int> ("LogTypes").get_value_or (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR); +#ifdef DCPOMATIC_WINDOWS + _win32_console = f.optional_bool_child ("Win32Console").get_value_or (false); +#endif list<cxml::NodePtr> his = f.node_children ("History"); for (list<cxml::NodePtr>::const_iterator i = his.begin(); i != his.end(); ++i) { _history.push_back ((*i)->content ()); } -} - -void -Config::read_old_metadata () -{ - /* XXX: this won't work with non-Latin filenames */ - ifstream f (file(true).string().c_str ()); - string line; - while (getline (f, line)) { - if (line.empty ()) { - continue; + cxml::NodePtr signer = f.optional_node_child ("Signer"); + dcp::CertificateChain signer_chain; + if (signer) { + /* Read the signing certificates and private key in from the config file */ + list<cxml::NodePtr> certificates = signer->node_children ("Certificate"); + for (list<cxml::NodePtr>::const_iterator i = certificates.begin(); i != certificates.end(); ++i) { + signer_chain.add (dcp::Certificate ((*i)->content ())); } - if (line[0] == '#') { - continue; - } + _signer.reset (new dcp::Signer (signer_chain, signer->string_child ("PrivateKey"))); + } else { + /* Make a new set of signing certificates and key */ + _signer.reset (new dcp::Signer (openssl_path ())); + } - size_t const s = line.find (' '); - if (s == string::npos) { - continue; - } - - string const k = line.substr (0, s); - string const v = line.substr (s + 1); - - if (k == N_("num_local_encoding_threads")) { - _num_local_encoding_threads = atoi (v.c_str ()); - } else if (k == N_("default_directory")) { - _default_directory = v; - } else if (k == N_("server_port")) { - _server_port_base = atoi (v.c_str ()); - } else if (k == N_("server")) { - vector<string> b; - split (b, v, is_any_of (" ")); - if (b.size() == 2) { - _servers.push_back (b[0]); - } - } else if (k == N_("tms_ip")) { - _tms_ip = v; - } else if (k == N_("tms_path")) { - _tms_path = v; - } else if (k == N_("tms_user")) { - _tms_user = v; - } else if (k == N_("tms_password")) { - _tms_password = v; - } else if (k == N_("sound_processor")) { - _sound_processor = SoundProcessor::from_id (v); - } else if (k == "language") { - _language = v; - } else if (k == "default_container") { - _default_container = Ratio::from_id (v); - } else if (k == "default_dcp_content_type") { - _default_dcp_content_type = DCPContentType::from_isdcf_name (v); - } else if (k == "dcp_metadata_issuer") { - _dcp_issuer = v; - } + if (f.optional_string_child ("DecryptionCertificate")) { + _decryption_certificate = dcp::Certificate (f.string_child ("DecryptionCertificate")); + } - _default_isdcf_metadata.read_old_metadata (k, v); + if (f.optional_string_child ("DecryptionPrivateKey")) { + _decryption_private_key = f.string_child ("DecryptionPrivateKey"); + } + + if (!f.optional_string_child ("DecryptionCertificate") || !f.optional_string_child ("DecryptionPrivateKey")) { + /* Generate our own decryption certificate and key if either is not present in config */ + make_decryption_keys (); } } -/** @return Filename to write configuration to */ -boost::filesystem::path -Config::file (bool old) const +void +Config::make_decryption_keys () { - boost::filesystem::path p; - p /= g_get_user_config_dir (); - boost::system::error_code ec; - boost::filesystem::create_directory (p, ec); - if (old) { - p /= ".dvdomatic"; - } else { - p /= "dcpomatic"; - boost::filesystem::create_directory (p, ec); - p /= "config.xml"; - } - return p; + boost::filesystem::path p = dcp::make_certificate_chain (openssl_path ()); + _decryption_certificate = dcp::Certificate (dcp::file_to_string (p / "leaf.signed.pem")); + _decryption_private_key = dcp::file_to_string (p / "leaf.key"); + boost::filesystem::remove_all (p); } +/** @return Filename to write configuration to */ boost::filesystem::path -Config::signer_chain_directory () const +Config::file () const { boost::filesystem::path p; p /= g_get_user_config_dir (); - p /= "dcpomatic"; - p /= "crypt"; - boost::filesystem::create_directories (p); + boost::system::error_code ec; + boost::filesystem::create_directory (p, ec); + p /= "dcpomatic2"; + boost::filesystem::create_directory (p, ec); + p /= "config.xml"; return p; } @@ -347,8 +325,8 @@ Config::write () const root->add_child("TMSPath")->add_child_text (_tms_path); root->add_child("TMSUser")->add_child_text (_tms_user); root->add_child("TMSPassword")->add_child_text (_tms_password); - if (_sound_processor) { - root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ()); + if (_cinema_sound_processor) { + root->add_child("CinemaSoundProcessor")->add_child_text (_cinema_sound_processor->id ()); } if (_language) { root->add_child("Language")->add_child_text (_language.get()); @@ -391,12 +369,25 @@ Config::write () const root->add_child("MaximumJ2KBandwidth")->add_child_text (raw_convert<string> (_maximum_j2k_bandwidth)); root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0"); root->add_child("LogTypes")->add_child_text (raw_convert<string> (_log_types)); +#ifdef DCPOMATIC_WINDOWS + root->add_child("Win32Console")->add_child_text (_win32_console ? "1" : "0"); +#endif + + xmlpp::Element* signer = root->add_child ("Signer"); + dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf (); + for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) { + signer->add_child("Certificate")->add_child_text (i->certificate (true)); + } + signer->add_child("PrivateKey")->add_child_text (_signer->key ()); + + root->add_child("DecryptionCertificate")->add_child_text (_decryption_certificate.certificate (true)); + root->add_child("DecryptionPrivateKey")->add_child_text (_decryption_private_key); for (vector<boost::filesystem::path>::const_iterator i = _history.begin(); i != _history.end(); ++i) { root->add_child("History")->add_child_text (i->string ()); } - doc.write_to_file_formatted (file(false).string ()); + doc.write_to_file_formatted (file().string ()); } boost::filesystem::path diff --git a/src/lib/config.h b/src/lib/config.h index 0639382a0..55a172d78 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -28,15 +28,17 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> #include <boost/filesystem.hpp> +#include <dcp/metadata.h> +#include <dcp/certificates.h> +#include <dcp/signer.h> #include "isdcf_metadata.h" #include "colour_conversion.h" -#include "server.h" #include "video_content.h" class ServerDescription; class Scaler; class Filter; -class SoundProcessor; +class CinemaSoundProcessor; class DCPContentType; class Ratio; class Cinema; @@ -104,9 +106,9 @@ public: return _tms_password; } - /** @return The sound processor that we are using */ - SoundProcessor const * sound_processor () const { - return _sound_processor; + /** @return The cinema sound processor that we are using */ + CinemaSoundProcessor const * cinema_sound_processor () const { + return _cinema_sound_processor; } std::list<boost::shared_ptr<Cinema> > cinemas () const { @@ -193,6 +195,18 @@ public: return _kdm_email; } + boost::shared_ptr<const dcp::Signer> signer () const { + return _signer; + } + + dcp::Certificate decryption_certificate () const { + return _decryption_certificate; + } + + std::string decryption_private_key () const { + return _decryption_private_key; + } + bool check_for_updates () const { return _check_for_updates; } @@ -209,6 +223,12 @@ public: return _log_types; } +#ifdef DCPOMATIC_WINDOWS + bool win32_console () const { + return _win32_console; + } +#endif + std::vector<boost::filesystem::path> history () const { return _history; } @@ -371,6 +391,21 @@ public: void reset_kdm_email (); + void set_signer (boost::shared_ptr<const dcp::Signer> s) { + _signer = s; + changed (); + } + + void set_decryption_certificate (dcp::Certificate c) { + _decryption_certificate = c; + changed (); + } + + void set_decryption_private_key (std::string k) { + _decryption_private_key = k; + changed (); + } + void set_check_for_updates (bool c) { _check_for_updates = c; changed (); @@ -391,6 +426,13 @@ public: changed (); } +#ifdef DCPOMATIC_WINDOWS + void set_win32_console (bool c) { + _win32_console = c; + changed (); + } +#endif + void clear_history () { _history.clear (); changed (); @@ -398,8 +440,6 @@ public: void add_to_history (boost::filesystem::path p); - boost::filesystem::path signer_chain_directory () const; - void changed (); boost::signals2::signal<void ()> Changed; @@ -408,10 +448,10 @@ public: private: Config (); - boost::filesystem::path file (bool) const; + boost::filesystem::path file () const; void read (); - void read_old_metadata (); void write () const; + void make_decryption_keys (); /** number of threads to use for J2K encoding on the local machine */ int _num_local_encoding_threads; @@ -433,8 +473,8 @@ private: std::string _tms_user; /** Password to log into the TMS with */ std::string _tms_password; - /** Our sound processor */ - SoundProcessor const * _sound_processor; + /** Our cinema sound processor */ + CinemaSoundProcessor const * _cinema_sound_processor; std::list<int> _allowed_dcp_frame_rates; /** Allow any video frame rate for the DCP; if true, overrides _allowed_dcp_frame_rates */ bool _allow_any_dcp_frame_rate; @@ -458,12 +498,18 @@ private: std::string _kdm_cc; std::string _kdm_bcc; std::string _kdm_email; + boost::shared_ptr<const dcp::Signer> _signer; + dcp::Certificate _decryption_certificate; + std::string _decryption_private_key; /** true to check for updates on startup */ bool _check_for_updates; bool _check_for_test_updates; /** maximum allowed J2K bandwidth in bits per second */ int _maximum_j2k_bandwidth; int _log_types; +#ifdef DCPOMATIC_WINDOWS + bool _win32_console; +#endif std::vector<boost::filesystem::path> _history; /** Singleton instance, or 0 */ diff --git a/src/lib/content.cc b/src/lib/content.cc index 11a4b21cc..21e49a2c9 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -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,10 +17,14 @@ */ +/** @file src/lib/content.cc + * @brief Content class. + */ + #include <boost/thread/mutex.hpp> #include <libxml++/libxml++.h> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "content.h" #include "util.h" #include "content_factory.h" @@ -38,7 +42,7 @@ using std::cout; using std::vector; using std::max; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; int const ContentProperty::PATH = 400; int const ContentProperty::POSITION = 401; @@ -56,7 +60,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) @@ -76,7 +80,7 @@ Content::Content (shared_ptr<const Film> f, boost::filesystem::path p) _paths.push_back (p); } -Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) +Content::Content (shared_ptr<const Film> f, cxml::ConstNodePtr node) : _film (f) , _change_signals_frequent (false) { @@ -85,9 +89,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) @@ -98,11 +102,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.")); } @@ -121,9 +125,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 (raw_convert<string> (_position)); - node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start)); - node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end)); + node->add_child("Position")->add_child_text (raw_convert<string> (_position.get ())); + node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start.get ())); + node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end.get ())); } void @@ -148,7 +152,7 @@ Content::signal_changed (int p) } void -Content::set_position (Time p) +Content::set_position (DCPTime p) { { boost::mutex::scoped_lock lm (_mutex); @@ -163,7 +167,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); @@ -174,7 +178,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); @@ -206,22 +210,13 @@ 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 max (int64_t (0), 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 max (DCPTime (), full_length() - trim_start() - trim_end()); } /** @return string which includes everything about how this content affects @@ -233,9 +228,9 @@ Content::identifier () const SafeStringStream 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..f7e97feac 100644 --- a/src/lib/content.h +++ b/src/lib/content.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,6 +17,10 @@ */ +/** @file src/lib/content.h + * @brief Content class. + */ + #ifndef DCPOMATIC_CONTENT_H #define DCPOMATIC_CONTENT_H @@ -26,7 +30,9 @@ #include <boost/thread/mutex.hpp> #include <boost/enable_shared_from_this.hpp> #include <libxml++/libxml++.h> +#include <libcxml/cxml.h> #include "types.h" +#include "dcpomatic_time.h" namespace cxml { class Node; @@ -45,25 +51,38 @@ public: static int const TRIM_END; }; +/** @class Content + * @brief A piece of content represented by one or more files on disk. + */ class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable { 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>, cxml::ConstNodePtr); Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); virtual ~Content () {} + + /** Examine the content to establish digest, frame rates and any other + * useful metadata. + * @param job Job to use to report progress, or 0. + */ + virtual void examine (boost::shared_ptr<Job> job); - virtual void examine (boost::shared_ptr<Job>); + /** @return Quick one-line summary of the content, as will be presented in the + * film editor. + */ virtual std::string summary () const = 0; + /** @return Technical details of this content; these are written to logs to * help with debugging. */ 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,41 +114,43 @@ 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::shared_ptr<const Film> film () const { + return _film.lock (); + } boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed; @@ -139,8 +160,8 @@ protected: boost::weak_ptr<const Film> _film; /** _mutex which should be used to protect accesses, as examine - jobs can update content state in threads other than the main one. - */ + * jobs can update content state in threads other than the main one. + */ mutable boost::mutex _mutex; /** Paths of our data files */ @@ -148,9 +169,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_audio.h b/src/lib/content_audio.h new file mode 100644 index 000000000..535c0b491 --- /dev/null +++ b/src/lib/content_audio.h @@ -0,0 +1,44 @@ +/* + 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. + +*/ + +/** @file src/lib/content_audio.h + * @brief ContentAudio class. + */ + +#include "audio_buffers.h" + +/** @class ContentAudio + * @brief A block of audio from a piece of content, with a timestamp as a frame within that content. + */ +class ContentAudio +{ +public: + ContentAudio () + : audio (new AudioBuffers (0, 0)) + , frame (0) + {} + + ContentAudio (boost::shared_ptr<AudioBuffers> a, AudioFrame f) + : audio (a) + , frame (f) + {} + + boost::shared_ptr<AudioBuffers> audio; + AudioFrame frame; +}; diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc index 98b1dd859..16340adb4 100644 --- a/src/lib/content_factory.cc +++ b/src/lib/content_factory.cc @@ -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,16 +17,30 @@ */ +/** @file src/lib/content_factory.cc + * @brief Methods to create content objects. + */ + #include <libcxml/cxml.h> #include "ffmpeg_content.h" #include "image_content.h" #include "sndfile_content.h" +#include "subrip_content.h" +#include "dcp_content.h" +#include "dcp_subtitle_content.h" #include "util.h" using std::string; using std::list; using boost::shared_ptr; +/** Create a Content object from an XML node. + * @param film Film that the content will be in. + * @param node XML description. + * @param version XML state version. + * @param notes A list to which is added descriptions of any non-critial warnings / messages. + * @return Content object, or 0 if no content was recognised in the XML. + */ shared_ptr<Content> content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, list<string>& notes) { @@ -40,20 +54,38 @@ 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)); + } else if (type == "DCP") { + content.reset (new DCPContent (film, node, version)); + } else if (type == "DCPSubtitle") { + content.reset (new DCPSubtitleContent (film, node, version)); } return content; } +/** Create a Content object from a file, depending on its extension. + * @param film Film that the content will be in. + * @param path File's path. + * @return Content object. + */ 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 if (ext == ".xml") { + content.reset (new DCPSubtitleContent (film, path)); } else { content.reset (new FFmpegContent (film, path)); } diff --git a/src/lib/content_factory.h b/src/lib/content_factory.h index 2eeebbc9f..fae7648ea 100644 --- a/src/lib/content_factory.h +++ b/src/lib/content_factory.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,6 +17,10 @@ */ +/** @file src/lib/content_factory.h + * @brief Methods to create content objects. + */ + class Film; extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, cxml::NodePtr, int, std::list<std::string> &); diff --git a/src/lib/content_subtitle.cc b/src/lib/content_subtitle.cc new file mode 100644 index 000000000..93e0677bb --- /dev/null +++ b/src/lib/content_subtitle.cc @@ -0,0 +1,31 @@ +/* + 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 "content_subtitle.h" + +ContentTimePeriod +ContentTextSubtitle::period () const +{ + /* XXX: assuming we have some subs and they are all at the same time */ + assert (!subs.empty ()); + return ContentTimePeriod ( + ContentTime::from_seconds (double (subs.front().in().to_ticks()) / 250), + ContentTime::from_seconds (double (subs.front().out().to_ticks()) / 250) + ); +} diff --git a/src/lib/content_subtitle.h b/src/lib/content_subtitle.h new file mode 100644 index 000000000..8868618ad --- /dev/null +++ b/src/lib/content_subtitle.h @@ -0,0 +1,68 @@ +/* + 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_CONTENT_SUBTITLE_H +#define DCPOMATIC_CONTENT_SUBTITLE_H + +#include <list> +#include <dcp/subtitle_string.h> +#include "dcpomatic_time.h" +#include "rect.h" +#include "image_subtitle.h" + +class Image; + +class ContentSubtitle +{ +public: + virtual ContentTimePeriod period () const = 0; +}; + +class ContentImageSubtitle : public ContentSubtitle +{ +public: + ContentImageSubtitle (ContentTimePeriod p, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r) + : sub (im, r) + , _period (p) + {} + + ContentTimePeriod period () const { + return _period; + } + + /* Our subtitle, with its rectangle unmodified by any offsets or scales that the content specifies */ + ImageSubtitle sub; + +private: + ContentTimePeriod _period; +}; + +class ContentTextSubtitle : public ContentSubtitle +{ +public: + ContentTextSubtitle (std::list<dcp::SubtitleString> s) + : subs (s) + {} + + ContentTimePeriod period () const; + + std::list<dcp::SubtitleString> subs; +}; + +#endif diff --git a/src/lib/content_video.h b/src/lib/content_video.h new file mode 100644 index 000000000..a7f73597c --- /dev/null +++ b/src/lib/content_video.h @@ -0,0 +1,48 @@ +/* + 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. + +*/ + +#ifndef DCPOMATIC_CONTENT_VIDEO_H +#define DCPOMATIC_CONTENT_VIDEO_H + +class ImageProxy; + +/** @class ContentVideo + * @brief A frame of video straight out of some content. + */ +class ContentVideo +{ +public: + ContentVideo () + : eyes (EYES_BOTH) + {} + + ContentVideo (boost::shared_ptr<const ImageProxy> i, Eyes e, Part p, VideoFrame f) + : image (i) + , eyes (e) + , part (p) + , frame (f) + {} + + boost::shared_ptr<const ImageProxy> image; + Eyes eyes; + Part part; + VideoFrame frame; +}; + +#endif diff --git a/src/lib/cross.cc b/src/lib/cross.cc index 9b7d5594f..d84c17c55 100644 --- a/src/lib/cross.cc +++ b/src/lib/cross.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 diff --git a/src/lib/cross.h b/src/lib/cross.h index 1c7754503..c206fa55d 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/cross.h + * @brief Cross-platform compatibility code. + */ + #ifndef DCPOMATIC_CROSS_H #define DCPOMATIC_CROSS_H @@ -42,7 +46,9 @@ extern boost::filesystem::path app_contents (); extern FILE * fopen_boost (boost::filesystem::path, std::string); extern int dcpomatic_fseek (FILE *, int64_t, int); -/** A class which tries to keep the computer awake on various operating systems. +/** @class Waker + * @brief A class which tries to keep the computer awake on various operating systems. + * * Create a Waker to prevent sleep, and call ::nudge every so often (every minute or so). * Destroy the Waker to allow sleep again. */ diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc new file mode 100644 index 000000000..a5b5f37e1 --- /dev/null +++ b/src/lib/dcp_content.cc @@ -0,0 +1,162 @@ +/* + 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/dcp.h> +#include <dcp/exceptions.h> +#include "dcp_content.h" +#include "dcp_examiner.h" +#include "job.h" +#include "film.h" +#include "config.h" +#include "compose.hpp" + +#include "i18n.h" + +using std::string; +using std::cout; +using boost::shared_ptr; +using boost::optional; + +int const DCPContentProperty::CAN_BE_PLAYED = 600; + +DCPContent::DCPContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f) + , VideoContent (f) + , SingleStreamAudioContent (f) + , SubtitleContent (f) + , _has_subtitles (false) + , _encrypted (false) + , _directory (p) + , _kdm_valid (false) +{ + read_directory (p); +} + +DCPContent::DCPContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) + : Content (f, node) + , VideoContent (f, node, version) + , SingleStreamAudioContent (f, node, version) + , SubtitleContent (f, node, version) +{ + _name = node->string_child ("Name"); + _has_subtitles = node->bool_child ("HasSubtitles"); + _directory = node->string_child ("Directory"); + _encrypted = node->bool_child ("Encrypted"); + if (node->optional_node_child ("KDM")) { + _kdm = dcp::EncryptedKDM (node->string_child ("KDM")); + } + _kdm_valid = node->bool_child ("KDMValid"); +} + +void +DCPContent::read_directory (boost::filesystem::path p) +{ + for (boost::filesystem::directory_iterator i(p); i != boost::filesystem::directory_iterator(); ++i) { + if (boost::filesystem::is_regular_file (i->path ())) { + _paths.push_back (i->path ()); + } else if (boost::filesystem::is_directory (i->path ())) { + read_directory (i->path ()); + } + } +} + +void +DCPContent::examine (shared_ptr<Job> job) +{ + bool const could_be_played = can_be_played (); + + job->set_progress_unknown (); + Content::examine (job); + + shared_ptr<DCPExaminer> examiner (new DCPExaminer (shared_from_this ())); + take_from_video_examiner (examiner); + take_from_audio_examiner (examiner); + + boost::mutex::scoped_lock lm (_mutex); + _name = examiner->name (); + _has_subtitles = examiner->has_subtitles (); + _encrypted = examiner->encrypted (); + _kdm_valid = examiner->kdm_valid (); + + if (could_be_played != can_be_played ()) { + signal_changed (DCPContentProperty::CAN_BE_PLAYED); + } +} + +string +DCPContent::summary () const +{ + boost::mutex::scoped_lock lm (_mutex); + return String::compose (_("%1 [DCP]"), _name); +} + +string +DCPContent::technical_summary () const +{ + return Content::technical_summary() + " - " + + VideoContent::technical_summary() + " - " + + AudioContent::technical_summary() + " - "; +} + +void +DCPContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("DCP"); + + Content::as_xml (node); + VideoContent::as_xml (node); + SingleStreamAudioContent::as_xml (node); + SubtitleContent::as_xml (node); + + boost::mutex::scoped_lock lm (_mutex); + node->add_child("Name")->add_child_text (_name); + node->add_child("HasSubtitles")->add_child_text (_has_subtitles ? "1" : "0"); + node->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); + node->add_child("Directory")->add_child_text (_directory.string ()); + if (_kdm) { + node->add_child("KDM")->add_child_text (_kdm->as_xml ()); + } + node->add_child("KDMValid")->add_child_text (_kdm_valid ? "1" : "0"); +} + +DCPTime +DCPContent::full_length () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + return DCPTime (video_length (), FrameRateChange (video_frame_rate (), film->video_frame_rate ())); +} + +string +DCPContent::identifier () const +{ + return SubtitleContent::identifier (); +} + +void +DCPContent::add_kdm (dcp::EncryptedKDM k) +{ + _kdm = k; +} + +bool +DCPContent::can_be_played () const +{ + return !_encrypted || _kdm_valid; +} diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h new file mode 100644 index 000000000..da78e6d72 --- /dev/null +++ b/src/lib/dcp_content.h @@ -0,0 +1,98 @@ +/* + 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_DCP_CONTENT_H +#define DCPOMATIC_DCP_CONTENT_H + +/** @file src/lib/dcp_content.h + * @brief DCPContent class. + */ + +#include <libcxml/cxml.h> +#include <dcp/encrypted_kdm.h> +#include <dcp/decrypted_kdm.h> +#include "video_content.h" +#include "single_stream_audio_content.h" +#include "subtitle_content.h" + +class DCPContentProperty +{ +public: + static int const CAN_BE_PLAYED; +}; + +/** @class DCPContent + * @brief An existing DCP used as input. + */ +class DCPContent : public VideoContent, public SingleStreamAudioContent, public SubtitleContent +{ +public: + DCPContent (boost::shared_ptr<const Film> f, boost::filesystem::path p); + DCPContent (boost::shared_ptr<const Film> f, cxml::ConstNodePtr, int version); + + boost::shared_ptr<DCPContent> shared_from_this () { + return boost::dynamic_pointer_cast<DCPContent> (Content::shared_from_this ()); + } + + DCPTime full_length () const; + + void examine (boost::shared_ptr<Job>); + std::string summary () const; + std::string technical_summary () const; + void as_xml (xmlpp::Node *) const; + std::string identifier () const; + + /* SubtitleContent */ + bool has_subtitles () const { + boost::mutex::scoped_lock lm (_mutex); + return _has_subtitles; + } + + boost::filesystem::path directory () const { + boost::mutex::scoped_lock lm (_mutex); + return _directory; + } + + bool encrypted () const { + boost::mutex::scoped_lock lm (_mutex); + return _encrypted; + } + + void add_kdm (dcp::EncryptedKDM); + + boost::optional<dcp::EncryptedKDM> kdm () const { + return _kdm; + } + + bool can_be_played () const; + +private: + void read_directory (boost::filesystem::path); + + std::string _name; + bool _has_subtitles; + /** true if our DCP is encrypted */ + bool _encrypted; + boost::filesystem::path _directory; + boost::optional<dcp::EncryptedKDM> _kdm; + /** true if _kdm successfully decrypts the first frame of our DCP */ + bool _kdm_valid; +}; + +#endif diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc index f24ed95ea..e5466e139 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) , _isdcf_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 88f3c4a85..ebfe09518 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 _isdcf_name; /** All available DCP content types */ diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc new file mode 100644 index 000000000..bf016ef87 --- /dev/null +++ b/src/lib/dcp_decoder.cc @@ -0,0 +1,140 @@ +/* + 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/dcp.h> +#include <dcp/cpl.h> +#include <dcp/reel.h> +#include <dcp/mono_picture_mxf.h> +#include <dcp/stereo_picture_mxf.h> +#include <dcp/reel_picture_asset.h> +#include <dcp/reel_sound_asset.h> +#include <dcp/mono_picture_frame.h> +#include <dcp/stereo_picture_frame.h> +#include <dcp/sound_frame.h> +#include "dcp_decoder.h" +#include "dcp_content.h" +#include "j2k_image_proxy.h" +#include "image.h" +#include "config.h" + +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c, shared_ptr<Log> log) + : VideoDecoder (c) + , AudioDecoder (c) + , SubtitleDecoder (c) + , _log (log) + , _dcp_content (c) +{ + dcp::DCP dcp (c->directory ()); + dcp.read (); + if (c->kdm ()) { + dcp.add (dcp::DecryptedKDM (c->kdm().get (), Config::instance()->decryption_private_key ())); + } + assert (dcp.cpls().size() == 1); + _reels = dcp.cpls().front()->reels (); + _reel = _reels.begin (); +} + +bool +DCPDecoder::pass () +{ + if (_reel == _reels.end () || !_dcp_content->can_be_played ()) { + return true; + } + + float const vfr = _dcp_content->video_frame_rate (); + int64_t const frame = _next.frames (vfr); + + if ((*_reel)->main_picture ()) { + shared_ptr<dcp::PictureMXF> mxf = (*_reel)->main_picture()->mxf (); + shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (mxf); + shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (mxf); + int64_t const entry_point = (*_reel)->main_picture()->entry_point (); + if (mono) { + video (shared_ptr<ImageProxy> (new J2KImageProxy (mono->get_frame (entry_point + frame), mxf->size(), _log)), frame); + } else { + video ( + shared_ptr<ImageProxy> (new J2KImageProxy (stereo->get_frame (entry_point + frame), mxf->size(), dcp::EYE_LEFT, _log)), + frame + ); + + video ( + shared_ptr<ImageProxy> (new J2KImageProxy (stereo->get_frame (entry_point + frame), mxf->size(), dcp::EYE_RIGHT, _log)), + frame + ); + } + } + + if ((*_reel)->main_sound ()) { + int64_t const entry_point = (*_reel)->main_sound()->entry_point (); + shared_ptr<const dcp::SoundFrame> sf = (*_reel)->main_sound()->mxf()->get_frame (entry_point + frame); + uint8_t const * from = sf->data (); + + int const channels = _dcp_content->audio_channels (); + int const frames = sf->size() / (3 * channels); + shared_ptr<AudioBuffers> data (new AudioBuffers (channels, frames)); + for (int i = 0; i < frames; ++i) { + for (int j = 0; j < channels; ++j) { + data->data()[j][i] = float (from[0] | (from[1] << 8) | (from[2] << 16)) / (1 << 23); + from += 3; + } + } + + audio (data, _next); + } + + /* XXX: subtitle */ + + _next += ContentTime::from_frames (1, vfr); + + if ((*_reel)->main_picture ()) { + if (_next.frames (vfr) >= (*_reel)->main_picture()->duration()) { + ++_reel; + } + } + + return false; +} + +void +DCPDecoder::seek (ContentTime t, bool accurate) +{ + VideoDecoder::seek (t, accurate); + AudioDecoder::seek (t, accurate); + SubtitleDecoder::seek (t, accurate); + + _reel = _reels.begin (); + while (_reel != _reels.end() && t >= ContentTime::from_frames ((*_reel)->main_picture()->duration(), _dcp_content->video_frame_rate ())) { + t -= ContentTime::from_frames ((*_reel)->main_picture()->duration(), _dcp_content->video_frame_rate ()); + ++_reel; + } + + _next = t; +} + + +list<ContentTimePeriod> +DCPDecoder::subtitles_during (ContentTimePeriod, bool starting) const +{ + return list<ContentTimePeriod> (); +} diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h new file mode 100644 index 000000000..d81b20b5c --- /dev/null +++ b/src/lib/dcp_decoder.h @@ -0,0 +1,46 @@ +/* + 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 "video_decoder.h" +#include "audio_decoder.h" +#include "subtitle_decoder.h" + +namespace dcp { + class Reel; +} + +class DCPContent; +class Log; + +class DCPDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder +{ +public: + DCPDecoder (boost::shared_ptr<const DCPContent>, boost::shared_ptr<Log>); + +private: + void seek (ContentTime t, bool accurate); + bool pass (); + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + ContentTime _next; + std::list<boost::shared_ptr<dcp::Reel> > _reels; + std::list<boost::shared_ptr<dcp::Reel> >::iterator _reel; + boost::shared_ptr<Log> _log; + boost::shared_ptr<const DCPContent> _dcp_content; +}; diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc new file mode 100644 index 000000000..1e4cc899d --- /dev/null +++ b/src/lib/dcp_examiner.cc @@ -0,0 +1,136 @@ +/* + 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/dcp.h> +#include <dcp/cpl.h> +#include <dcp/reel.h> +#include <dcp/reel_picture_asset.h> +#include <dcp/reel_sound_asset.h> +#include <dcp/mono_picture_mxf.h> +#include <dcp/mono_picture_frame.h> +#include <dcp/stereo_picture_mxf.h> +#include <dcp/stereo_picture_frame.h> +#include <dcp/sound_mxf.h> +#include "dcp_examiner.h" +#include "dcp_content.h" +#include "exceptions.h" +#include "image.h" +#include "config.h" + +#include "i18n.h" + +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content) + : _video_length (0) + , _audio_length (0) + , _has_subtitles (false) + , _encrypted (false) + , _kdm_valid (false) +{ + dcp::DCP dcp (content->directory ()); + dcp.read (); + + if (content->kdm ()) { + dcp.add (dcp::DecryptedKDM (content->kdm().get(), Config::instance()->decryption_private_key ())); + } + + if (dcp.cpls().size() == 0) { + throw DCPError ("No CPLs found in DCP"); + } else if (dcp.cpls().size() > 1) { + throw DCPError ("Multiple CPLs found in DCP"); + } + + _name = dcp.cpls().front()->content_title_text (); + + list<shared_ptr<dcp::Reel> > reels = dcp.cpls().front()->reels (); + for (list<shared_ptr<dcp::Reel> >::const_iterator i = reels.begin(); i != reels.end(); ++i) { + + if ((*i)->main_picture ()) { + dcp::Fraction const frac = (*i)->main_picture()->frame_rate (); + float const fr = float(frac.numerator) / frac.denominator; + if (!_video_frame_rate) { + _video_frame_rate = fr; + } else if (_video_frame_rate.get() != fr) { + throw DCPError (_("Mismatched frame rates in DCP")); + } + + shared_ptr<dcp::PictureMXF> mxf = (*i)->main_picture()->mxf (); + if (!_video_size) { + _video_size = mxf->size (); + } else if (_video_size.get() != mxf->size ()) { + throw DCPError (_("Mismatched video sizes in DCP")); + } + + _video_length += ContentTime::from_frames ((*i)->main_picture()->duration(), _video_frame_rate.get ()); + } + + if ((*i)->main_sound ()) { + shared_ptr<dcp::SoundMXF> mxf = (*i)->main_sound()->mxf (); + + if (!_audio_channels) { + _audio_channels = mxf->channels (); + } else if (_audio_channels.get() != mxf->channels ()) { + throw DCPError (_("Mismatched audio channel counts in DCP")); + } + + if (!_audio_frame_rate) { + _audio_frame_rate = mxf->sampling_rate (); + } else if (_audio_frame_rate.get() != mxf->sampling_rate ()) { + throw DCPError (_("Mismatched audio frame rates in DCP")); + } + + _audio_length += ContentTime::from_frames ((*i)->main_sound()->duration(), _video_frame_rate.get ()); + } + + if ((*i)->main_subtitle ()) { + _has_subtitles = true; + } + } + + _encrypted = dcp.encrypted (); + _kdm_valid = true; + + /* Check that we can read the first picture frame */ + try { + if (!dcp.cpls().empty () && !dcp.cpls().front()->reels().empty ()) { + shared_ptr<dcp::PictureMXF> mxf = dcp.cpls().front()->reels().front()->main_picture()->mxf (); + shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (mxf); + shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (mxf); + + shared_ptr<Image> image (new Image (PIX_FMT_RGB24, _video_size.get(), false)); + + if (mono) { + mono->get_frame(0)->rgb_frame (image->data()[0]); + } else { + stereo->get_frame(0)->rgb_frame (dcp::EYE_LEFT, image->data()[0]); + } + + } + } catch (dcp::DCPReadError& e) { + _kdm_valid = false; + if (_encrypted && content->kdm ()) { + /* XXX: maybe don't use an exception for this */ + throw StringError (_("The KDM does not decrypt the DCP. Perhaps it is targeted at the wrong CPL")); + } + } +} diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h new file mode 100644 index 000000000..03d43d0f6 --- /dev/null +++ b/src/lib/dcp_examiner.h @@ -0,0 +1,81 @@ +/* + 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 "video_examiner.h" +#include "audio_examiner.h" + +class DCPContent; + +class DCPExaminer : public VideoExaminer, public AudioExaminer +{ +public: + DCPExaminer (boost::shared_ptr<const DCPContent>); + + float video_frame_rate () const { + return _video_frame_rate.get_value_or (24); + } + + dcp::Size video_size () const { + return _video_size.get_value_or (dcp::Size (1998, 1080)); + } + + ContentTime video_length () const { + return _video_length; + } + + std::string name () const { + return _name; + } + + bool has_subtitles () const { + return _has_subtitles; + } + + bool encrypted () const { + return _encrypted; + } + + int audio_channels () const { + return _audio_channels.get_value_or (0); + } + + ContentTime audio_length () const { + return _audio_length; + } + + int audio_frame_rate () const { + return _audio_frame_rate.get_value_or (48000); + } + + bool kdm_valid () const { + return _kdm_valid; + } + +private: + boost::optional<float> _video_frame_rate; + boost::optional<dcp::Size> _video_size; + ContentTime _video_length; + boost::optional<int> _audio_channels; + boost::optional<int> _audio_frame_rate; + ContentTime _audio_length; + std::string _name; + bool _has_subtitles; + bool _encrypted; + bool _kdm_valid; +}; diff --git a/src/lib/dcp_subtitle_content.cc b/src/lib/dcp_subtitle_content.cc new file mode 100644 index 000000000..83b0d200c --- /dev/null +++ b/src/lib/dcp_subtitle_content.cc @@ -0,0 +1,88 @@ +/* + 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_content.h> +#include <dcp/raw_convert.h> +#include "dcp_subtitle_content.h" + +#include "i18n.h" + +using std::string; +using std::list; +using boost::shared_ptr; +using dcp::raw_convert; + +DCPSubtitleContent::DCPSubtitleContent (shared_ptr<const Film> film, boost::filesystem::path path) + : Content (film, path) + , SubtitleContent (film, path) +{ + +} + +DCPSubtitleContent::DCPSubtitleContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version) + : Content (film, node) + , SubtitleContent (film, node, version) + , _length (node->number_child<DCPTime::Type> ("Length")) +{ + +} + +void +DCPSubtitleContent::examine (shared_ptr<Job> job) +{ + Content::examine (job); + dcp::SubtitleContent sc (path (0), false); + _length = DCPTime::from_seconds (sc.latest_subtitle_out().to_seconds ()); +} + +DCPTime +DCPSubtitleContent::full_length () const +{ + /* XXX: this assumes that the timing of the subtitle file is appropriate + for the DCP's frame rate. + */ + return _length; +} + +string +DCPSubtitleContent::summary () const +{ + return path_summary() + " " + _("[subtitles]"); +} + +string +DCPSubtitleContent::technical_summary () const +{ + return Content::technical_summary() + " - " + _("DCP XML subtitles"); +} + +string +DCPSubtitleContent::information () const +{ + +} + +void +DCPSubtitleContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("DCPSubtitle"); + Content::as_xml (node); + SubtitleContent::as_xml (node); + node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ())); +} diff --git a/src/lib/dcp_subtitle_content.h b/src/lib/dcp_subtitle_content.h new file mode 100644 index 000000000..5794b5951 --- /dev/null +++ b/src/lib/dcp_subtitle_content.h @@ -0,0 +1,43 @@ +/* + 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 DCPSubtitleContent : public SubtitleContent +{ +public: + DCPSubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path); + DCPSubtitleContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int); + + /* Content */ + 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; + + /* SubtitleContent */ + bool has_subtitles () const { + return true; + } + +private: + DCPTime _length; +}; diff --git a/src/lib/dcp_subtitle_decoder.cc b/src/lib/dcp_subtitle_decoder.cc new file mode 100644 index 000000000..20a9f32fe --- /dev/null +++ b/src/lib/dcp_subtitle_decoder.cc @@ -0,0 +1,83 @@ +/* + 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_content.h> +#include "dcp_subtitle_decoder.h" +#include "dcp_subtitle_content.h" + +using std::list; +using std::cout; +using boost::shared_ptr; + +DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr<const DCPSubtitleContent> content) + : SubtitleDecoder (content) +{ + dcp::SubtitleContent c (content->path (0), false); + _subtitles = c.subtitles (); + _next = _subtitles.begin (); +} + +void +DCPSubtitleDecoder::seek (ContentTime time, bool accurate) +{ + SubtitleDecoder::seek (time, accurate); + + _next = _subtitles.begin (); + list<dcp::SubtitleString>::const_iterator i = _subtitles.begin (); + while (i != _subtitles.end() && ContentTime::from_seconds (_next->in().to_seconds()) < time) { + ++i; + } +} + +bool +DCPSubtitleDecoder::pass () +{ + if (_next == _subtitles.end ()) { + return true; + } + + list<dcp::SubtitleString> s; + s.push_back (*_next); + text_subtitle (s); + ++_next; + + return false; +} + +list<ContentTimePeriod> +DCPSubtitleDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +{ + /* XXX: inefficient */ + + list<ContentTimePeriod> d; + + for (list<dcp::SubtitleString>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + ContentTimePeriod period ( + ContentTime::from_seconds (i->in().to_seconds ()), + ContentTime::from_seconds (i->out().to_seconds ()) + ); + + if ((starting && p.contains (period.from)) || (!starting && p.overlaps (period))) { + d.push_back (period); + } + } + + return d; +} + diff --git a/src/lib/dcp_subtitle_decoder.h b/src/lib/dcp_subtitle_decoder.h new file mode 100644 index 000000000..070562458 --- /dev/null +++ b/src/lib/dcp_subtitle_decoder.h @@ -0,0 +1,38 @@ +/* + 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_decoder.h" + +class DCPSubtitleContent; + +class DCPSubtitleDecoder : public SubtitleDecoder +{ +public: + DCPSubtitleDecoder (boost::shared_ptr<const DCPSubtitleContent>); + +protected: + void seek (ContentTime time, bool accurate); + bool pass (); + +private: + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + std::list<dcp::SubtitleString> _subtitles; + std::list<dcp::SubtitleString>::const_iterator _next; +}; diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video.cc index bb7eaa064..ccfc800c8 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video.cc @@ -41,16 +41,15 @@ #include <boost/array.hpp> #include <boost/asio.hpp> #include <boost/filesystem.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 <libdcp/raw_convert.h> +#include <boost/lexical_cast.hpp> +#include <dcp/gamma_lut.h> +#include <dcp/xyz_frame.h> +#include <dcp/rgb_xyz.h> +#include <dcp/colour_matrix.h> +#include <dcp/raw_convert.h> #include <libcxml/cxml.h> #include "film.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "config.h" #include "exceptions.h" #include "server.h" @@ -59,7 +58,8 @@ #include "image.h" #include "log.h" #include "cross.h" -#include "player_video_frame.h" +#include "player_video.h" +#include "encoded_data.h" #define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); @@ -68,8 +68,9 @@ using std::string; using std::cout; using boost::shared_ptr; -using libdcp::Size; -using libdcp::raw_convert; +using boost::lexical_cast; +using dcp::Size; +using dcp::raw_convert; #define DCI_COEFFICENT (48.0 / 52.37) @@ -79,20 +80,21 @@ using libdcp::raw_convert; * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ()) * @param l Log to write to. */ -DCPVideoFrame::DCPVideoFrame ( - shared_ptr<const PlayerVideoFrame> frame, int index, int dcp_fps, int bw, Resolution r, shared_ptr<Log> l +DCPVideo::DCPVideo ( + shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int bw, Resolution r, bool b, shared_ptr<Log> l ) : _frame (frame) , _index (index) , _frames_per_second (dcp_fps) , _j2k_bandwidth (bw) , _resolution (r) + , _burn_subtitles (b) , _log (l) { } -DCPVideoFrame::DCPVideoFrame (shared_ptr<const PlayerVideoFrame> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log) +DCPVideo::DCPVideo (shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log) : _frame (frame) , _log (log) { @@ -100,21 +102,19 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const PlayerVideoFrame> frame, shared_p _frames_per_second = node->number_child<int> ("FramesPerSecond"); _j2k_bandwidth = node->number_child<int> ("J2KBandwidth"); _resolution = Resolution (node->optional_number_child<int>("Resolution").get_value_or (RESOLUTION_2K)); + _burn_subtitles = node->bool_child ("BurnSubtitles"); } /** J2K-encode this frame on the local host. * @return Encoded data. */ shared_ptr<EncodedData> -DCPVideoFrame::encode_locally () +DCPVideo::encode_locally () { - shared_ptr<libdcp::LUT> in_lut; - if (_frame->colour_conversion().input_gamma_linearised) { - in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _frame->colour_conversion().input_gamma); - } else { - in_lut = libdcp::GammaLUT::cache.get (12, _frame->colour_conversion().input_gamma); - } - + shared_ptr<dcp::GammaLUT> in_lut = dcp::GammaLUT::cache.get ( + 12, _frame->colour_conversion().input_gamma, _frame->colour_conversion().input_gamma_linearised + ); + /* XXX: libdcp should probably use boost */ double matrix[3][3]; @@ -124,10 +124,10 @@ DCPVideoFrame::encode_locally () } } - shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz ( - _frame->image(), + shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz ( + _frame->image (_burn_subtitles), in_lut, - libdcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma), + dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false), matrix ); @@ -260,7 +260,7 @@ DCPVideoFrame::encode_locally () * @return Encoded data. */ shared_ptr<EncodedData> -DCPVideoFrame::encode_remotely (ServerDescription serv) +DCPVideo::encode_remotely (ServerDescription serv) { boost::asio::io_service io_service; boost::asio::ip::tcp::resolver resolver (io_service); @@ -285,7 +285,7 @@ DCPVideoFrame::encode_remotely (ServerDescription serv) socket->write ((uint8_t *) xml.c_str(), xml.length() + 1); /* Send binary data */ - _frame->send_binary (socket); + _frame->send_binary (socket, _burn_subtitles); /* Read the response (JPEG2000-encoded data); this blocks until the data is ready and sent back. @@ -299,105 +299,34 @@ DCPVideoFrame::encode_remotely (ServerDescription serv) } void -DCPVideoFrame::add_metadata (xmlpp::Element* el) const +DCPVideo::add_metadata (xmlpp::Element* el) const { el->add_child("Index")->add_child_text (raw_convert<string> (_index)); el->add_child("FramesPerSecond")->add_child_text (raw_convert<string> (_frames_per_second)); el->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth)); el->add_child("Resolution")->add_child_text (raw_convert<string> (int (_resolution))); - _frame->add_metadata (el); + el->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0"); + _frame->add_metadata (el, _burn_subtitles); } Eyes -DCPVideoFrame::eyes () const +DCPVideo::eyes () const { return _frame->eyes (); } -EncodedData::EncodedData (int s) - : _data (new uint8_t[s]) - , _size (s) -{ - -} - -EncodedData::EncodedData (boost::filesystem::path file) -{ - _size = boost::filesystem::file_size (file); - _data = new uint8_t[_size]; - - FILE* f = fopen_boost (file, "rb"); - if (!f) { - throw FileError (_("could not open file for reading"), file); - } - - size_t const r = fread (_data, 1, _size, f); - if (r != size_t (_size)) { - fclose (f); - throw FileError (_("could not read encoded data"), file); - } - - fclose (f); -} - - -EncodedData::~EncodedData () -{ - delete[] _data; -} - -/** Write this data to a J2K file. - * @param Film Film. - * @param frame DCP frame index. +/** @return true if this DCPVideo is definitely the same as another; + * (apart from the frame index), false if it is probably not. */ -void -EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const +bool +DCPVideo::same (shared_ptr<const DCPVideo> other) const { - boost::filesystem::path const tmp_j2c = film->j2c_path (frame, eyes, true); - - FILE* f = fopen_boost (tmp_j2c, "wb"); - - if (!f) { - throw WriteFileError (tmp_j2c, errno); + if (_frames_per_second != other->_frames_per_second || + _j2k_bandwidth != other->_j2k_bandwidth || + _resolution != other->_resolution || + _burn_subtitles != other->_burn_subtitles) { + return false; } - fwrite (_data, 1, _size, f); - fclose (f); - - boost::filesystem::path const real_j2c = film->j2c_path (frame, eyes, false); - - /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */ - boost::filesystem::rename (tmp_j2c, real_j2c); -} - -void -EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const -{ - boost::filesystem::path const info = film->info_path (frame, eyes); - FILE* h = fopen_boost (info, "w"); - fin.write (h); - fclose (h); -} - -/** Send this data to a socket. - * @param socket Socket - */ -void -EncodedData::send (shared_ptr<Socket> socket) -{ - socket->write (_size); - socket->write (_data, _size); -} - -LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s) - : EncodedData (s) -{ - memcpy (_data, d, s); -} - -/** @param s Size of data in bytes */ -RemotelyEncodedData::RemotelyEncodedData (int s) - : EncodedData (s) -{ - + return _frame->same (other->_frame); } diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h new file mode 100644 index 000000000..d517a8f02 --- /dev/null +++ b/src/lib/dcp_video.h @@ -0,0 +1,75 @@ +/* + 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 + 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/picture_mxf_writer.h> +#include "util.h" + +/** @file src/dcp_video_frame.h + * @brief A single frame of video destined for a DCP. + */ + +class Film; +class ServerDescription; +class Scaler; +class Image; +class Log; +class Subtitle; +class PlayerVideo; +class EncodedData; + +/** @class DCPVideo + * @brief A single frame of video destined for a DCP. + * + * Given an Image and some settings, this class knows how to encode + * the image to J2K either on the local host or on a remote server. + * + * Objects of this class are used for the queue that we keep + * of images that require encoding. + */ +class DCPVideo : public boost::noncopyable +{ +public: + DCPVideo (boost::shared_ptr<const PlayerVideo>, int, int, int, Resolution, bool b, boost::shared_ptr<Log>); + DCPVideo (boost::shared_ptr<const PlayerVideo>, cxml::ConstNodePtr, boost::shared_ptr<Log>); + + boost::shared_ptr<EncodedData> encode_locally (); + boost::shared_ptr<EncodedData> encode_remotely (ServerDescription); + + int index () const { + return _index; + } + + Eyes eyes () const; + + bool same (boost::shared_ptr<const DCPVideo> other) const; + +private: + + void add_metadata (xmlpp::Element *) const; + + boost::shared_ptr<const PlayerVideo> _frame; + int _index; ///< frame index within the DCP's intrinsic duration + int _frames_per_second; ///< Frames per second that we will use for the DCP + int _j2k_bandwidth; ///< J2K bandwidth to use + Resolution _resolution; ///< Resolution (2K or 4K) + bool _burn_subtitles; ///< true to burn subtitles into the image + + boost::shared_ptr<Log> _log; ///< log +}; diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc new file mode 100644 index 000000000..812c756ec --- /dev/null +++ b/src/lib/dcpomatic_time.cc @@ -0,0 +1,63 @@ +/* + 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; +} + +bool +ContentTimePeriod::overlaps (ContentTimePeriod const & other) const +{ + return (from < other.to && to >= other.from); +} + +bool +ContentTimePeriod::contains (ContentTime const & other) const +{ + return (from <= other && other < to); +} diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h new file mode 100644 index 000000000..55476d5b5 --- /dev/null +++ b/src/lib/dcpomatic_time.h @@ -0,0 +1,299 @@ +/* + 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 <sstream> +#include <iomanip> +#include <stdint.h> +#include "frame_rate_change.h" +#include "safe_stringstream.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) + {} + + typedef int64_t Type; + + explicit Time (Type t) + : _t (t) + {} + + virtual ~Time () {} + + Type 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); + } + + template <typename T> + void split (T r, int& h, int& m, int& s, int& f) const + { + /* Do this calculation with frames so that we can round + to a frame boundary at the start rather than the end. + */ + int64_t ff = frames (r); + + h = ff / (3600 * r); + ff -= h * 3600 * r; + m = ff / (60 * r); + ff -= m * 60 * r; + s = ff / r; + ff -= s * r; + + f = static_cast<int> (ff); + } + + template <typename T> + std::string timecode (T r) const { + int h; + int m; + int s; + int f; + split (r, h, m, s, f); + + SafeStringStream o; + o.width (2); + o.fill ('0'); + o << std::setw(2) << std::setfill('0') << h << ":" + << std::setw(2) << std::setfill('0') << m << ":" + << std::setw(2) << std::setfill('0') << s << ":" + << std::setw(2) << std::setfill('0') << f; + return o.str (); + } + +protected: + friend struct dcptime_round_up_test; + + Type _t; + static const int HZ = 96000; +}; + +class DCPTime; + +class ContentTime : public Time +{ +public: + ContentTime () : Time () {} + explicit ContentTime (Type t) : Time (t) {} + ContentTime (Type n, Type 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 (float r) { + Type const n = rint (HZ / r); + Type 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) { + assert (r > 0); + return ContentTime (f * HZ / r); + } + + static ContentTime max () { + return ContentTime (INT64_MAX); + } +}; + +std::ostream& operator<< (std::ostream& s, ContentTime t); + +class ContentTimePeriod +{ +public: + ContentTimePeriod () {} + ContentTimePeriod (ContentTime f, ContentTime t) + : from (f) + , to (t) + {} + + ContentTime from; + ContentTime to; + + ContentTimePeriod operator+ (ContentTime const & o) const { + return ContentTimePeriod (from + o, to + o); + } + + bool overlaps (ContentTimePeriod const & o) const; + bool contains (ContentTime const & o) const; +}; + +class DCPTime : public Time +{ +public: + DCPTime () : Time () {} + explicit DCPTime (Type 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- () const { + return DCPTime (-_t); + } + + 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 (float r) { + Type const n = rint (HZ / r); + Type 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) { + assert (r > 0); + 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/decoder.h b/src/lib/decoder.h index d67592ed8..583a92636 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -18,7 +18,7 @@ */ /** @file src/decoder.h - * @brief Parent class for decoders of content. + * @brief Decoder class. */ #ifndef DCPOMATIC_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,19 @@ class Film; class Decoder : public boost::noncopyable { public: - Decoder (boost::shared_ptr<const Film>); virtual ~Decoder () {} - /** Perform one decode pass of the content, which may or may not - * cause the object to emit some data. +protected: + /** Seek so that the next pass() will yield the next thing + * (video/sound frame, subtitle etc.) at or after the requested + * time. Pass accurate = true to try harder to ensure that, at worst, + * the next thing we yield comes before `time'. This may entail + * seeking some way before `time' to be on the safe side. + * Alternatively, if seeking is 100% accurate for this decoder, + * it may seek to just the right spot. */ - virtual void pass () = 0; - virtual bool done () const = 0; - -protected: - - virtual void flush () {}; - - /** The Film that we are decoding in */ - boost::weak_ptr<const Film> _film; + virtual void seek (ContentTime time, bool accurate) = 0; + virtual bool pass () = 0; }; #endif diff --git a/src/lib/dolby_cp750.cc b/src/lib/dolby_cp750.cc index aeb469d29..317d129d9 100644 --- a/src/lib/dolby_cp750.cc +++ b/src/lib/dolby_cp750.cc @@ -24,7 +24,7 @@ using namespace std; DolbyCP750::DolbyCP750 () - : SoundProcessor ("dolby_cp750", _("Dolby CP650 and CP750")) + : CinemaSoundProcessor ("dolby_cp750", _("Dolby CP650 and CP750")) { } diff --git a/src/lib/dolby_cp750.h b/src/lib/dolby_cp750.h index b6c0e7df2..c545844fe 100644 --- a/src/lib/dolby_cp750.h +++ b/src/lib/dolby_cp750.h @@ -17,9 +17,9 @@ */ -#include "sound_processor.h" +#include "cinema_sound_processor.h" -class DolbyCP750 : public SoundProcessor +class DolbyCP750 : public CinemaSoundProcessor { public: DolbyCP750 (); diff --git a/src/lib/encoded_data.cc b/src/lib/encoded_data.cc new file mode 100644 index 000000000..61d2644da --- /dev/null +++ b/src/lib/encoded_data.cc @@ -0,0 +1,122 @@ +/* + 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 "encoded_data.h" +#include "cross.h" +#include "exceptions.h" +#include "film.h" + +#include "i18n.h" + +using boost::shared_ptr; + +EncodedData::EncodedData (int s) + : _data (new uint8_t[s]) + , _size (s) +{ + +} + +EncodedData::EncodedData (uint8_t const * d, int s) + : _data (new uint8_t[s]) + , _size (s) +{ + memcpy (_data, d, s); +} + +EncodedData::EncodedData (boost::filesystem::path file) +{ + _size = boost::filesystem::file_size (file); + _data = new uint8_t[_size]; + + FILE* f = fopen_boost (file, "rb"); + if (!f) { + throw FileError (_("could not open file for reading"), file); + } + + size_t const r = fread (_data, 1, _size, f); + if (r != size_t (_size)) { + fclose (f); + throw FileError (_("could not read encoded data"), file); + } + + fclose (f); +} + + +EncodedData::~EncodedData () +{ + delete[] _data; +} + +/** Write this data to a J2K file. + * @param Film Film. + * @param frame DCP frame index. + */ +void +EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const +{ + boost::filesystem::path const tmp_j2c = film->j2c_path (frame, eyes, true); + + FILE* f = fopen_boost (tmp_j2c, "wb"); + + if (!f) { + throw WriteFileError (tmp_j2c, errno); + } + + fwrite (_data, 1, _size, f); + fclose (f); + + boost::filesystem::path const real_j2c = film->j2c_path (frame, eyes, false); + + /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */ + boost::filesystem::rename (tmp_j2c, real_j2c); +} + +void +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"); + fin.write (h); + fclose (h); +} + +/** Send this data to a socket. + * @param socket Socket + */ +void +EncodedData::send (shared_ptr<Socket> socket) +{ + socket->write (_size); + socket->write (_data, _size); +} + +LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s) + : EncodedData (s) +{ + memcpy (_data, d, s); +} + +/** @param s Size of data in bytes */ +RemotelyEncodedData::RemotelyEncodedData (int s) + : EncodedData (s) +{ + +} diff --git a/src/lib/dcp_video_frame.h b/src/lib/encoded_data.h index e4006d986..232ed6e8a 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/encoded_data.h @@ -1,6 +1,5 @@ /* 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 it under the terms of the GNU General Public License as published by @@ -18,22 +17,13 @@ */ -#include <openjpeg.h> -#include <libdcp/picture_asset.h> -#include <libdcp/picture_asset_writer.h> -#include "util.h" - -/** @file src/dcp_video_frame.h - * @brief A single frame of video destined for a DCP. - */ +#include <boost/noncopyable.hpp> +#include <boost/filesystem.hpp> +#include <dcp/picture_mxf_writer.h> +#include "types.h" +class Socket; class Film; -class ServerDescription; -class Scaler; -class Image; -class Log; -class Subtitle; -class PlayerVideoFrame; /** @class EncodedData * @brief Container for J2K-encoded data. @@ -43,6 +33,7 @@ class EncodedData : public boost::noncopyable public: /** @param s Size of data, in bytes */ EncodedData (int s); + EncodedData (uint8_t const * d, int s); EncodedData (boost::filesystem::path); @@ -50,7 +41,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 { @@ -90,40 +81,3 @@ class RemotelyEncodedData : public EncodedData public: RemotelyEncodedData (int s); }; - -/** @class DCPVideoFrame - * @brief A single frame of video destined for a DCP. - * - * Given an Image and some settings, this class knows how to encode - * the image to J2K either on the local host or on a remote server. - * - * Objects of this class are used for the queue that we keep - * of images that require encoding. - */ -class DCPVideoFrame : public boost::noncopyable -{ -public: - DCPVideoFrame (boost::shared_ptr<const PlayerVideoFrame>, int, int, int, Resolution, boost::shared_ptr<Log>); - DCPVideoFrame (boost::shared_ptr<const PlayerVideoFrame>, boost::shared_ptr<const cxml::Node>, boost::shared_ptr<Log>); - - boost::shared_ptr<EncodedData> encode_locally (); - boost::shared_ptr<EncodedData> encode_remotely (ServerDescription); - - int index () const { - return _index; - } - - Eyes eyes () const; - -private: - - void add_metadata (xmlpp::Element *) const; - - boost::shared_ptr<const PlayerVideoFrame> _frame; - int _index; ///< frame index within the DCP's intrinsic duration - int _frames_per_second; ///< Frames per second that we will use for the DCP - int _j2k_bandwidth; ///< J2K bandwidth to use - Resolution _resolution; ///< Resolution (2K or 4K) - - boost::shared_ptr<Log> _log; ///< log -}; diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 693fd587e..0c9faa70d 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -29,13 +29,13 @@ #include "film.h" #include "log.h" #include "config.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "server.h" #include "cross.h" #include "writer.h" #include "server_finder.h" #include "player.h" -#include "player_video_frame.h" +#include "player_video.h" #include "i18n.h" @@ -58,15 +58,14 @@ using boost::scoped_array; int const Encoder::_history_size = 25; /** @param f Film that we are encoding */ -Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j) +Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j, shared_ptr<Writer> writer) : _film (f) , _job (j) , _video_frames_out (0) , _terminate (false) + , _writer (writer) { - _have_a_real_frame[EYES_BOTH] = false; - _have_a_real_frame[EYES_LEFT] = false; - _have_a_real_frame[EYES_RIGHT] = false; + } Encoder::~Encoder () @@ -88,18 +87,17 @@ Encoder::add_worker_threads (ServerDescription d) } void -Encoder::process_begin () +Encoder::begin () { for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) { _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, optional<ServerDescription> ()))); } - _writer.reset (new Writer (_film, _job)); ServerFinder::instance()->connect (boost::bind (&Encoder::server_found, this, _1)); } void -Encoder::process_end () +Encoder::end () { boost::mutex::scoped_lock lock (_mutex); @@ -126,7 +124,7 @@ Encoder::process_end () So just mop up anything left in the queue here. */ - for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { + for (list<shared_ptr<DCPVideo> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { LOG_GENERAL (N_("Encode left-over frame %1"), (*i)->index ()); try { _writer->write ((*i)->encode_locally(), (*i)->index (), (*i)->eyes ()); @@ -135,9 +133,6 @@ Encoder::process_end () LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); } } - - _writer->finish (); - _writer.reset (); } /** @return an estimate of the current number of frames we are encoding per second, @@ -181,8 +176,11 @@ Encoder::frame_done () } } +/** Called in order, so each time this is called the supplied frame is the one + * after the previous one. + */ void -Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) +Encoder::enqueue (shared_ptr<PlayerVideo> pv) { _waker.nudge (); @@ -209,20 +207,24 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) rethrow (); if (_writer->can_fake_write (_video_frames_out)) { - _writer->fake_write (_video_frames_out, pvf->eyes ()); - _have_a_real_frame[pvf->eyes()] = false; - frame_done (); - } else if (same && _have_a_real_frame[pvf->eyes()]) { - /* Use the last frame that we encoded. */ - _writer->repeat (_video_frames_out, pvf->eyes()); + /* We can fake-write this frame */ + _writer->fake_write (_video_frames_out, pv->eyes ()); frame_done (); + } else if (pv->has_j2k ()) { + /* This frame already has JPEG2000 data, so just write it */ + _writer->write (pv->j2k(), _video_frames_out, pv->eyes ()); } else { /* Queue this new frame for encoding */ LOG_TIMING ("adding to queue of %1", _queue.size ()); - _queue.push_back (shared_ptr<DCPVideoFrame> ( - new DCPVideoFrame ( - pvf, _video_frames_out, _film->video_frame_rate(), - _film->j2k_bandwidth(), _film->resolution(), _film->log() + _queue.push_back (shared_ptr<DCPVideo> ( + new DCPVideo ( + pv, + _video_frames_out, + _film->video_frame_rate(), + _film->j2k_bandwidth(), + _film->resolution(), + _film->burn_subtitles(), + _film->log() ) )); @@ -230,21 +232,14 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) waiting on that. */ _empty_condition.notify_all (); - _have_a_real_frame[pvf->eyes()] = true; } - if (pvf->eyes() != EYES_LEFT) { + if (pv->eyes() != EYES_LEFT) { ++_video_frames_out; } } void -Encoder::process_audio (shared_ptr<const AudioBuffers> data) -{ - _writer->write (data); -} - -void Encoder::terminate_threads () { { @@ -273,6 +268,8 @@ try encodings. */ int remote_backoff = 0; + shared_ptr<DCPVideo> last_dcp_video; + shared_ptr<EncodedData> last_encoded; while (true) { @@ -287,7 +284,7 @@ try } LOG_TIMING ("[%1] encoder thread wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); - shared_ptr<DCPVideoFrame> vf = _queue.front (); + shared_ptr<DCPVideo> vf = _queue.front (); LOG_TIMING ("[%1] encoder thread pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->index(), vf->eyes ()); _queue.pop_front (); @@ -295,38 +292,47 @@ try shared_ptr<EncodedData> encoded; - if (server) { - try { - encoded = vf->encode_remotely (server.get ()); - - if (remote_backoff > 0) { - LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", server->host_name ()); + if (last_dcp_video && vf->same (last_dcp_video)) { + /* We already have encoded data for the same input as this one, so take a short-cut */ + encoded = last_encoded; + } else { + /* We need to encode this input */ + if (server) { + try { + encoded = vf->encode_remotely (server.get ()); + + if (remote_backoff > 0) { + LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", server->host_name ()); + } + + /* This job succeeded, so remove any backoff */ + remote_backoff = 0; + + } catch (std::exception& e) { + if (remote_backoff < 60) { + /* back off more */ + remote_backoff += 10; + } + LOG_ERROR ( + N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), + vf->index(), server->host_name(), e.what(), remote_backoff + ); } - /* This job succeeded, so remove any backoff */ - remote_backoff = 0; - - } catch (std::exception& e) { - if (remote_backoff < 60) { - /* back off more */ - remote_backoff += 10; + } else { + try { + LOG_TIMING ("[%1] encoder thread begins local encode of %2", boost::this_thread::get_id(), vf->index()); + encoded = vf->encode_locally (); + LOG_TIMING ("[%1] encoder thread finishes local encode of %2", boost::this_thread::get_id(), vf->index()); + } catch (std::exception& e) { + LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); } - LOG_ERROR ( - N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), - vf->index(), server->host_name(), e.what(), remote_backoff - ); - } - - } else { - try { - LOG_TIMING ("[%1] encoder thread begins local encode of %2", boost::this_thread::get_id(), vf->index()); - encoded = vf->encode_locally (); - LOG_TIMING ("[%1] encoder thread finishes local encode of %2", boost::this_thread::get_id(), vf->index()); - } catch (std::exception& e) { - LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); } } + last_dcp_video = vf; + last_encoded = encoded; + if (encoded) { _writer->write (encoded, vf->index (), vf->eyes ()); frame_done (); diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 8d5aa2c40..51df0176b 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -38,45 +38,42 @@ extern "C" { #include "util.h" #include "config.h" #include "cross.h" +#include "exceptions.h" class Image; class AudioBuffers; class Film; class ServerDescription; -class DCPVideoFrame; +class DCPVideo; class EncodedData; class Writer; class Job; class ServerFinder; -class PlayerVideoFrame; +class PlayerVideo; /** @class Encoder - * @brief Encoder to J2K and WAV for DCP. + * @brief Class to manage encoding to JPEG2000. * - * Video is supplied to process_video as RGB frames, and audio - * is supplied as uncompressed PCM in blocks of various sizes. + * This class keeps a queue of frames to be encoded and distributes + * the work around threads and encoding servers. */ class Encoder : public boost::noncopyable, public ExceptionStore { public: - Encoder (boost::shared_ptr<const Film> f, boost::weak_ptr<Job>); + Encoder (boost::shared_ptr<const Film> f, boost::weak_ptr<Job>, boost::shared_ptr<Writer>); virtual ~Encoder (); /** Called to indicate that a processing run is about to begin */ - void process_begin (); + void begin (); /** Call with a frame of video. - * @param pvf Video frame image. - * @param same true if pvf is the same as the last time we were called. + * @param f Video frame. */ - void process_video (boost::shared_ptr<PlayerVideoFrame> pvf, bool same); - - /** Call with some audio data */ - void process_audio (boost::shared_ptr<const AudioBuffers>); + void enqueue (boost::shared_ptr<PlayerVideo> f); /** Called when a processing run has finished */ - void process_end (); + void end (); float current_encoding_rate () const; int video_frames_out () const; @@ -106,9 +103,8 @@ private: /** Number of video frames written for the DCP so far */ int _video_frames_out; - bool _have_a_real_frame[EYES_COUNT]; bool _terminate; - std::list<boost::shared_ptr<DCPVideoFrame> > _queue; + std::list<boost::shared_ptr<DCPVideo> > _queue; std::list<boost::thread *> _threads; mutable boost::mutex _mutex; /** condition to manage thread wakeups when we have nothing to do */ diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc index 8144f41b9..95d4c8cce 100644 --- a/src/lib/exceptions.cc +++ b/src/lib/exceptions.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 @@ -56,8 +56,20 @@ 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.empty() ? "[nothing]" : saw, expecting), f) +{ + +} + +InvalidSignerError::InvalidSignerError () + : StringError (_("The certificate chain for signing is invalid")) +{ + +} diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 3423a5754..52f257a8d 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -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 @@ -17,13 +17,13 @@ */ -#ifndef DCPOMATIC_EXCEPTIONS_H -#define DCPOMATIC_EXCEPTIONS_H - -/** @file src/exceptions.h +/** @file src/lib/exceptions.h * @brief Our exceptions. */ +#ifndef DCPOMATIC_EXCEPTIONS_H +#define DCPOMATIC_EXCEPTIONS_H + #include <stdexcept> #include <cstring> #include <boost/exception/all.hpp> @@ -205,7 +205,7 @@ public: {} }; -/** @class NetworkError. +/** @class NetworkError * @brief Indicates some problem with communication on the network. */ class NetworkError : public StringError @@ -216,6 +216,9 @@ public: {} }; +/** @class KDMError + * @brief A problem with a KDM. + */ class KDMError : public StringError { public: @@ -224,15 +227,44 @@ public: {} }; +/** @class PixelFormatError + * @brief A problem with an unsupported pixel format. + */ class PixelFormatError : public StringError { public: PixelFormatError (std::string o, AVPixelFormat f); }; -/** 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 +/** @class SubRipError + * @brief An error that occurs while parsing a SubRip file. + */ +class SubRipError : public FileError +{ +public: + SubRipError (std::string, std::string, boost::filesystem::path); +}; + +class DCPError : public StringError +{ +public: + DCPError (std::string s) + : StringError (s) + {} +}; + +class InvalidSignerError : public StringError +{ +public: + InvalidSignerError (); +}; + +/** @class ExceptionStore + * @brief 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 * * void my_thread () * try { @@ -269,6 +301,4 @@ private: mutable boost::mutex _mutex; }; - - #endif diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index ebe62b51f..fa369dda4 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -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 @@ -22,9 +22,11 @@ extern "C" { #include <libavformat/avformat.h> #include <libswscale/swscale.h> } -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "ffmpeg.h" #include "ffmpeg_content.h" +#include "ffmpeg_audio_stream.h" +#include "ffmpeg_subtitle_stream.h" #include "exceptions.h" #include "util.h" @@ -33,7 +35,7 @@ extern "C" { using std::string; using std::cout; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; boost::mutex FFmpeg::_mutex; @@ -47,8 +49,7 @@ FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c) , _video_stream (-1) { setup_general (); - setup_video (); - setup_audio (); + setup_decoders (); } FFmpeg::~FFmpeg () @@ -56,14 +57,10 @@ FFmpeg::~FFmpeg () boost::mutex::scoped_lock lm (_mutex); for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { - AVCodecContext* context = _format_context->streams[i]->codec; - if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) { - avcodec_close (context); - } + avcodec_close (_format_context->streams[i]->codec); } av_frame_free (&_frame); - avformat_close_input (&_format_context); } @@ -143,46 +140,24 @@ FFmpeg::setup_general () } void -FFmpeg::setup_video () -{ - boost::mutex::scoped_lock lm (_mutex); - - assert (_video_stream >= 0); - AVCodecContext* context = _format_context->streams[_video_stream]->codec; - AVCodec* codec = avcodec_find_decoder (context->codec_id); - - if (codec == 0) { - throw DecodeError (_("could not find video decoder")); - } - - if (avcodec_open2 (context, codec, 0) < 0) { - throw DecodeError (N_("could not open video decoder")); - } -} - -void -FFmpeg::setup_audio () +FFmpeg::setup_decoders () { boost::mutex::scoped_lock lm (_mutex); for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { AVCodecContext* context = _format_context->streams[i]->codec; - if (context->codec_type != AVMEDIA_TYPE_AUDIO) { - continue; - } AVCodec* codec = avcodec_find_decoder (context->codec_id); - if (codec == 0) { - throw DecodeError (_("could not find audio decoder")); - } - - if (avcodec_open2 (context, codec, 0) < 0) { - throw DecodeError (N_("could not open audio decoder")); + if (codec) { + if (avcodec_open2 (context, codec, 0) < 0) { + throw DecodeError (N_("could not open decoder")); + } } + + /* We are silently ignoring any failures to find suitable decoders here */ } } - AVCodecContext * FFmpeg::video_codec_context () const { @@ -192,9 +167,23 @@ 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; } +AVCodecContext * +FFmpeg::subtitle_codec_context () const +{ + if (!_ffmpeg_content->subtitle_stream ()) { + return 0; + } + + return _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec; +} + int FFmpeg::avio_read (uint8_t* buffer, int const amount) { diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h index 760918437..8aaa54f84 100644 --- a/src/lib/ffmpeg.h +++ b/src/lib/ffmpeg.h @@ -56,6 +56,7 @@ public: protected: AVCodecContext* video_codec_context () const; AVCodecContext* audio_codec_context () const; + AVCodecContext* subtitle_codec_context () const; boost::shared_ptr<const FFmpegContent> _ffmpeg_content; @@ -79,8 +80,7 @@ protected: private: void setup_general (); - void setup_video (); - void setup_audio (); + void setup_decoders (); }; #endif diff --git a/src/lib/ffmpeg_audio_stream.cc b/src/lib/ffmpeg_audio_stream.cc new file mode 100644 index 000000000..d8666e89e --- /dev/null +++ b/src/lib/ffmpeg_audio_stream.cc @@ -0,0 +1,47 @@ +/* + 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 <libxml++/libxml++.h> +#include <libcxml/cxml.h> +#include <dcp/raw_convert.h> +#include "ffmpeg_audio_stream.h" + +using std::string; +using dcp::raw_convert; + +FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version) + : FFmpegStream (node) + , _frame_rate (node->number_child<int> ("FrameRate")) + , _channels (node->number_child<int64_t> ("Channels")) + , _mapping (node->node_child ("Mapping"), version) +{ + first_audio = node->optional_number_child<double> ("FirstAudio"); +} + +void +FFmpegAudioStream::as_xml (xmlpp::Node* root) const +{ + FFmpegStream::as_xml (root); + root->add_child("FrameRate")->add_child_text (raw_convert<string> (_frame_rate)); + root->add_child("Channels")->add_child_text (raw_convert<string> (_channels)); + if (first_audio) { + root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get().get())); + } + _mapping.as_xml (root->add_child("Mapping")); +} diff --git a/src/lib/ffmpeg_audio_stream.h b/src/lib/ffmpeg_audio_stream.h new file mode 100644 index 000000000..1587afcae --- /dev/null +++ b/src/lib/ffmpeg_audio_stream.h @@ -0,0 +1,74 @@ +/* + 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 "ffmpeg_stream.h" +#include "audio_mapping.h" +#include "dcpomatic_time.h" + +struct ffmpeg_pts_offset_test; + +class FFmpegAudioStream : public FFmpegStream +{ +public: + FFmpegAudioStream (std::string n, int i, int f, int c) + : FFmpegStream (n, i) + , _frame_rate (f) + , _channels (c) + , _mapping (c) + { + _mapping.make_default (); + } + + FFmpegAudioStream (cxml::ConstNodePtr, int); + + void as_xml (xmlpp::Node *) const; + + int frame_rate () const { + return _frame_rate; + } + + int channels () const { + return _channels; + } + + AudioMapping mapping () const { + return _mapping; + } + + void set_mapping (AudioMapping m) { + _mapping = m; + } + + boost::optional<ContentTime> first_audio; + +private: + friend struct ffmpeg_pts_offset_test; + + /* Constructor for tests */ + FFmpegAudioStream () + : FFmpegStream ("", 0) + , _frame_rate (0) + , _channels (0) + , _mapping (1) + {} + + int _frame_rate; + int _channels; + AudioMapping _mapping; +}; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index a4209f5b6..bb4e02230 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -21,9 +21,11 @@ extern "C" { #include <libavformat/avformat.h> } #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "ffmpeg_content.h" #include "ffmpeg_examiner.h" +#include "ffmpeg_subtitle_stream.h" +#include "ffmpeg_audio_stream.h" #include "compose.hpp" #include "job.h" #include "util.h" @@ -45,7 +47,7 @@ using std::cout; using std::pair; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; int const FFmpegContentProperty::SUBTITLE_STREAM = 101; @@ -62,7 +64,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path } -FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version, list<string>& notes) +FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version, list<string>& notes) : Content (f, node) , VideoContent (f, node, version) , AudioContent (f, node) @@ -108,7 +110,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, vector<boost::shared_ptr for (size_t i = 0; i < c.size(); ++i) { shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c[i]); - if (f->with_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) { + if (fc->use_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) { throw JoinError (_("Content to be joined must use the same subtitle stream.")); } @@ -156,7 +158,7 @@ FFmpegContent::as_xml (xmlpp::Node* node) const } if (_first_video) { - node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get ())); + node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get().get())); } } @@ -167,20 +169,15 @@ 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); - VideoContent::Frame video_length = 0; - video_length = examiner->video_length (); - LOG_GENERAL ("Video length obtained from header as %1 frames", video_length); + shared_ptr<const Film> film = _film.lock (); + assert (film); { boost::mutex::scoped_lock lm (_mutex); - _video_length = video_length; - _subtitle_streams = examiner->subtitle_streams (); if (!_subtitle_streams.empty ()) { _subtitle_stream = _subtitle_streams.front (); @@ -194,9 +191,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); signal_changed (FFmpegContentProperty::AUDIO_STREAMS); @@ -237,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 ""; } SafeStringStream s; - s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n"; + s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n"; s << VideoContent::information (); return s.str (); @@ -271,19 +265,14 @@ 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 (); - float const vfr = video_frame_rate (); - VideoContent::Frame const vl = video_length_after_3d_combine (); - - boost::mutex::scoped_lock lm (_mutex); - if (!_audio_stream) { - return 0; + if (!audio_stream ()) { + return ContentTime (); } - - return video_frames_to_audio_frames (vl, cafr, vfr); + + return video_length (); } int @@ -295,11 +284,11 @@ FFmpegContent::audio_channels () const return 0; } - return _audio_stream->channels; + return _audio_stream->channels (); } int -FFmpegContent::content_audio_frame_rate () const +FFmpegContent::audio_frame_rate () const { boost::mutex::scoped_lock lm (_mutex); @@ -307,7 +296,7 @@ FFmpegContent::content_audio_frame_rate () const return 0; } - return _audio_stream->frame_rate; + return _audio_stream->frame_rate (); } bool @@ -322,94 +311,12 @@ operator!= (FFmpegStream const & a, FFmpegStream const & b) return a._id != b._id; } -FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node) - : name (node->string_child ("Name")) - , _id (node->number_child<int> ("Id")) -{ - -} - -void -FFmpegStream::as_xml (xmlpp::Node* root) const -{ - root->add_child("Name")->add_child_text (name); - root->add_child("Id")->add_child_text (raw_convert<string> (_id)); -} - -FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version) - : FFmpegStream (node) - , mapping (node->node_child ("Mapping"), version) -{ - frame_rate = node->number_child<int> ("FrameRate"); - channels = node->number_child<int64_t> ("Channels"); - first_audio = node->optional_number_child<double> ("FirstAudio"); -} - -void -FFmpegAudioStream::as_xml (xmlpp::Node* root) const -{ - FFmpegStream::as_xml (root); - root->add_child("FrameRate")->add_child_text (raw_convert<string> (frame_rate)); - root->add_child("Channels")->add_child_text (raw_convert<string> (channels)); - if (first_audio) { - root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get ())); - } - mapping.as_xml (root->add_child("Mapping")); -} - -bool -FFmpegStream::uses_index (AVFormatContext const * fc, int index) const -{ - size_t i = 0; - while (i < fc->nb_streams) { - if (fc->streams[i]->id == _id) { - return int (i) == index; - } - ++i; - } - - return false; -} - -AVStream * -FFmpegStream::stream (AVFormatContext const * fc) const -{ - size_t i = 0; - while (i < fc->nb_streams) { - if (fc->streams[i]->id == _id) { - return fc->streams[i]; - } - ++i; - } - - assert (false); - return 0; -} - -/** Construct a SubtitleStream from a value returned from to_string(). - * @param t String returned from to_string(). - * @param v State file version. - */ -FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node) - : FFmpegStream (node) -{ - -} - -void -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); - - FrameRateChange frc (video_frame_rate (), film->video_frame_rate ()); - return video_length_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate (); + return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ())); } AudioMapping @@ -421,7 +328,7 @@ FFmpegContent::audio_mapping () const return AudioMapping (); } - return _audio_stream->mapping; + return _audio_stream->mapping (); } void @@ -438,8 +345,8 @@ FFmpegContent::set_filters (vector<Filter const *> const & filters) void FFmpegContent::set_audio_mapping (AudioMapping m) { - audio_stream()->mapping = m; - signal_changed (AudioContentProperty::AUDIO_MAPPING); + audio_stream()->set_mapping (m); + AudioContent::set_audio_mapping (m); } string @@ -482,3 +389,29 @@ FFmpegContent::audio_analysis_path () const p /= name; return p; } + +list<ContentTimePeriod> +FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const +{ + list<ContentTimePeriod> d; + + shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream (); + if (!stream) { + return d; + } + + /* XXX: inefficient */ + for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) { + if ((starting && period.contains (i->from)) || (!starting && period.overlaps (*i))) { + d.push_back (*i); + } + } + + return d; +} + +bool +FFmpegContent::has_subtitles () const +{ + return !subtitle_streams().empty (); +} diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index c15e5c10e..da8152c0d 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.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 @@ -21,6 +21,7 @@ #define DCPOMATIC_FFMPEG_CONTENT_H #include <boost/enable_shared_from_this.hpp> +#include <boost/lexical_cast.hpp> #include "video_content.h" #include "audio_content.h" #include "subtitle_content.h" @@ -30,88 +31,9 @@ struct AVFormatContext; struct AVStream; class Filter; -class ffmpeg_pts_offset_test; - -class FFmpegStream -{ -public: - FFmpegStream (std::string n, int i) - : name (n) - , _id (i) - {} - - FFmpegStream (boost::shared_ptr<const cxml::Node>); - - void as_xml (xmlpp::Node *) const; - - /** @param c An AVFormatContext. - * @param index A stream index within the AVFormatContext. - * @return true if this FFmpegStream uses the given stream index. - */ - bool uses_index (AVFormatContext const * c, int index) const; - AVStream* stream (AVFormatContext const * c) const; - - std::string technical_summary () const { - return "id " + boost::lexical_cast<std::string> (_id); - } - - std::string identifier () const { - return boost::lexical_cast<std::string> (_id); - } - - std::string name; - - friend bool operator== (FFmpegStream const & a, FFmpegStream const & b); - friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b); - -private: - int _id; -}; - -class FFmpegAudioStream : public FFmpegStream -{ -public: - FFmpegAudioStream (std::string n, int i, int f, int c) - : FFmpegStream (n, i) - , frame_rate (f) - , channels (c) - , mapping (c) - { - mapping.make_default (); - } - - FFmpegAudioStream (boost::shared_ptr<const cxml::Node>, int); - - void as_xml (xmlpp::Node *) const; - - int frame_rate; - int channels; - AudioMapping mapping; - boost::optional<double> first_audio; - -private: - friend class ffmpeg_pts_offset_test; - - /* Constructor for tests */ - FFmpegAudioStream () - : FFmpegStream ("", 0) - , frame_rate (0) - , channels (0) - , mapping (1) - {} -}; - -class FFmpegSubtitleStream : public FFmpegStream -{ -public: - FFmpegSubtitleStream (std::string n, int i) - : FFmpegStream (n, i) - {} - - FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>); - - void as_xml (xmlpp::Node *) const; -}; +class FFmpegSubtitleStream; +class FFmpegAudioStream; +struct ffmpeg_pts_offset_test; class FFmpegContentProperty : public VideoContentProperty { @@ -127,7 +49,7 @@ class FFmpegContent : public VideoContent, public AudioContent, public SubtitleC { public: FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path); - FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version, std::list<std::string> &); + FFmpegContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int version, std::list<std::string> &); FFmpegContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); boost::shared_ptr<FFmpegContent> shared_from_this () { @@ -139,18 +61,21 @@ 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; - int content_audio_frame_rate () const; + ContentTime audio_length () const; + int audio_frame_rate () const; AudioMapping audio_mapping () const; void set_audio_mapping (AudioMapping); boost::filesystem::path audio_analysis_path () const; + /* SubtitleContent */ + bool has_subtitles () const; + void set_filters (std::vector<Filter const *> const &); std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const { @@ -181,19 +106,21 @@ 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; } + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + private: - friend class ffmpeg_pts_offset_test; + friend struct ffmpeg_pts_offset_test; std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams; boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream; std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; boost::shared_ptr<FFmpegAudioStream> _audio_stream; - boost::optional<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 d40b798ba..15443c346 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -31,23 +31,26 @@ extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } -#include "film.h" #include "filter.h" #include "exceptions.h" #include "image.h" #include "util.h" #include "log.h" #include "ffmpeg_decoder.h" +#include "ffmpeg_audio_stream.h" +#include "ffmpeg_subtitle_stream.h" #include "filter_graph.h" #include "audio_buffers.h" #include "ffmpeg_content.h" -#include "image_proxy.h" +#include "raw_image_proxy.h" +#include "film.h" +#include "timer.h" #include "i18n.h" -#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); -#define LOG_ERROR(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR); -#define LOG_WARNING(...) film->log()->log (__VA_ARGS__, Log::TYPE_WARNING); +#define LOG_GENERAL(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); +#define LOG_ERROR(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR); +#define LOG_WARNING(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING); using std::cout; using std::string; @@ -55,26 +58,19 @@ using std::vector; using std::list; using std::min; using std::pair; +using std::make_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) + : VideoDecoder (c) + , AudioDecoder (c) + , SubtitleDecoder (c) , FFmpeg (c) - , _subtitle_codec_context (0) - , _subtitle_codec (0) - , _decode_video (video) - , _decode_audio (audio) - , _pts_offset (0) - , _just_sought (false) + , _log (log) { - setup_subtitle (); - /* Audio and video frame PTS values may not start with 0. We want to fiddle them so that: @@ -84,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_video = c->first_video(); + bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio; /* First, make one of them start at 0 */ @@ -104,24 +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; - } -} - -FFmpegDecoder::~FFmpegDecoder () -{ - boost::mutex::scoped_lock lm (_mutex); - - if (_subtitle_codec_context) { - avcodec_close (_subtitle_codec_context); + 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; } } @@ -135,20 +114,15 @@ FFmpegDecoder::flush () /* XXX: should we reset _packet.data and size after each *_decode_* call? */ - if (_decode_video) { - while (decode_video_packet ()) {} - } + while (decode_video_packet ()) {} - if (_ffmpeg_content->audio_stream() && _decode_audio) { + if (_ffmpeg_content->audio_stream()) { decode_audio_packet (); + AudioDecoder::flush (); } - - /* Stop us being asked for any more data */ - _video_position = _ffmpeg_content->video_length_after_3d_combine (); - _audio_position = _ffmpeg_content->audio_length (); } -void +bool FFmpegDecoder::pass () { int r = av_read_frame (_format_context, &_packet); @@ -158,29 +132,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); LOG_ERROR (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) { + + if (si == _video_stream) { decode_video_packet (); - } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) { + } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) { 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_subtitle_packet (); } av_free_packet (&_packet); + return false; } /** @param data pointer to array of pointers to buffers. @@ -313,82 +283,40 @@ FFmpegDecoder::bytes_per_audio_sample () const } void -FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate) +FFmpegDecoder::seek (ContentTime time, bool accurate) { - double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base); - - /* 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. + VideoDecoder::seek (time, accurate); + AudioDecoder::seek (time, accurate); + + /* If we are doing an `accurate' seek, we need to use pre-roll, as + we don't really know what the seek will give us. */ - int initial = frame; - if (accurate) { - initial -= 5; - } + ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0); + time -= pre_roll; - if (initial < 0) { - initial = 0; - } - - /* Initial seek time in the stream's timebase */ - int64_t const initial_vt = ((initial / _ffmpeg_content->original_video_frame_rate()) - _pts_offset) / time_base; - - av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD); - - avcodec_flush_buffers (video_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. + /* XXX: it seems debatable whether PTS should be used here... + http://www.mjbshaw.com/2012/04/seeking-in-ffmpeg-know-your-timestamp.html */ - if (!accurate) { - _just_sought = true; - } - - _video_position = frame; - if (frame == 0 || !accurate) { - /* We're already there, or we're as close as we need to be */ - return; - } + ContentTime const u = time - _pts_offset; + int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base); - while (true) { - int r = av_read_frame (_format_context, &_packet); - if (r < 0) { - return; - } + if (_ffmpeg_content->audio_stream ()) { + s = min ( + s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)) + ); + } - 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->original_video_frame_rate() - ); + av_seek_frame (_format_context, _video_stream, s, 0); - if (_video_position >= (frame - 1)) { - av_free_packet (&_packet); - break; - } - } - - av_free_packet (&_packet); + 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 ()); } - - /* _video_position should be the next thing to be emitted, which will the one after the thing - we just saw. - */ - _video_position++; } void @@ -404,42 +332,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); LOG_ERROR ("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 */ - int64_t frames = pts * _ffmpeg_content->content_audio_frame_rate (); - while (frames > 0) { - int64_t const this_time = min (frames, (int64_t) _ffmpeg_content->content_audio_frame_rate() / 2); - - shared_ptr<AudioBuffers> silence ( - new AudioBuffers (_ffmpeg_content->audio_channels(), this_time) - ); - - silence->make_silent (); - audio (silence, _audio_position); - frames -= this_time; - } - } - } + 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; @@ -460,17 +369,13 @@ 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); - LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format); } else { graph = *i; @@ -478,56 +383,16 @@ FFmpegDecoder::decode_video_packet () list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame); - shared_ptr<const Film> film = _film.lock (); - assert (film); - for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) { 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->original_video_frame_rate ()); - _just_sought = false; - } - - double const next = _video_position / _ffmpeg_content->original_video_frame_rate(); - double const one_frame = 1 / _ffmpeg_content->original_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 - ) - ); - - shared_ptr<const Film> film = _film.lock (); - assert (film); - - black->make_black (); - video (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), 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 (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), false, _video_position); - } - + double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds (); + video ( + shared_ptr<ImageProxy> (new RawImageProxy (image, _video_content->film()->log())), + rint (pts * _ffmpeg_content->video_frame_rate ()) + ); } else { LOG_WARNING ("Dropping frame without PTS"); } @@ -535,47 +400,13 @@ FFmpegDecoder::decode_video_packet () return true; } - - -void -FFmpegDecoder::setup_subtitle () -{ - boost::mutex::scoped_lock lm (_mutex); - - if (!_ffmpeg_content->subtitle_stream()) { - return; - } - - _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec; - if (_subtitle_codec_context == 0) { - throw DecodeError (N_("could not find subtitle stream")); - } - - _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); - - if (_subtitle_codec == 0) { - throw DecodeError (N_("could not find subtitle decoder")); - } - - if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) { - throw DecodeError (N_("could not open subtitle decoder")); - } -} - -bool -FFmpegDecoder::done () const -{ - bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length()); - bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length()); - return vd && ad; -} void FFmpegDecoder::decode_subtitle_packet () { int got_subtitle; AVSubtitle sub; - if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) { + if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) { return; } @@ -583,31 +414,29 @@ 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 (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ()); 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; - - /* 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; + ContentTimePeriod period = subtitle_period (sub) + _pts_offset; AVSubtitleRect const * rect = sub.rects[0]; if (rect->type != SUBTITLE_BITMAP) { - throw DecodeError (_("non-bitmap subtitles not yet supported")); + /* XXX */ + // throw DecodeError (_("non-bitmap subtitles not yet supported")); + return; } /* 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]; @@ -629,20 +458,24 @@ 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 ( + period, image, dcpomatic::Rect<double> ( static_cast<double> (rect->x) / vs.width, static_cast<double> (rect->y) / vs.height, static_cast<double> (rect->w) / vs.width, static_cast<double> (rect->h) / vs.height - ), - from, - to + ) ); - avsubtitle_free (&sub); } + +list<ContentTimePeriod> +FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +{ + return _ffmpeg_content->subtitles_during (p, starting); +} diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index d4b4fa1c0..9f85c2dca 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -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 @@ -37,7 +37,7 @@ extern "C" { #include "subtitle_decoder.h" #include "ffmpeg.h" -class Film; +class Log; class FilterGraph; class ffmpeg_pts_offset_test; @@ -47,22 +47,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 (); - - void pass (); - void seek (VideoContent::Frame, bool); - bool done () const; + FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>); private: - friend class ::ffmpeg_pts_offset_test; - - static double compute_pts_offset (double, double, float); + friend struct ::ffmpeg_pts_offset_test; + void seek (ContentTime time, bool); + bool pass (); void flush (); - void setup_subtitle (); - AVSampleFormat audio_sample_format () const; int bytes_per_audio_sample () const; @@ -73,15 +66,12 @@ private: void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size); - AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle - AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + boost::shared_ptr<Log> _log; std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; boost::mutex _filter_graphs_mutex; - bool _decode_video; - bool _decode_audio; - - double _pts_offset; - bool _just_sought; + ContentTime _pts_offset; }; diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index 5ccc8028b..48d85da6f 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -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 @@ -23,6 +23,9 @@ extern "C" { } #include "ffmpeg_examiner.h" #include "ffmpeg_content.h" +#include "ffmpeg_audio_stream.h" +#include "ffmpeg_subtitle_stream.h" +#include "util.h" #include "safe_stringstream.h" #include "i18n.h" @@ -61,55 +64,93 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c) } } - /* Run through until we find the first audio (for each stream) and video */ - + /* Run through until we find: + * - the first video. + * - the first audio for each stream. + * - the subtitle periods for each stream. + * + * We have to note subtitle periods as otherwise we have no way of knowing + * where we should look for subtitles (video and audio are always present, + * so they are ok). + */ while (true) { int r = av_read_frame (_format_context, &_packet); if (r < 0) { break; } - int frame_finished; - AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec; - if (_packet.stream_index == _video_stream && !_first_video) { - if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - _first_video = frame_time (_format_context->streams[_video_stream]); - } - } else { - for (size_t i = 0; i < _audio_streams.size(); ++i) { - if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index) && !_audio_streams[i]->first_audio) { - if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->stream (_format_context)); - } - } + if (_packet.stream_index == _video_stream) { + video_packet (context); + } + + for (size_t i = 0; i < _audio_streams.size(); ++i) { + if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index)) { + audio_packet (context, _audio_streams[i]); } } - bool have_all_audio = true; - size_t i = 0; - while (i < _audio_streams.size() && have_all_audio) { - have_all_audio = _audio_streams[i]->first_audio; - ++i; + for (size_t i = 0; i < _subtitle_streams.size(); ++i) { + if (_subtitle_streams[i]->uses_index (_format_context, _packet.stream_index)) { + subtitle_packet (context, _subtitle_streams[i]); + } } av_free_packet (&_packet); - - if (_first_video && have_all_audio) { - break; + } +} + +void +FFmpegExaminer::video_packet (AVCodecContext* context) +{ + if (_first_video) { + return; + } + + int frame_finished; + if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + _first_video = frame_time (_format_context->streams[_video_stream]); + } +} + +void +FFmpegExaminer::audio_packet (AVCodecContext* context, shared_ptr<FFmpegAudioStream> stream) +{ + if (stream->first_audio) { + return; + } + + int frame_finished; + if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + stream->first_audio = frame_time (stream->stream (_format_context)); + } +} + +void +FFmpegExaminer::subtitle_packet (AVCodecContext* context, shared_ptr<FFmpegSubtitleStream> stream) +{ + int frame_finished; + AVSubtitle sub; + if (avcodec_decode_subtitle2 (context, &sub, &frame_finished, &_packet) >= 0 && frame_finished) { + ContentTimePeriod const period = subtitle_period (sub); + if (sub.num_rects == 0 && !stream->periods.empty () && stream->periods.back().to > period.from) { + /* Finish the last subtitle */ + stream->periods.back().to = period.from; + } else if (sub.num_rects == 1) { + stream->periods.push_back (period); } } } -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::from_seconds (bet * av_q2d (s->time_base)); } return t; @@ -118,27 +159,25 @@ FFmpegExaminer::frame_time (AVStream* s) const float FFmpegExaminer::video_frame_rate () const { - AVStream* s = _format_context->streams[_video_stream]; - - if (s->avg_frame_rate.num && s->avg_frame_rate.den) { - return av_q2d (s->avg_frame_rate); - } - - return av_q2d (s->r_frame_rate); + /* This use of r_frame_rate is debateable; there's a few different + * frame rates in the format context, but this one seems to be the most + * reliable. + */ + return av_q2d (av_stream_get_r_frame_rate (_format_context->streams[_video_stream])); } -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 (ContentTime::Type (1), length.get ())); } string diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 369dac29c..8c31eb2c4 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,17 +41,21 @@ public: return _audio_streams; } - boost::optional<double> first_video () const { + boost::optional<ContentTime> first_video () const { return _first_video; } private: + void video_packet (AVCodecContext *); + void audio_packet (AVCodecContext *, boost::shared_ptr<FFmpegAudioStream>); + void subtitle_packet (AVCodecContext *, boost::shared_ptr<FFmpegSubtitleStream>); + 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/ffmpeg_stream.cc b/src/lib/ffmpeg_stream.cc new file mode 100644 index 000000000..3fac33327 --- /dev/null +++ b/src/lib/ffmpeg_stream.cc @@ -0,0 +1,71 @@ +/* + 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. + +*/ + +extern "C" { +#include <libavformat/avformat.h> +} +#include <libxml++/libxml++.h> +#include <dcp/raw_convert.h> +#include "ffmpeg_stream.h" + +using std::string; +using dcp::raw_convert; + +FFmpegStream::FFmpegStream (cxml::ConstNodePtr node) + : name (node->string_child ("Name")) + , _id (node->number_child<int> ("Id")) +{ + +} + +void +FFmpegStream::as_xml (xmlpp::Node* root) const +{ + root->add_child("Name")->add_child_text (name); + root->add_child("Id")->add_child_text (raw_convert<string> (_id)); +} + +bool +FFmpegStream::uses_index (AVFormatContext const * fc, int index) const +{ + size_t i = 0; + while (i < fc->nb_streams) { + if (fc->streams[i]->id == _id) { + return int (i) == index; + } + ++i; + } + + return false; +} + +AVStream * +FFmpegStream::stream (AVFormatContext const * fc) const +{ + size_t i = 0; + while (i < fc->nb_streams) { + if (fc->streams[i]->id == _id) { + return fc->streams[i]; + } + ++i; + } + + assert (false); + return 0; +} diff --git a/src/lib/ffmpeg_stream.h b/src/lib/ffmpeg_stream.h new file mode 100644 index 000000000..6bbcd0b01 --- /dev/null +++ b/src/lib/ffmpeg_stream.h @@ -0,0 +1,65 @@ +/* + 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. + +*/ + +#ifndef DCPOMATIC_FFMPEG_STREAM_H +#define DCPOMATIC_FFMPEG_STREAM_H + +#include <boost/lexical_cast.hpp> +#include <libcxml/cxml.h> + +struct AVFormatContext; +struct AVStream; + +class FFmpegStream +{ +public: + FFmpegStream (std::string n, int i) + : name (n) + , _id (i) + {} + + FFmpegStream (cxml::ConstNodePtr); + + void as_xml (xmlpp::Node *) const; + + /** @param c An AVFormatContext. + * @param index A stream index within the AVFormatContext. + * @return true if this FFmpegStream uses the given stream index. + */ + bool uses_index (AVFormatContext const * c, int index) const; + AVStream* stream (AVFormatContext const * c) const; + + std::string technical_summary () const { + return "id " + boost::lexical_cast<std::string> (_id); + } + + std::string identifier () const { + return boost::lexical_cast<std::string> (_id); + } + + std::string name; + + friend bool operator== (FFmpegStream const & a, FFmpegStream const & b); + friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b); + +private: + int _id; +}; + +#endif diff --git a/src/lib/ffmpeg_subtitle_stream.cc b/src/lib/ffmpeg_subtitle_stream.cc new file mode 100644 index 000000000..3d8fd4e83 --- /dev/null +++ b/src/lib/ffmpeg_subtitle_stream.cc @@ -0,0 +1,36 @@ +/* + 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 "ffmpeg_subtitle_stream.h" + +/** Construct a SubtitleStream from a value returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + */ +FFmpegSubtitleStream::FFmpegSubtitleStream (cxml::ConstNodePtr node) + : FFmpegStream (node) +{ + +} + +void +FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const +{ + FFmpegStream::as_xml (root); +} diff --git a/src/lib/ffmpeg_subtitle_stream.h b/src/lib/ffmpeg_subtitle_stream.h new file mode 100644 index 000000000..b16b825e7 --- /dev/null +++ b/src/lib/ffmpeg_subtitle_stream.h @@ -0,0 +1,36 @@ +/* + 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 "dcpomatic_time.h" +#include "ffmpeg_stream.h" + +class FFmpegSubtitleStream : public FFmpegStream +{ +public: + FFmpegSubtitleStream (std::string n, int i) + : FFmpegStream (n, i) + {} + + FFmpegSubtitleStream (cxml::ConstNodePtr); + + void as_xml (xmlpp::Node *) const; + + std::vector<ContentTimePeriod> periods; +}; + diff --git a/src/lib/file_group.cc b/src/lib/file_group.cc index 048f69233..9c8d43204 100644 --- a/src/lib/file_group.cc +++ b/src/lib/file_group.cc @@ -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,6 +17,10 @@ */ +/** @file src/lib/file_group.cc + * @brief FileGroup class. + */ + #include <cstdio> #include <sndfile.h> #include "file_group.h" @@ -26,6 +30,7 @@ using std::vector; using std::cout; +/** Construct a FileGroup with no files */ FileGroup::FileGroup () : _current_path (0) , _current_file (0) @@ -33,14 +38,17 @@ FileGroup::FileGroup () } +/** Construct a FileGroup with a single file */ FileGroup::FileGroup (boost::filesystem::path p) : _current_path (0) , _current_file (0) { _paths.push_back (p); + ensure_open_path (0); seek (0, SEEK_SET); } +/** Construct a FileGroup with multiple files */ FileGroup::FileGroup (vector<boost::filesystem::path> const & p) : _paths (p) , _current_path (0) @@ -50,6 +58,7 @@ FileGroup::FileGroup (vector<boost::filesystem::path> const & p) seek (0, SEEK_SET); } +/** Destroy a FileGroup, closing any open file */ FileGroup::~FileGroup () { if (_current_file) { @@ -160,6 +169,7 @@ FileGroup::read (uint8_t* buffer, int amount) const return read; } +/** @return Combined length of all the files */ int64_t FileGroup::length () const { diff --git a/src/lib/file_group.h b/src/lib/file_group.h index 65091c936..5a65de96f 100644 --- a/src/lib/file_group.h +++ b/src/lib/file_group.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,12 +17,19 @@ */ +/** @file src/lib/file_group.h + * @brief FileGroup class. + */ + #ifndef DCPOMATIC_FILE_GROUP_H #define DCPOMATIC_FILE_GROUP_H #include <vector> #include <boost/filesystem.hpp> +/** @class FileGroup + * @brief A class to make a list of files behave like they were concatenated. + */ class FileGroup { public: diff --git a/src/lib/film.cc b/src/lib/film.cc index 54503ef72..268109921 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -26,15 +26,14 @@ #include <unistd.h> #include <boost/filesystem.hpp> #include <boost/algorithm/string.hpp> -#include <boost/date_time.hpp> +#include <boost/lexical_cast.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 <libdcp/raw_convert.h> +#include <dcp/cpl.h> +#include <dcp/signer.h> +#include <dcp/util.h> +#include <dcp/local_time.h> +#include <dcp/raw_convert.h> #include "film.h" #include "job.h" #include "util.h" @@ -77,9 +76,10 @@ using boost::ends_with; using boost::starts_with; using boost::optional; using boost::is_any_of; -using libdcp::Size; -using libdcp::Signer; -using libdcp::raw_convert; +using dcp::Size; +using dcp::Signer; +using dcp::raw_convert; +using dcp::raw_convert; #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, Log::TYPE_GENERAL); @@ -91,11 +91,14 @@ using libdcp::raw_convert; * 7 -> 8 * Use <Scale> tag in <VideoContent> rather than <Ratio>. * 8 -> 9 - * DCI -> ISDCF. + * DCI -> ISDCF * 9 -> 10 * Subtitle X and Y scale. + * + * Bumped to 32 for 2.0 branch; some times are expressed in Times rather + * than frames now. */ -int const Film::current_state_version = 10; +int const Film::current_state_version = 32; /** Construct a Film object in a given directory. * @@ -109,7 +112,6 @@ Film::Film (boost::filesystem::path dir, bool log) , _container (Config::instance()->default_container ()) , _resolution (RESOLUTION_2K) , _scaler (Scaler::from_id ("bicubic")) - , _with_subtitles (false) , _signed (true) , _encrypted (false) , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ()) @@ -119,6 +121,7 @@ Film::Film (boost::filesystem::path dir, bool log) , _three_d (false) , _sequence_video (true) , _interop (false) + , _burn_subtitles (false) , _state_version (current_state_version) , _dirty (false) { @@ -182,12 +185,12 @@ Film::video_identifier () const s << "_S"; } - if (_three_d) { - s << "_3D"; + if (_burn_subtitles) { + s << "_B"; } - if (_with_subtitles) { - s << "_WS"; + if (_three_d) { + s << "_3D"; } return s.str (); @@ -227,6 +230,12 @@ Film::audio_mxf_filename () const return filename_safe_name() + "_audio.mxf"; } +boost::filesystem::path +Film::subtitle_xml_filename () const +{ + return filename_safe_name() + "_subtitle.xml"; +} + string Film::filename_safe_name () const { @@ -369,7 +378,6 @@ Film::metadata () const root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution)); root->add_child("Scaler")->add_child_text (_scaler->id ()); - root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth)); _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata")); root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate)); @@ -378,6 +386,7 @@ Film::metadata () const root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0"); root->add_child("Interop")->add_child_text (_interop ? "1" : "0"); + root->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0"); root->add_child("Signed")->add_child_text (_signed ? "1" : "0"); root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); root->add_child("Key")->add_child_text (_key.hex ()); @@ -441,7 +450,6 @@ Film::read_metadata () _resolution = string_to_resolution (f.string_child ("Resolution")); _scaler = Scaler::from_id (f.string_child ("Scaler")); - _with_subtitles = f.bool_child ("WithSubtitles"); _j2k_bandwidth = f.number_child<int> ("J2KBandwidth"); _video_frame_rate = f.number_child<int> ("VideoFrameRate"); _signed = f.optional_bool_child("Signed").get_value_or (true); @@ -450,7 +458,10 @@ 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")); + if (_state_version >= 32) { + _burn_subtitles = f.bool_child ("BurnSubtitles"); + } + _key = dcp::Key (f.string_child ("Key")); list<string> notes; /* This method is the only one that can return notes (so far) */ @@ -585,9 +596,9 @@ Film::isdcf_name (bool if_created_now) const /* XXX: this uses the first bit of content only */ /* The standard says we don't do this for trailers, for some strange reason */ - if (dcp_content_type() && dcp_content_type()->libdcp_kind() != libdcp::TRAILER) { - Ratio const * content_ratio = 0; + if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) { ContentList cl = content (); + Ratio const * content_ratio = 0; for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) { shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i); if (vc) { @@ -689,7 +700,6 @@ Film::dcp_name (bool if_created_now) const return name(); } - void Film::set_directory (boost::filesystem::path d) { @@ -740,13 +750,6 @@ Film::set_scaler (Scaler const * s) } void -Film::set_with_subtitles (bool w) -{ - _with_subtitles = w; - signal_changed (WITH_SUBTITLES); -} - -void Film::set_j2k_bandwidth (int b) { _j2k_bandwidth = b; @@ -789,6 +792,13 @@ Film::set_interop (bool i) } void +Film::set_burn_subtitles (bool b) +{ + _burn_subtitles = b; + signal_changed (BURN_SUBTITLES); +} + +void Film::signal_changed (Property p) { _dirty = true; @@ -869,7 +879,7 @@ Film::j2c_path (int f, Eyes e, bool t) const return file (p); } -/** Find all the DCPs in our directory that can be libdcp::DCP::read() and return details of their CPLs */ +/** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs */ vector<CPLSummary> Film::cpls () const { @@ -883,11 +893,14 @@ Film::cpls () const ) { try { - libdcp::DCP dcp (*i); + dcp::DCP dcp (*i); dcp.read (); out.push_back ( CPLSummary ( - i->path().leaf().string(), dcp.cpls().front()->id(), dcp.cpls().front()->name(), dcp.cpls().front()->filename() + i->path().leaf().string(), + dcp.cpls().front()->id(), + dcp.cpls().front()->annotation_text(), + dcp.cpls().front()->file() ) ); } catch (...) { @@ -932,6 +945,13 @@ Film::content () const } void +Film::examine_content (shared_ptr<Content> c) +{ + shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c)); + JobManager::instance()->add (j); +} + +void Film::examine_and_add_content (shared_ptr<Content> c) { if (dynamic_pointer_cast<FFmpegContent> (c)) { @@ -986,22 +1006,22 @@ Film::move_content_later (shared_ptr<Content> c) _playlist->move_later (c); } -Time +DCPTime Film::length () const { return _playlist->length (); } -bool -Film::has_subtitles () const +int +Film::best_video_frame_rate () const { - return _playlist->has_subtitles (); + return _playlist->best_dcp_frame_rate (); } -OutputVideoFrame -Film::best_video_frame_rate () const +FrameRateChange +Film::active_frame_rate_change (DCPTime t) const { - return _playlist->best_dcp_frame_rate (); + return _playlist->active_frame_rate_change (t, video_frame_rate ()); } void @@ -1022,31 +1042,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 */ @@ -1062,61 +1058,62 @@ 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 ()); + return fit_ratio_within (container()->ratio(), full_frame (), 1); } -/** @param from KDM from time in local time. - * @param to KDM to time in local time. - */ -libdcp::KDM +dcp::EncryptedKDM Film::make_kdm ( - shared_ptr<libdcp::Certificate> target, + dcp::Certificate target, boost::filesystem::path cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) const { - shared_ptr<const Signer> signer = make_signer (); - - time_t now = time (0); - struct tm* tm = localtime (&now); - string const issue_date = libdcp::tm_to_string (tm); + shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file)); + shared_ptr<const dcp::Signer> signer = Config::instance()->signer(); + if (!signer->valid ()) { + throw InvalidSignerError (); + } - return libdcp::KDM (cpl_file, signer, target, key (), from, until, "DCP-o-matic", issue_date, formulation); + return dcp::DecryptedKDM ( + cpl, key(), from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string() + ).encrypt (signer, target, formulation); } -list<libdcp::KDM> +list<dcp::EncryptedKDM> Film::make_kdms ( list<shared_ptr<Screen> > screens, boost::filesystem::path dcp, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) const { - list<libdcp::KDM> kdms; + list<dcp::EncryptedKDM> kdms; for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) { - kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until, formulation)); + if ((*i)->certificate) { + kdms.push_back (make_kdm ((*i)->certificate.get(), dcp, from, until, formulation)); + } } return kdms; @@ -1128,7 +1125,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 @@ -1146,10 +1143,3 @@ Film::should_be_enough_disk_space (double& required, double& available) const available = double (s.available) / 1073741824.0f; return (available - required) > 1; } - -FrameRateChange -Film::active_frame_rate_change (Time t) const -{ - return _playlist->active_frame_rate_change (t, video_frame_rate ()); -} - diff --git a/src/lib/film.h b/src/lib/film.h index b7d105688..8a0823094 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -31,8 +31,9 @@ #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/decrypted_kdm.h> +#include <dcp/encrypted_kdm.h> #include "util.h" #include "types.h" #include "isdcf_metadata.h" @@ -46,7 +47,7 @@ class Playlist; class AudioContent; class Scaler; class Screen; -class isdcf_name_test; +struct isdcf_name_test; /** @class Film * @@ -69,6 +70,7 @@ public: boost::filesystem::path video_mxf_filename () const; boost::filesystem::path audio_mxf_filename () const; + boost::filesystem::path subtitle_xml_filename () const; void send_dcp_to_tms (); void make_dcp (); @@ -97,20 +99,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::vector<CPLSummary> cpls () 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; @@ -118,29 +115,28 @@ public: /* Proxies for some Playlist methods */ ContentList content () const; - Time length () const; - bool has_subtitles () const; - OutputVideoFrame best_video_frame_rate () const; - FrameRateChange active_frame_rate_change (Time) const; + DCPTime length () const; + int best_video_frame_rate () const; + FrameRateChange active_frame_rate_change (DCPTime) const; - libdcp::KDM + dcp::EncryptedKDM make_kdm ( - boost::shared_ptr<libdcp::Certificate> target, + dcp::Certificate target, boost::filesystem::path cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) const; - std::list<libdcp::KDM> make_kdms ( + std::list<dcp::EncryptedKDM> make_kdms ( std::list<boost::shared_ptr<Screen> >, boost::filesystem::path cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) const; - libdcp::Key key () const { + dcp::Key key () const { return _key; } @@ -161,17 +157,18 @@ public: CONTAINER, RESOLUTION, SCALER, - WITH_SUBTITLES, SIGNED, ENCRYPTED, J2K_BANDWIDTH, ISDCF_METADATA, VIDEO_FRAME_RATE, AUDIO_CHANNELS, - /** The setting of _three_d has been changed */ + /** The setting of _three_d has changed */ THREE_D, SEQUENCE_VIDEO, INTEROP, + /** The setting of _burn_subtitles has changed */ + BURN_SUBTITLES, }; @@ -205,10 +202,6 @@ public: return _scaler; } - bool with_subtitles () const { - return _with_subtitles; - } - /* signed is a reserved word */ bool is_signed () const { return _signed; @@ -246,6 +239,10 @@ public: bool interop () const { return _interop; } + + bool burn_subtitles () const { + return _burn_subtitles; + } /* SET */ @@ -253,6 +250,7 @@ public: void set_directory (boost::filesystem::path); void set_name (std::string); void set_use_isdcf_name (bool); + void examine_content (boost::shared_ptr<Content>); void examine_and_add_content (boost::shared_ptr<Content>); void add_content (boost::shared_ptr<Content>); void remove_content (boost::shared_ptr<Content>); @@ -262,7 +260,6 @@ public: void set_container (Ratio const *); void set_resolution (Resolution); void set_scaler (Scaler const *); - void set_with_subtitles (bool); void set_signed (bool); void set_encrypted (bool); void set_j2k_bandwidth (int); @@ -273,6 +270,7 @@ public: void set_isdcf_date_today (); void set_sequence_video (bool); void set_interop (bool); + void set_burn_subtitles (bool); /** Emitted when some property has of the Film has changed */ mutable boost::signals2::signal<void (Property)> Changed; @@ -285,7 +283,7 @@ public: private: - friend class ::isdcf_name_test; + friend struct ::isdcf_name_test; void signal_changed (Property); std::string video_identifier () const; @@ -315,8 +313,6 @@ private: Resolution _resolution; /** Scaler algorithm to use */ Scaler const * _scaler; - /** True if subtitles should be shown for this film */ - bool _with_subtitles; bool _signed; bool _encrypted; /** bandwidth for J2K files in bits per second */ @@ -335,15 +331,16 @@ private: bool _three_d; bool _sequence_video; bool _interop; - libdcp::Key _key; + bool _burn_subtitles; + dcp::Key _key; int _state_version; /** true if our state has changed since we last saved it */ mutable bool _dirty; - friend class paths_test; - friend class film_metadata_test; + friend struct paths_test; + friend struct film_metadata_test; }; #endif diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 639992d70..2d8f83aa7 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -45,26 +45,29 @@ 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) - : _buffer_src_context (0) +FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p) + : _copy (false) + , _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) , _pixel_format (p) + , _frame (0) { - _frame = av_frame_alloc (); - - string filters = Filter::ffmpeg_string (content->filters()); + string const filters = Filter::ffmpeg_string (content->filters()); if (filters.empty ()) { - filters = "copy"; + _copy = true; + return; } + _frame = av_frame_alloc (); + AVFilterGraph* graph = avfilter_graph_alloc(); if (graph == 0) { throw DecodeError (N_("could not create filter graph.")); @@ -122,12 +125,15 @@ 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 () { - av_frame_free (&_frame); + if (_frame) { + av_frame_free (&_frame); + } } /** Take an AVFrame and process it using our configured filters, returning a @@ -138,19 +144,23 @@ FilterGraph::process (AVFrame* frame) { list<pair<shared_ptr<Image>, int64_t> > images; - if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) { - throw DecodeError (N_("could not push buffer into filter chain.")); - } - - while (true) { - if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) { - break; + if (_copy) { + images.push_back (make_pair (shared_ptr<Image> (new Image (frame)), av_frame_get_best_effort_timestamp (frame))); + } else { + if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) { + throw DecodeError (N_("could not push buffer into filter chain.")); + } + + while (true) { + if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) { + break; + } + + images.push_back (make_pair (shared_ptr<Image> (new Image (_frame)), av_frame_get_best_effort_timestamp (_frame))); + av_frame_unref (_frame); } - - images.push_back (make_pair (shared_ptr<Image> (new Image (_frame)), av_frame_get_best_effort_timestamp (_frame))); - av_frame_unref (_frame); } - + return images; } @@ -159,7 +169,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..5b43c5512 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -36,16 +36,18 @@ 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: + /** true if this graph has no filters in, so it just copies stuff straight through */ + bool _copy; 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/image.cc b/src/lib/image.cc index 066f12c07..0da2d1e48 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 @@ -30,6 +30,8 @@ extern "C" { #include "image.h" #include "exceptions.h" #include "scaler.h" +#include "timer.h" +#include "rect.h" #include "md5_digester.h" #include "i18n.h" @@ -38,8 +40,9 @@ using std::string; using std::min; using std::cout; using std::cerr; +using std::list; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; int Image::line_factor (int n) const @@ -83,7 +86,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 +102,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 +142,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 +173,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) { @@ -344,10 +347,33 @@ Image::make_black () } void +Image::make_transparent () +{ + if (_pixel_format != PIX_FMT_RGBA) { + throw PixelFormatError ("make_transparent()", _pixel_format); + } + + memset (data()[0], 0, lines(0) * stride()[0]); +} + +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); + assert (other->pixel_format() == PIX_FMT_RGBA); + int const other_bpp = 4; + + int this_bpp = 0; + switch (_pixel_format) { + case PIX_FMT_BGRA: + case PIX_FMT_RGBA: + this_bpp = 4; + break; + case PIX_FMT_RGB24: + this_bpp = 3; + break; + default: + assert (false); + } int start_tx = position.x; int start_ox = 0; @@ -366,15 +392,17 @@ 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] + start_tx * 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[0] = op[0] + (tp[0] * (1 - alpha)); + tp[1] = op[1] + (tp[1] * (1 - alpha)); + tp[2] = op[2] + (tp[2] * (1 - alpha)); + tp[3] = op[3] + (tp[3] * (1 - alpha)); + + tp += this_bpp; + op += other_bpp; } } } @@ -458,8 +486,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) { @@ -501,7 +529,7 @@ Image::allocate () } Image::Image (Image const & other) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other._pixel_format) , _aligned (other._aligned) { @@ -519,7 +547,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) { @@ -538,7 +566,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) { @@ -571,7 +599,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); @@ -614,7 +642,7 @@ Image::stride () const return _stride; } -libdcp::Size +dcp::Size Image::size () const { return _size; @@ -626,6 +654,31 @@ Image::aligned () const return _aligned; } +PositionImage +merge (list<PositionImage> images) +{ + if (images.empty ()) { + return PositionImage (); + } + + if (images.size() == 1) { + return images.front (); + } + + dcpomatic::Rect<int> all (images.front().position, images.front().image->size().width, images.front().image->size().height); + for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) { + all.extend (dcpomatic::Rect<int> (i->position, i->image->size().width, i->image->size().height)); + } + + shared_ptr<Image> merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true)); + merged->make_transparent (); + for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) { + merged->alpha_blend (i->image, i->position - all.position()); + } + + return PositionImage (merged, all.position ()); +} + string Image::digest () const { @@ -637,4 +690,137 @@ Image::digest () const return digester.get (); } - + +bool +operator== (Image const & a, Image const & b) +{ + if (a.components() != b.components() || a.pixel_format() != b.pixel_format() || a.aligned() != b.aligned()) { + return false; + } + + for (int c = 0; c < a.components(); ++c) { + if (a.lines(c) != b.lines(c) || a.line_size()[c] != b.line_size()[c] || a.stride()[c] != b.stride()[c]) { + return false; + } + + uint8_t* p = a.data()[c]; + uint8_t* q = b.data()[c]; + for (int y = 0; y < a.lines(c); ++y) { + if (memcmp (p, q, a.line_size()[c]) != 0) { + return false; + } + + p += a.stride()[c]; + q += b.stride()[c]; + } + } + + return true; +} + +void +Image::fade (float f) +{ + switch (_pixel_format) { + case PIX_FMT_YUV420P: + case PIX_FMT_YUV422P: + case PIX_FMT_YUV444P: + case PIX_FMT_YUV411P: + case PIX_FMT_YUVJ420P: + case PIX_FMT_YUVJ422P: + case PIX_FMT_YUVJ444P: + case PIX_FMT_RGB24: + case PIX_FMT_ARGB: + case PIX_FMT_RGBA: + case PIX_FMT_ABGR: + case PIX_FMT_BGRA: + case PIX_FMT_RGB555LE: + /* 8-bit */ + for (int c = 0; c < 3; ++c) { + uint8_t* p = data()[c]; + for (int y = 0; y < lines(c); ++y) { + uint8_t* q = p; + for (int x = 0; x < line_size()[c]; ++x) { + *q = int (float (*q) * f); + ++q; + } + p += stride()[c]; + } + } + break; + + case PIX_FMT_YUV422P9LE: + case PIX_FMT_YUV444P9LE: + case PIX_FMT_YUV422P10LE: + case PIX_FMT_YUV444P10LE: + case PIX_FMT_YUV422P16LE: + case PIX_FMT_YUV444P16LE: + case AV_PIX_FMT_YUVA420P9LE: + case AV_PIX_FMT_YUVA422P9LE: + case AV_PIX_FMT_YUVA444P9LE: + case AV_PIX_FMT_YUVA420P10LE: + case AV_PIX_FMT_YUVA422P10LE: + case AV_PIX_FMT_YUVA444P10LE: + /* 16-bit little-endian */ + for (int c = 0; c < 3; ++c) { + int const stride_pixels = stride()[c] / 2; + int const line_size_pixels = line_size()[c] / 2; + uint16_t* p = reinterpret_cast<uint16_t*> (data()[c]); + for (int y = 0; y < lines(c); ++y) { + uint16_t* q = p; + for (int x = 0; x < line_size_pixels; ++x) { + *q = int (float (*q) * f); + ++q; + } + p += stride_pixels; + } + } + break; + + case PIX_FMT_YUV422P9BE: + case PIX_FMT_YUV444P9BE: + case PIX_FMT_YUV444P10BE: + case PIX_FMT_YUV422P10BE: + case AV_PIX_FMT_YUVA420P9BE: + case AV_PIX_FMT_YUVA422P9BE: + case AV_PIX_FMT_YUVA444P9BE: + case AV_PIX_FMT_YUVA420P10BE: + case AV_PIX_FMT_YUVA422P10BE: + case AV_PIX_FMT_YUVA444P10BE: + case AV_PIX_FMT_YUVA420P16BE: + case AV_PIX_FMT_YUVA422P16BE: + case AV_PIX_FMT_YUVA444P16BE: + /* 16-bit big-endian */ + for (int c = 0; c < 3; ++c) { + int const stride_pixels = stride()[c] / 2; + int const line_size_pixels = line_size()[c] / 2; + uint16_t* p = reinterpret_cast<uint16_t*> (data()[c]); + for (int y = 0; y < lines(c); ++y) { + uint16_t* q = p; + for (int x = 0; x < line_size_pixels; ++x) { + *q = swap_16 (int (float (swap_16 (*q)) * f)); + ++q; + } + p += stride_pixels; + } + } + break; + + case PIX_FMT_UYVY422: + { + int const Y = lines(0); + int const X = line_size()[0]; + uint8_t* p = data()[0]; + for (int y = 0; y < Y; ++y) { + for (int x = 0; x < X; ++x) { + *p = int (float (*p) * f); + ++p; + } + } + break; + } + + default: + throw PixelFormatError ("fade()", _pixel_format); + } +} diff --git a/src/lib/image.h b/src/lib/image.h index f83bf6998..172250eb1 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -31,16 +31,17 @@ extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> } -#include <libdcp/image.h> +#include <dcp/image.h> #include "util.h" #include "position.h" +#include "position_image.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,21 +51,23 @@ 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 make_transparent (); void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos); void copy (boost::shared_ptr<const Image> image, Position<int> pos); + void fade (float); void read_from_socket (boost::shared_ptr<Socket>); void write_to_socket (boost::shared_ptr<Socket>) const; @@ -76,7 +79,7 @@ public: std::string digest () const; private: - friend class pixel_formats_test; + friend struct pixel_formats_test; void allocate (); void swap (Image &); @@ -91,4 +94,7 @@ private: bool _aligned; }; +extern PositionImage merge (std::list<PositionImage> images); +extern bool operator== (Image const & a, Image const & b); + #endif diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc index 915da7beb..84b0b75c9 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -20,11 +20,11 @@ #include <libcxml/cxml.h> #include "image_content.h" #include "image_examiner.h" -#include "config.h" #include "compose.hpp" #include "film.h" #include "job.h" #include "frame_rate_change.h" +#include "exceptions.h" #include "safe_stringstream.h" #include "i18n.h" @@ -55,7 +55,7 @@ ImageContent::ImageContent (shared_ptr<const Film> f, boost::filesystem::path p) } -ImageContent::ImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version) +ImageContent::ImageContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) : Content (f, node) , VideoContent (f, node, version) { @@ -109,13 +109,11 @@ ImageContent::examine (shared_ptr<Job> job) assert (film); shared_ptr<ImageExaminer> examiner (new ImageExaminer (film, shared_from_this(), job)); - take_from_video_examiner (examiner); - set_video_length (examiner->video_length ()); } void -ImageContent::set_video_length (VideoContent::Frame len) +ImageContent::set_video_length (ContentTime len) { { boost::mutex::scoped_lock lm (_mutex); @@ -125,14 +123,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); - - FrameRateChange frc (video_frame_rate(), film->video_frame_rate ()); - return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate(); + return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate(), film->video_frame_rate())); } string @@ -140,7 +136,7 @@ ImageContent::identifier () const { SafeStringStream 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..a1b1437c8 100644 --- a/src/lib/image_content.h +++ b/src/lib/image_content.h @@ -31,7 +31,7 @@ class ImageContent : public VideoContent { public: ImageContent (boost::shared_ptr<const Film>, boost::filesystem::path); - ImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int); + ImageContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int); boost::shared_ptr<ImageContent> shared_from_this () { return boost::dynamic_pointer_cast<ImageContent> (Content::shared_from_this ()); @@ -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 7a9acd9e4..8702c1a33 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -23,7 +23,7 @@ #include "image_content.h" #include "image_decoder.h" #include "image.h" -#include "image_proxy.h" +#include "magick_image_proxy.h" #include "film.h" #include "exceptions.h" @@ -31,43 +31,35 @@ 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; + if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) { + return true; } - if (_image && _image_content->still ()) { - video (_image, true, _video_position); - return; + if (!_image_content->still() || !_image) { + /* Either we need an image or we are using moving images, so load one */ + _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position), _image_content->film()->log ())); } - - shared_ptr<const Film> film = _film.lock (); - assert (film); - - _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position), film->log ())); - video (_image, false, _video_position); + + video (_image, _video_position); + ++_video_position; + return false; } void -ImageDecoder::seek (VideoContent::Frame frame, bool) -{ - _video_position = frame; -} - -bool -ImageDecoder::done () const +ImageDecoder::seek (ContentTime time, bool accurate) { - return _video_position >= _image_content->video_length (); + VideoDecoder::seek (time, accurate); + _video_position = time.frames (_image_content->video_frame_rate ()); } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 5b82dd85c..242f69477 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> c); 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<ImageProxy> _image; + VideoFrame _video_position; }; diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc index 4ff324f68..004b89e65 100644 --- a/src/lib/image_examiner.cc +++ b/src/lib/image_examiner.cc @@ -36,21 +36,20 @@ using boost::shared_ptr; 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::from_seconds (Config::instance()->default_still_length()); } else { - _video_length = _image_content->number_of_paths (); + _video_length = ContentTime::from_frames (_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/image_proxy.cc b/src/lib/image_proxy.cc index 3aba6cf7c..b6b387b76 100644 --- a/src/lib/image_proxy.cc +++ b/src/lib/image_proxy.cc @@ -17,10 +17,12 @@ */ -#include <Magick++.h> -#include <libdcp/util.h> -#include <libdcp/raw_convert.h> +#include <dcp/util.h> +#include <dcp/raw_convert.h> #include "image_proxy.h" +#include "raw_image_proxy.h" +#include "magick_image_proxy.h" +#include "j2k_image_proxy.h" #include "image.h" #include "exceptions.h" #include "cross.h" @@ -40,147 +42,6 @@ ImageProxy::ImageProxy (shared_ptr<Log> log) } -RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log) - : ImageProxy (log) - , _image (image) -{ - -} - -RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) - : ImageProxy (log) -{ - libdcp::Size size ( - xml->number_child<int> ("Width"), xml->number_child<int> ("Height") - ); - - _image.reset (new Image (static_cast<AVPixelFormat> (xml->number_child<int> ("PixelFormat")), size, true)); - _image->read_from_socket (socket); -} - -shared_ptr<Image> -RawImageProxy::image () const -{ - return _image; -} - -void -RawImageProxy::add_metadata (xmlpp::Node* node) const -{ - node->add_child("Type")->add_child_text (N_("Raw")); - node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width)); - node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height)); - node->add_child("PixelFormat")->add_child_text (libdcp::raw_convert<string> (_image->pixel_format ())); -} - -void -RawImageProxy::send_binary (shared_ptr<Socket> socket) const -{ - _image->write_to_socket (socket); -} - -MagickImageProxy::MagickImageProxy (boost::filesystem::path path, shared_ptr<Log> log) - : ImageProxy (log) -{ - /* Read the file into a Blob */ - - boost::uintmax_t const size = boost::filesystem::file_size (path); - FILE* f = fopen_boost (path, "rb"); - if (!f) { - throw OpenFileError (path); - } - - uint8_t* data = new uint8_t[size]; - if (fread (data, 1, size, f) != size) { - delete[] data; - throw ReadFileError (path); - } - - fclose (f); - _blob.update (data, size); - delete[] data; -} - -MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket, shared_ptr<Log> log) - : ImageProxy (log) -{ - uint32_t const size = socket->read_uint32 (); - uint8_t* data = new uint8_t[size]; - socket->read (data, size); - _blob.update (data, size); - delete[] data; -} - -shared_ptr<Image> -MagickImageProxy::image () const -{ - if (_image) { - return _image; - } - - LOG_TIMING ("[%1] MagickImageProxy begins decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); - - Magick::Image* magick_image = 0; - string error; - try { - magick_image = new Magick::Image (_blob); - } catch (Magick::Exception& e) { - error = e.what (); - } - - if (!magick_image) { - /* ImageMagick cannot auto-detect Targa files, it seems, so try here with an - explicit format. I can't find it documented that passing a (0, 0) geometry - is allowed, but it seems to work. - */ - try { - magick_image = new Magick::Image (_blob, Magick::Geometry (0, 0), "TGA"); - } catch (...) { - - } - } - - if (!magick_image) { - /* If we failed both an auto-detect and a forced-Targa we give the error from - the auto-detect. - */ - throw DecodeError (String::compose (_("Could not decode image file (%1)"), error)); - } - - LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ()); - - libdcp::Size size (magick_image->columns(), magick_image->rows()); - - _image.reset (new Image (PIX_FMT_RGB24, size, true)); - - /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */ - uint8_t* p = _image->data()[0]; - for (int i = 0; i < size.height; ++i) { - using namespace MagickCore; - magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p); - p += _image->stride()[0]; - } - - delete magick_image; - - LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); - - return _image; -} - -void -MagickImageProxy::add_metadata (xmlpp::Node* node) const -{ - node->add_child("Type")->add_child_text (N_("Magick")); -} - -void -MagickImageProxy::send_binary (shared_ptr<Socket> socket) const -{ - socket->write (_blob.length ()); - socket->write ((uint8_t *) _blob.data (), _blob.length ()); -} - shared_ptr<ImageProxy> image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) { @@ -188,6 +49,8 @@ image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shar return shared_ptr<ImageProxy> (new RawImageProxy (xml, socket, log)); } else if (xml->string_child("Type") == N_("Magick")) { return shared_ptr<MagickImageProxy> (new MagickImageProxy (xml, socket, log)); + } else if (xml->string_child("Type") == N_("J2K")) { + return shared_ptr<J2KImageProxy> (new J2KImageProxy (xml, socket, log)); } throw NetworkError (_("Unexpected image type received by server")); diff --git a/src/lib/image_proxy.h b/src/lib/image_proxy.h index c0ccd9125..7ff28e174 100644 --- a/src/lib/image_proxy.h +++ b/src/lib/image_proxy.h @@ -17,6 +17,9 @@ */ +#ifndef DCPOMATIC_IMAGE_PROXY_H +#define DCPOMATIC_IMAGE_PROXY_H + /** @file src/lib/image_proxy.h * @brief ImageProxy and subclasses. */ @@ -34,6 +37,11 @@ namespace cxml { class Node; } +namespace dcp { + class MonoPictureFrame; + class StereoPictureFrame; +} + /** @class ImageProxy * @brief A class which holds an Image, and can produce it on request. * @@ -42,7 +50,7 @@ namespace cxml { * of happening in a single-threaded decoder. * * For example, large TIFFs are slow to decode, so this class will keep - * the TIFF data TIFF until such a time that the actual image is needed. + * the TIFF data compressed until the decompressed image is needed. * At this point, the class decodes the TIFF to an Image. */ class ImageProxy : public boost::noncopyable @@ -55,38 +63,15 @@ public: virtual boost::shared_ptr<Image> image () const = 0; virtual void add_metadata (xmlpp::Node *) const = 0; virtual void send_binary (boost::shared_ptr<Socket>) const = 0; + /** @return true if our image is definitely the same as another, false if it is probably not */ + virtual bool same (boost::shared_ptr<const ImageProxy>) const { + return false; + } protected: boost::shared_ptr<Log> _log; }; -class RawImageProxy : public ImageProxy -{ -public: - RawImageProxy (boost::shared_ptr<Image>, boost::shared_ptr<Log> log); - RawImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); - - boost::shared_ptr<Image> image () const; - void add_metadata (xmlpp::Node *) const; - void send_binary (boost::shared_ptr<Socket>) const; - -private: - boost::shared_ptr<Image> _image; -}; - -class MagickImageProxy : public ImageProxy -{ -public: - MagickImageProxy (boost::filesystem::path, boost::shared_ptr<Log> log); - MagickImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); - - boost::shared_ptr<Image> image () const; - void add_metadata (xmlpp::Node *) const; - void send_binary (boost::shared_ptr<Socket>) const; - -private: - Magick::Blob _blob; - mutable boost::shared_ptr<Image> _image; -}; - boost::shared_ptr<ImageProxy> image_proxy_factory (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + +#endif diff --git a/src/lib/image_subtitle.h b/src/lib/image_subtitle.h new file mode 100644 index 000000000..b25943ae1 --- /dev/null +++ b/src/lib/image_subtitle.h @@ -0,0 +1,46 @@ +/* + 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_IMAGE_SUBTITLE_H +#define DCPOMATIC_IMAGE_SUBTITLE_H + +#include "rect.h" + +class Image; + +class ImageSubtitle +{ +public: + ImageSubtitle (boost::shared_ptr<Image> i, dcpomatic::Rect<double> r) + : image (i) + , rectangle (r) + {} + + boost::shared_ptr<Image> image; + /** Area that the subtitle covers on its corresponding video, expressed in + * proportions of the image size; e.g. rectangle.x = 0.5 would mean that + * the rectangle starts half-way across the video. + * + * This rectangle may or may not have had a SubtitleContent's offsets and + * scale applied to it, depending on context. + */ + dcpomatic::Rect<double> rectangle; +}; + +#endif diff --git a/src/lib/isdcf_metadata.cc b/src/lib/isdcf_metadata.cc index dfba50a9a..7d960b6ac 100644 --- a/src/lib/isdcf_metadata.cc +++ b/src/lib/isdcf_metadata.cc @@ -19,16 +19,16 @@ #include <iostream> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "isdcf_metadata.h" #include "i18n.h" using std::string; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; -ISDCFMetadata::ISDCFMetadata (shared_ptr<const cxml::Node> node) +ISDCFMetadata::ISDCFMetadata (cxml::ConstNodePtr node) { content_version = node->number_child<int> ("ContentVersion"); audio_language = node->string_child ("AudioLanguage"); diff --git a/src/lib/isdcf_metadata.h b/src/lib/isdcf_metadata.h index 0fb7e7baa..e63f290e4 100644 --- a/src/lib/isdcf_metadata.h +++ b/src/lib/isdcf_metadata.h @@ -22,10 +22,7 @@ #include <string> #include <libxml++/libxml++.h> - -namespace cxml { - class Node; -} +#include <libcxml/cxml.h> class ISDCFMetadata { @@ -38,7 +35,7 @@ public: , two_d_version_of_three_d (false) {} - ISDCFMetadata (boost::shared_ptr<const cxml::Node>); + ISDCFMetadata (cxml::ConstNodePtr); void as_xml (xmlpp::Node *) const; void read_old_metadata (std::string, std::string); diff --git a/src/lib/j2k_image_proxy.cc b/src/lib/j2k_image_proxy.cc new file mode 100644 index 000000000..6924fad79 --- /dev/null +++ b/src/lib/j2k_image_proxy.cc @@ -0,0 +1,123 @@ +/* + 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 <libcxml/cxml.h> +#include <dcp/raw_convert.h> +#include <dcp/mono_picture_frame.h> +#include <dcp/stereo_picture_frame.h> +#include "j2k_image_proxy.h" +#include "util.h" +#include "image.h" +#include "encoded_data.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +J2KImageProxy::J2KImageProxy (shared_ptr<const dcp::MonoPictureFrame> frame, dcp::Size size, shared_ptr<Log> log) + : ImageProxy (log) + , _mono (frame) + , _size (size) +{ + +} + +J2KImageProxy::J2KImageProxy (shared_ptr<const dcp::StereoPictureFrame> frame, dcp::Size size, dcp::Eye eye, shared_ptr<Log> log) + : ImageProxy (log) + , _stereo (frame) + , _size (size) + , _eye (eye) +{ + +} + +J2KImageProxy::J2KImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) + : ImageProxy (log) +{ + _size = dcp::Size (xml->number_child<int> ("Width"), xml->number_child<int> ("Height")); + if (xml->optional_number_child<int> ("Eye")) { + _eye = static_cast<dcp::Eye> (xml->number_child<int> ("Eye")); + int const left_size = xml->number_child<int> ("LeftSize"); + int const right_size = xml->number_child<int> ("RightSize"); + shared_ptr<dcp::StereoPictureFrame> f (new dcp::StereoPictureFrame ()); + socket->read (f->left_j2k_data(), left_size); + socket->read (f->right_j2k_data(), right_size); + _stereo = f; + } else { + int const size = xml->number_child<int> ("Size"); + shared_ptr<dcp::MonoPictureFrame> f (new dcp::MonoPictureFrame ()); + socket->read (f->j2k_data (), size); + _mono = f; + } +} + +shared_ptr<Image> +J2KImageProxy::image () const +{ + shared_ptr<Image> image (new Image (PIX_FMT_RGB24, _size, false)); + + if (_mono) { + _mono->rgb_frame (image->data()[0]); + } else { + _stereo->rgb_frame (_eye, image->data()[0]); + } + + return shared_ptr<Image> (new Image (image, true)); +} + +void +J2KImageProxy::add_metadata (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text (N_("J2K")); + node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_size.width)); + node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_size.height)); + if (_stereo) { + node->add_child("Eye")->add_child_text (dcp::raw_convert<string> (_eye)); + node->add_child("LeftSize")->add_child_text (dcp::raw_convert<string> (_stereo->left_j2k_size ())); + node->add_child("RightSize")->add_child_text (dcp::raw_convert<string> (_stereo->right_j2k_size ())); + } else { + node->add_child("Size")->add_child_text (dcp::raw_convert<string> (_mono->j2k_size ())); + } +} + +void +J2KImageProxy::send_binary (shared_ptr<Socket> socket) const +{ + if (_mono) { + socket->write (_mono->j2k_data(), _mono->j2k_size ()); + } else { + socket->write (_stereo->left_j2k_data(), _stereo->left_j2k_size ()); + socket->write (_stereo->right_j2k_data(), _stereo->right_j2k_size ()); + } +} + +shared_ptr<EncodedData> +J2KImageProxy::j2k () const +{ + if (_mono) { + return shared_ptr<EncodedData> (new EncodedData (_mono->j2k_data(), _mono->j2k_size())); + } else { + if (_eye == dcp::EYE_LEFT) { + return shared_ptr<EncodedData> (new EncodedData (_stereo->left_j2k_data(), _stereo->left_j2k_size())); + } else { + return shared_ptr<EncodedData> (new EncodedData (_stereo->right_j2k_data(), _stereo->right_j2k_size())); + } + } +} diff --git a/src/lib/j2k_image_proxy.h b/src/lib/j2k_image_proxy.h new file mode 100644 index 000000000..d7b5c83fc --- /dev/null +++ b/src/lib/j2k_image_proxy.h @@ -0,0 +1,46 @@ +/* + 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/util.h> +#include "image_proxy.h" + +class EncodedData; + +class J2KImageProxy : public ImageProxy +{ +public: + J2KImageProxy (boost::shared_ptr<const dcp::MonoPictureFrame> frame, dcp::Size, boost::shared_ptr<Log> log); + J2KImageProxy (boost::shared_ptr<const dcp::StereoPictureFrame> frame, dcp::Size, dcp::Eye, boost::shared_ptr<Log> log); + J2KImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + + boost::shared_ptr<Image> image () const; + void add_metadata (xmlpp::Node *) const; + void send_binary (boost::shared_ptr<Socket>) const; + + boost::shared_ptr<EncodedData> j2k () const; + dcp::Size size () const { + return _size; + } + +private: + boost::shared_ptr<const dcp::MonoPictureFrame> _mono; + boost::shared_ptr<const dcp::StereoPictureFrame> _stereo; + dcp::Size _size; + dcp::Eye _eye; +}; diff --git a/src/lib/job.cc b/src/lib/job.cc index 52ec1426c..1d3cb73b6 100644 --- a/src/lib/job.cc +++ b/src/lib/job.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 @@ -23,13 +23,14 @@ #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" #include "ui_signaller.h" #include "exceptions.h" -#include "safe_stringstream.h" +#include "film.h" +#include "log.h" #include "i18n.h" @@ -66,8 +67,8 @@ 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()); try { @@ -204,7 +205,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 { @@ -279,6 +280,7 @@ Job::error_summary () const void Job::set_error (string s, string d) { + _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d), Log::TYPE_ERROR); boost::mutex::scoped_lock lm (_state_mutex); _error_summary = s; _error_details = d; diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc index f5054b8ed..108860594 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -21,7 +21,8 @@ #include <boost/shared_ptr.hpp> #include <quickmail.h> #include <zip.h> -#include <libdcp/kdm.h> +#include <dcp/encrypted_kdm.h> +#include <dcp/types.h> #include "kdm.h" #include "cinema.h" #include "exceptions.h" @@ -37,13 +38,13 @@ using boost::shared_ptr; struct ScreenKDM { - ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k) + ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k) : screen (s) , kdm (k) {} shared_ptr<Screen> screen; - libdcp::KDM kdm; + dcp::EncryptedKDM kdm; }; static string @@ -104,17 +105,17 @@ make_screen_kdms ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ) { - list<libdcp::KDM> kdms = film->make_kdms (screens, cpl, from, to, formulation); + list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, cpl, from, to, formulation); list<ScreenKDM> screen_kdms; list<shared_ptr<Screen> >::iterator i = screens.begin (); - list<libdcp::KDM>::iterator j = kdms.begin (); + list<dcp::EncryptedKDM>::iterator j = kdms.begin (); while (i != screens.end() && j != kdms.end ()) { screen_kdms.push_back (ScreenKDM (*i, *j)); ++i; @@ -129,9 +130,9 @@ make_cinema_kdms ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ) { list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, cpl, from, to, formulation); @@ -175,9 +176,9 @@ write_kdm_files ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ) { @@ -196,9 +197,9 @@ write_kdm_zip_files ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ) { @@ -216,9 +217,9 @@ email_kdms ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ) { list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, cpl, from, to, formulation); diff --git a/src/lib/kdm.h b/src/lib/kdm.h index 8fb4ec494..b80ef454f 100644 --- a/src/lib/kdm.h +++ b/src/lib/kdm.h @@ -27,9 +27,9 @@ extern void write_kdm_files ( boost::shared_ptr<const Film> film, std::list<boost::shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ); @@ -37,9 +37,9 @@ extern void write_kdm_zip_files ( boost::shared_ptr<const Film> film, std::list<boost::shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ); @@ -47,8 +47,8 @@ extern void email_kdms ( boost::shared_ptr<const Film> film, std::list<boost::shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ); diff --git a/src/lib/magick_image_proxy.cc b/src/lib/magick_image_proxy.cc new file mode 100644 index 000000000..e5265187f --- /dev/null +++ b/src/lib/magick_image_proxy.cc @@ -0,0 +1,152 @@ +/* + 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 <Magick++.h> +#include "magick_image_proxy.h" +#include "cross.h" +#include "exceptions.h" +#include "util.h" +#include "log.h" +#include "image.h" +#include "log.h" + +#include "i18n.h" + +#define LOG_TIMING(...) _log->microsecond_log (String::compose (__VA_ARGS__), Log::TYPE_TIMING); + +using std::string; +using std::cout; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +MagickImageProxy::MagickImageProxy (boost::filesystem::path path, shared_ptr<Log> log) + : ImageProxy (log) +{ + /* Read the file into a Blob */ + + boost::uintmax_t const size = boost::filesystem::file_size (path); + FILE* f = fopen_boost (path, "rb"); + if (!f) { + throw OpenFileError (path); + } + + uint8_t* data = new uint8_t[size]; + if (fread (data, 1, size, f) != size) { + delete[] data; + throw ReadFileError (path); + } + + fclose (f); + _blob.update (data, size); + delete[] data; +} + +MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket, shared_ptr<Log> log) + : ImageProxy (log) +{ + uint32_t const size = socket->read_uint32 (); + uint8_t* data = new uint8_t[size]; + socket->read (data, size); + _blob.update (data, size); + delete[] data; +} + +shared_ptr<Image> +MagickImageProxy::image () const +{ + if (_image) { + return _image; + } + + LOG_TIMING ("[%1] MagickImageProxy begins decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); + + Magick::Image* magick_image = 0; + string error; + try { + magick_image = new Magick::Image (_blob); + } catch (Magick::Exception& e) { + error = e.what (); + } + + if (!magick_image) { + /* ImageMagick cannot auto-detect Targa files, it seems, so try here with an + explicit format. I can't find it documented that passing a (0, 0) geometry + is allowed, but it seems to work. + */ + try { + magick_image = new Magick::Image (_blob, Magick::Geometry (0, 0), "TGA"); + } catch (...) { + + } + } + + if (!magick_image) { + /* If we failed both an auto-detect and a forced-Targa we give the error from + the auto-detect. + */ + throw DecodeError (String::compose (_("Could not decode image file (%1)"), error)); + } + + dcp::Size size (magick_image->columns(), magick_image->rows()); + LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ()); + + _image.reset (new Image (PIX_FMT_RGB24, size, true)); + + /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */ + uint8_t* p = _image->data()[0]; + for (int i = 0; i < size.height; ++i) { + using namespace MagickCore; + magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p); + p += _image->stride()[0]; + } + + delete magick_image; + + LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); + + return _image; +} + +void +MagickImageProxy::add_metadata (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text (N_("Magick")); +} + +void +MagickImageProxy::send_binary (shared_ptr<Socket> socket) const +{ + socket->write (_blob.length ()); + socket->write ((uint8_t *) _blob.data (), _blob.length ()); +} + +bool +MagickImageProxy::same (shared_ptr<const ImageProxy> other) const +{ + shared_ptr<const MagickImageProxy> mp = dynamic_pointer_cast<const MagickImageProxy> (other); + if (!mp) { + return false; + } + + if (_blob.length() != mp->_blob.length()) { + return false; + } + + return memcmp (_blob.data(), mp->_blob.data(), _blob.length()) == 0; +} diff --git a/src/lib/magick_image_proxy.h b/src/lib/magick_image_proxy.h new file mode 100644 index 000000000..8b43d0a00 --- /dev/null +++ b/src/lib/magick_image_proxy.h @@ -0,0 +1,36 @@ +/* + 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 "image_proxy.h" + +class MagickImageProxy : public ImageProxy +{ +public: + MagickImageProxy (boost::filesystem::path, boost::shared_ptr<Log> log); + MagickImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + + boost::shared_ptr<Image> image () const; + void add_metadata (xmlpp::Node *) const; + void send_binary (boost::shared_ptr<Socket>) const; + bool same (boost::shared_ptr<const ImageProxy> other) const; + +private: + Magick::Blob _blob; + mutable boost::shared_ptr<Image> _image; +}; diff --git a/src/lib/mid_side_decoder.cc b/src/lib/mid_side_decoder.cc new file mode 100644 index 000000000..be82f6754 --- /dev/null +++ b/src/lib/mid_side_decoder.cc @@ -0,0 +1,72 @@ +/* + 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 "mid_side_decoder.h" +#include "audio_buffers.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +string +MidSideDecoder::name () const +{ + return _("Mid-side decoder"); +} + +string +MidSideDecoder::id () const +{ + return N_("mid-side-decoder"); +} + +ChannelCount +MidSideDecoder::in_channels () const +{ + return ChannelCount (2); +} + +int +MidSideDecoder::out_channels (int) const +{ + return 3; +} + +shared_ptr<AudioProcessor> +MidSideDecoder::clone (int) const +{ + return shared_ptr<AudioProcessor> (new MidSideDecoder ()); +} + +shared_ptr<AudioBuffers> +MidSideDecoder::run (shared_ptr<const AudioBuffers> in) +{ + shared_ptr<AudioBuffers> out (new AudioBuffers (3, in->frames ())); + for (int i = 0; i < in->frames(); ++i) { + float const left = in->data()[0][i]; + float const right = in->data()[1][i]; + float const mid = (left + right) / 2; + out->data()[0][i] = left - mid; + out->data()[1][i] = right - mid; + out->data()[2][i] = mid; + } + + return out; +} diff --git a/src/lib/mid_side_decoder.h b/src/lib/mid_side_decoder.h new file mode 100644 index 000000000..dac6cb7d9 --- /dev/null +++ b/src/lib/mid_side_decoder.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 "audio_processor.h" + +class MidSideDecoder : public AudioProcessor +{ +public: + std::string name () const; + std::string id () const; + ChannelCount in_channels () const; + int out_channels (int) const; + boost::shared_ptr<AudioProcessor> clone (int) const; + boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<const AudioBuffers>); +}; + + diff --git a/src/lib/piece.cc b/src/lib/piece.cc deleted file mode 100644 index 494fb17a0..000000000 --- a/src/lib/piece.cc +++ /dev/null @@ -1,84 +0,0 @@ -/* - 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 "piece.h" -#include "player.h" - -using boost::shared_ptr; - -Piece::Piece (shared_ptr<Content> c) - : content (c) - , video_position (c->position ()) - , audio_position (c->position ()) - , repeat_to_do (0) - , repeat_done (0) -{ - -} - -Piece::Piece (shared_ptr<Content> c, shared_ptr<Decoder> d) - : content (c) - , decoder (d) - , video_position (c->position ()) - , audio_position (c->position ()) - , repeat_to_do (0) - , repeat_done (0) -{ - -} - -/** Set this piece to repeat a video frame a given number of times */ -void -Piece::set_repeat (IncomingVideo video, int num) -{ - repeat_video = video; - repeat_to_do = num; - repeat_done = 0; -} - -void -Piece::reset_repeat () -{ - repeat_video.image.reset (); - repeat_to_do = 0; - repeat_done = 0; -} - -bool -Piece::repeating () const -{ - return repeat_done != repeat_to_do; -} - -void -Piece::repeat (Player* player) -{ - player->process_video ( - repeat_video.weak_piece, - repeat_video.image, - repeat_video.eyes, - repeat_video.part, - repeat_done > 0, - repeat_video.frame, - (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ()) - ); - - ++repeat_done; -} - diff --git a/src/lib/piece.h b/src/lib/piece.h index 17b87b884..976409381 100644 --- a/src/lib/piece.h +++ b/src/lib/piece.h @@ -25,42 +25,19 @@ class Content; class Decoder; -class Piece; -class ImageProxy; -class Player; - -struct IncomingVideo -{ -public: - boost::weak_ptr<Piece> weak_piece; - boost::shared_ptr<const ImageProxy> image; - Eyes eyes; - Part part; - bool same; - VideoContent::Frame frame; - Time extra; -}; class Piece { public: - Piece (boost::shared_ptr<Content> c); - Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d); - void set_repeat (IncomingVideo video, int num); - void reset_repeat (); - bool repeating () const; - void repeat (Player* player); - + Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d, FrameRateChange f) + : content (c) + , decoder (d) + , frc (f) + {} + boost::shared_ptr<Content> content; boost::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; }; #endif diff --git a/src/lib/player.cc b/src/lib/player.cc index 8063d1212..db99cd2ad 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -17,26 +17,38 @@ */ -#include <stdint.h> #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 "dcp_content.h" #include "playlist.h" #include "job.h" #include "image.h" -#include "image_proxy.h" +#include "raw_image_proxy.h" #include "ratio.h" -#include "resampler.h" #include "log.h" #include "scaler.h" -#include "player_video_frame.h" +#include "render_subtitles.h" +#include "config.h" +#include "content_video.h" +#include "player_video.h" #include "frame_rate_change.h" +#include "dcp_content.h" +#include "dcp_decoder.h" +#include "dcp_subtitle_content.h" +#include "dcp_subtitle_decoder.h" +#include <boost/foreach.hpp> +#include <stdint.h> +#include <algorithm> #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); @@ -44,23 +56,21 @@ using std::list; using std::cout; using std::min; using std::max; +using std::min; using std::vector; using std::pair; using std::map; +using std::make_pair; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; +using boost::optional; Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) : _film (f) , _playlist (p) - , _video (true) - , _audio (true) , _have_valid_pieces (false) - , _video_position (0) - , _audio_position (0) - , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1)) - , _last_emit_was_black (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)); @@ -69,579 +79,508 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) } void -Player::disable_video () -{ - _video = false; -} - -void -Player::disable_audio () +Player::setup_pieces () { - _audio = false; -} + list<shared_ptr<Piece> > old_pieces = _pieces; + _pieces.clear (); -bool -Player::pass () -{ - if (!_have_valid_pieces) { - setup_pieces (); - } + ContentList content = _playlist->content (); - Time earliest_t = TIME_MAX; - shared_ptr<Piece> earliest; - enum { - VIDEO, - AUDIO - } type = VIDEO; + for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { - for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done () || (*i)->content->length_after_trim() == 0) { + if (!(*i)->paths_valid ()) { continue; } - - shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder); - shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); - - if (_video && vd) { - if ((*i)->video_position < earliest_t) { - earliest_t = (*i)->video_position; - earliest = *i; - type = VIDEO; + + 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; } } - if (_audio && ad && ad->has_audio ()) { - if ((*i)->audio_position < earliest_t) { - earliest_t = (*i)->audio_position; - earliest = *i; - type = AUDIO; - } + 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 ()); } - } - if (!earliest) { - flush (); - return true; - } + /* FFmpeg */ + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + if (fc) { + decoder.reset (new FFmpegDecoder (fc, _film->log())); + frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); + } - switch (type) { - case VIDEO: - if (earliest_t > _video_position) { - emit_black (); - } else { - if (earliest->repeating ()) { - earliest->repeat (this); - } else { - earliest->decoder->pass (); - } + shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (*i); + if (dc) { + decoder.reset (new DCPDecoder (dc, _film->log ())); + frc = FrameRateChange (dc->video_frame_rate(), _film->video_frame_rate()); } - 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() * ac->output_audio_frame_rate() / ac->content_audio_frame_rate(), - true - ); - } + /* ImageContent */ + shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i); + if (ic) { + /* 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) { + decoder = imd; } } - } - break; - } - 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; + if (!decoder) { + decoder.reset (new ImageDecoder (ic)); } - 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); - } + frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate()); } - 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 ()); + /* SndfileContent */ + shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); + if (sc) { + decoder.reset (new SndfileDecoder (sc)); + frc = best_overlap_frc; } - } - - 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 ImageProxy> image, Eyes eyes, Part part, bool same, VideoContent::Frame frame, Time extra) -{ - /* 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.part = part; - _last_incoming_video.same = same; - _last_incoming_video.frame = frame; - _last_incoming_video.extra = extra; - - shared_ptr<Piece> piece = weak_piece.lock (); - if (!piece) { - return; - } - - shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content); - assert (content); - - FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate()); - if (frc.skip && (frame % 2) == 1) { - return; - } - - Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate()); - if (content->trimmed (relative_time)) { - return; - } - - 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<PlayerVideoFrame> pi ( - new PlayerVideoFrame ( - image, - content->crop(), - image_size, - _video_container_size, - _film->scaler(), - eyes, - part, - content->colour_conversion() - ) - ); - - if (_film->with_subtitles ()) { - for (list<Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - if (i->covers (time)) { - /* This may be true for more than one of _subtitles, but the last (latest-starting) - one is the one we want to use, so that's ok. - */ - Position<int> const container_offset ( - (_video_container_size.width - image_size.width) / 2, - (_video_container_size.height - image_size.width) / 2 - ); - - pi->set_subtitle (i->out_image(), i->out_position() + container_offset); - } + /* SubRipContent */ + shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i); + if (rc) { + decoder.reset (new SubRipDecoder (rc)); + frc = best_overlap_frc; } - } - /* Clear out old subtitles */ - for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ) { - list<Subtitle>::iterator j = i; - ++j; - - if (i->ends_before (time)) { - _subtitles.erase (i); + /* DCPSubtitleContent */ + shared_ptr<const DCPSubtitleContent> dsc = dynamic_pointer_cast<const DCPSubtitleContent> (*i); + if (dsc) { + decoder.reset (new DCPSubtitleDecoder (dsc)); + frc = best_overlap_frc; } - i = j; + _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ()))); } -#ifdef DCPOMATIC_DEBUG - _last_video = piece->content; -#endif + _have_valid_pieces = true; +} - Video (pi, same, time); +void +Player::content_changed (weak_ptr<Content> w, int property, bool frequent) +{ + shared_ptr<Content> c = w.lock (); + if (!c) { + return; + } - _last_emit_was_black = false; - _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate()); + if ( + property == ContentProperty::POSITION || + property == ContentProperty::LENGTH || + property == ContentProperty::TRIM_START || + property == ContentProperty::TRIM_END || + property == ContentProperty::PATH || + property == VideoContentProperty::VIDEO_FRAME_TYPE || + property == DCPContentProperty::CAN_BE_PLAYED + ) { + + _have_valid_pieces = false; + Changed (frequent); - if (frc.repeat > 1 && !piece->repeating ()) { - piece->set_repeat (_last_incoming_video, frc.repeat - 1); + } else if ( + property == SubtitleContentProperty::USE_SUBTITLES || + property == SubtitleContentProperty::SUBTITLE_X_OFFSET || + property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || + property == SubtitleContentProperty::SUBTITLE_X_SCALE || + property == SubtitleContentProperty::SUBTITLE_Y_SCALE || + property == VideoContentProperty::VIDEO_CROP || + property == VideoContentProperty::VIDEO_SCALE || + property == VideoContentProperty::VIDEO_FRAME_RATE + ) { + + Changed (frequent); } } /** @param already_resampled true if this data has already been through the chain up to the resampler */ void -Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame, bool already_resampled) +Player::playlist_changed () { - shared_ptr<Piece> piece = weak_piece.lock (); - if (!piece) { - return; - } + _have_valid_pieces = false; + Changed (false); +} - shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content); - assert (content); +void +Player::set_video_container_size (dcp::Size s) +{ + _video_container_size = s; - if (!already_resampled) { - /* Gain */ - if (content->audio_gain() != 0) { - shared_ptr<AudioBuffers> gain (new AudioBuffers (audio)); - 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; - } - } - - Time const relative_time = _film->audio_frames_to_time (frame); + _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true)); + _black_image->make_black (); +} - if (content->trimmed (relative_time)) { - return; - } +void +Player::film_changed (Film::Property p) +{ + /* Here we should notice Film properties that affect our output, and + alert listeners that our output now would be different to how it was + last time we were run. + */ - 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())); - 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) { - dcp_mapped->accumulate_channel ( - audio.get(), - i, - static_cast<libdcp::Channel> (j), - map.get (i, static_cast<libdcp::Channel> (j)) - ); - } - } + if (p == Film::SCALER || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { + Changed (false); } +} - audio = 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 ()) { - return; +list<PositionImage> +Player::transform_image_subtitles (list<ImageSubtitle> subs) const +{ + list<PositionImage> all; + + for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if (!i->image) { + continue; } - shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames)); - trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0); - - audio = trimmed; - time = 0; + /* We will scale the subtitle up to fit _video_container_size */ + dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height); + + /* Then we need a corrective translation, consisting of two parts: + * + * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be + * rect.x * _video_container_size.width and rect.y * _video_container_size.height. + * + * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be + * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and + * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2). + * + * Combining these two translations gives these expressions. + */ + + all.push_back ( + PositionImage ( + i->image->scale ( + scaled_size, + Scaler::from_id ("bicubic"), + i->image->pixel_format (), + true + ), + Position<int> ( + rint (_video_container_size.width * i->rectangle.x), + rint (_video_container_size.height * i->rectangle.y) + ) + ) + ); } - _audio_merger.push (audio, time); - piece->audio_position += _film->audio_frames_to_time (audio->frames ()); + return all; } void -Player::flush () +Player::set_approximate_size () { - TimedAudioBuffers<Time> tb = _audio_merger.flush (); - if (_audio && tb.audio) { - Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); - } - - while (_video && _video_position < _audio_position) { - emit_black (); - } + _approximate_size = true; +} - while (_audio && _audio_position < _video_position) { - emit_silence (_film->time_to_audio_frames (_video_position - _audio_position)); - } - +shared_ptr<PlayerVideo> +Player::black_player_video_frame (DCPTime time) const +{ + return shared_ptr<PlayerVideo> ( + new PlayerVideo ( + shared_ptr<const ImageProxy> (new RawImageProxy (_black_image, _film->log ())), + time, + Crop (), + optional<float> (), + _video_container_size, + _video_container_size, + Scaler::from_id ("bicubic"), + EYES_BOTH, + PART_WHOLE, + Config::instance()->colour_conversions().front().conversion + ) + ); } -/** Seek so that the next pass() will yield (approximately) the requested frame. - * Pass accurate = true to try harder to get close to the request. - * @return true on error - */ -void -Player::seek (Time t, bool accurate) +/** @return All PlayerVideos at the given time (there may be two frames for 3D) */ +list<shared_ptr<PlayerVideo> > +Player::get_video (DCPTime time, bool accurate) { if (!_have_valid_pieces) { setup_pieces (); } + + list<shared_ptr<Piece> > ov = overlaps<VideoContent> ( + time, + time + DCPTime::from_frames (1, _film->video_frame_rate ()) + ); - if (_pieces.empty ()) { - return; - } + list<shared_ptr<PlayerVideo> > pvf; - 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; + if (ov.empty ()) { + /* No video content at this time */ + pvf.push_back (black_player_video_frame (time)); + } else { + /* Create a PlayerVideo from the content's video at this time */ + + shared_ptr<Piece> piece = ov.back (); + shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder); + assert (decoder); + shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content); + assert (content); + + list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate); + if (content_video.empty ()) { + pvf.push_back (black_player_video_frame (time)); + return pvf; } + + dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size (), _approximate_size ? 4 : 1); + if (_approximate_size) { + image_size.width &= ~3; + image_size.height &= ~3; + } + + for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) { + pvf.push_back ( + shared_ptr<PlayerVideo> ( + new PlayerVideo ( + i->image, + content_video_to_dcp (piece, i->frame), + content->crop (), + content->fade (i->frame), + image_size, + _video_container_size, + _film->scaler(), + i->eyes, + i->part, + content->colour_conversion () + ) + ) + ); + } + } - /* 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); - - /* Hence set the piece positions to the `global' time */ - (*i)->video_position = (*i)->audio_position = vc->position() + s; + /* Add subtitles (for possible burn-in) to whatever PlayerVideos we got */ - /* And seek the decoder */ - dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek ( - vc->time_to_content_video_frames (s + vc->trim_start ()), accurate - ); + PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false); - (*i)->reset_repeat (); - } + list<PositionImage> sub_images; - _video_position = _audio_position = t; + /* Image subtitles */ + list<PositionImage> c = transform_image_subtitles (ps.image); + copy (c.begin(), c.end(), back_inserter (sub_images)); - /* XXX: don't seek audio because we don't need to... */ + /* Text subtitles (rendered to images) */ + sub_images.push_back (render_subtitles (ps.text, _video_container_size)); + + if (!sub_images.empty ()) { + for (list<shared_ptr<PlayerVideo> >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) { + (*i)->set_subtitle (merge (sub_images)); + } + } + + return pvf; } -void -Player::setup_pieces () +shared_ptr<AudioBuffers> +Player::get_audio (DCPTime time, DCPTime length, bool accurate) { - list<shared_ptr<Piece> > old_pieces = _pieces; + if (!_have_valid_pieces) { + setup_pieces (); + } - _pieces.clear (); + AudioFrame const length_frames = length.frames (_film->audio_frame_rate ()); - ContentList content = _playlist->content (); - sort (content.begin(), content.end(), ContentSorter ()); + shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames)); + audio->make_silent (); + + list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length); + if (ov.empty ()) { + return audio; + } - for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { + for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) { - if (!(*i)->paths_valid ()) { + shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content); + assert (content); + shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); + assert (decoder); + + if (content->audio_frame_rate() == 0) { + /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no + * audio stream). + */ continue; } - shared_ptr<Piece> piece (new Piece (*i)); + /* The time that we should request from the content */ + DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0); + DCPTime offset; + if (request < DCPTime ()) { + /* We went off the start of the content, so we will need to offset + the stuff we get back. + */ + offset = -request; + request = DCPTime (); + } - /* XXX: into content? */ + AudioFrame const content_frame = dcp_to_content_audio (*i, request); - 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, _5, 0)); - fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2, false)); - fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4)); + /* Audio from this piece's decoder (which might be more or less than what we asked for) */ + shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate); - fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true); - piece->decoder = fd; + /* Gain */ + if (content->audio_gain() != 0) { + shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio)); + gain->apply_gain (content->audio_gain ()); + all->audio = gain; } - - 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; - } - } - 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, _5, 0)); - piece->decoder = id; + /* Remap channels */ + shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->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<dcp::Channel> (j)) > 0) { + dcp_mapped->accumulate_channel ( + all->audio.get(), + i, + j, + map.get (i, static_cast<dcp::Channel> (j)) + ); + } } } + + all->audio = dcp_mapped; - 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, false)); - - piece->decoder = sd; - } - - _pieces.push_back (piece); + audio->accumulate_frames ( + all->audio.get(), + content_frame - all->frame, + offset.frames (_film->audio_frame_rate()), + min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ()) + ); } - _have_valid_pieces = true; + return audio; } -void -Player::content_changed (weak_ptr<Content> w, int property, bool frequent) +VideoFrame +Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const { - shared_ptr<Content> c = w.lock (); - if (!c) { - return; - } + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (DCPTime::Type (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - if ( - property == ContentProperty::POSITION || property == ContentProperty::LENGTH || - property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END || - property == VideoContentProperty::VIDEO_FRAME_TYPE - ) { - - _have_valid_pieces = false; - Changed (frequent); - - } else if ( - property == SubtitleContentProperty::SUBTITLE_X_OFFSET || - property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || - property == SubtitleContentProperty::SUBTITLE_X_SCALE || - property == SubtitleContentProperty::SUBTITLE_Y_SCALE - ) { - - for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - i->update (_film, _video_container_size); - } - - Changed (frequent); - - } else if ( - property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE || - property == VideoContentProperty::VIDEO_FRAME_RATE - ) { - - Changed (frequent); - - } else if (property == ContentProperty::PATH) { - - _have_valid_pieces = false; - Changed (frequent); - } + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor (); } -void -Player::playlist_changed () +DCPTime +Player::content_video_to_dcp (shared_ptr<const Piece> piece, VideoFrame f) const { - _have_valid_pieces = false; - Changed (false); + DCPTime t = DCPTime::from_frames (f / piece->frc.factor (), _film->video_frame_rate()) - piece->content->trim_start () + piece->content->position (); + if (t < DCPTime ()) { + t = DCPTime (); + } + + return t; } -void -Player::set_video_container_size (libdcp::Size s) +AudioFrame +Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const { - _video_container_size = s; + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (DCPTime::Type (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true)); - im->make_black (); - - _black_frame.reset ( - new PlayerVideoFrame ( - shared_ptr<ImageProxy> (new RawImageProxy (im, _film->log ())), - Crop(), - _video_container_size, - _video_container_size, - Scaler::from_id ("bicubic"), - EYES_BOTH, - PART_WHOLE, - ColourConversion () - ) - ); + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate()); } -shared_ptr<Resampler> -Player::resampler (shared_ptr<AudioContent> c, bool create) +ContentTime +Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const { - 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> (); - } - - LOG_GENERAL ( - "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels() - ); + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (DCPTime::Type (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); - _resamplers[c] = r; - return r; + return ContentTime (s + piece->content->trim_start(), piece->frc); } void -Player::emit_black () +PlayerStatistics::dump (shared_ptr<Log> log) const { -#ifdef DCPOMATIC_DEBUG - _last_video.reset (); -#endif - - Video (_black_frame, _last_emit_was_black, _video_position); - _video_position += _film->video_frames_to_time (1); - _last_emit_was_black = true; + log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat), Log::TYPE_GENERAL); + log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()), Log::TYPE_GENERAL); } -void -Player::emit_silence (OutputAudioFrame most) +PlayerStatistics const & +Player::statistics () const { - if (most == 0) { - return; - } - - OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2); - shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N)); - silence->make_silent (); - Audio (silence, _audio_position); - _audio_position += _film->audio_frames_to_time (N); + return _statistics; } -void -Player::film_changed (Film::Property p) +PlayerSubtitles +Player::get_subtitles (DCPTime time, DCPTime length, bool starting) { - /* Here we should notice Film properties that affect our output, and - alert listeners that our output now would be different to how it was - last time we were run. - */ + list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length); - if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { - Changed (false); - } -} + PlayerSubtitles ps (time, length); -void -Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to) -{ - if (!image) { - /* A null image means that we should stop any current subtitles at `from' */ - for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - i->set_stop (from); + for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) { + shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content); + if (!subtitle_content->use_subtitles ()) { + continue; } - } else { - _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to)); - } -} -/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles. - * @return false if this could not be done. - */ -bool -Player::repeat_last_video () -{ - if (!_last_incoming_video.image || !_have_valid_pieces) { - return false; - } + shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder); + ContentTime const from = dcp_to_content_subtitle (*j, time); + /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */ + ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ()); - process_video ( - _last_incoming_video.weak_piece, - _last_incoming_video.image, - _last_incoming_video.eyes, - _last_incoming_video.part, - _last_incoming_video.same, - _last_incoming_video.frame, - _last_incoming_video.extra - ); + list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting); + for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) { + + /* Apply content's subtitle offsets */ + i->sub.rectangle.x += subtitle_content->subtitle_x_offset (); + i->sub.rectangle.y += subtitle_content->subtitle_y_offset (); + + /* Apply content's subtitle scale */ + i->sub.rectangle.width *= subtitle_content->subtitle_x_scale (); + i->sub.rectangle.height *= subtitle_content->subtitle_y_scale (); + + /* Apply a corrective translation to keep the subtitle centred after that scale */ + i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1); + i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1); + + ps.image.push_back (i->sub); + } + + list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting); + BOOST_FOREACH (ContentTextSubtitle& ts, text) { + BOOST_FOREACH (dcp::SubtitleString& s, ts.subs) { + s.set_v_position (s.v_position() + subtitle_content->subtitle_y_offset ()); + s.set_size (s.size() * max (subtitle_content->subtitle_x_scale(), subtitle_content->subtitle_y_scale())); + ps.text.push_back (s); + } + } + } - return true; + return ps; } diff --git a/src/lib/player.h b/src/lib/player.h index 6e70ad707..a3b745c8e 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -27,10 +27,13 @@ #include "content.h" #include "film.h" #include "rect.h" -#include "audio_merger.h" #include "audio_content.h" +#include "dcpomatic_time.h" +#include "content_subtitle.h" +#include "position_image.h" #include "piece.h" -#include "subtitle.h" +#include "content_video.h" +#include "player_subtitles.h" class Job; class Film; @@ -38,42 +41,60 @@ class Playlist; class AudioContent; class Piece; class Image; +class Decoder; class Resampler; -class PlayerVideoFrame; +class PlayerVideo; class ImageProxy; +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. + * @brief A class which can `play' a Playlist. */ class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable { public: Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>); - void disable_video (); - void disable_audio (); - - bool pass (); - void seek (Time, bool); + std::list<boost::shared_ptr<PlayerVideo> > get_video (DCPTime time, bool accurate); + boost::shared_ptr<AudioBuffers> get_audio (DCPTime time, DCPTime length, bool accurate); + PlayerSubtitles get_subtitles (DCPTime time, DCPTime length, bool starting); - Time video_position () const { - return _video_position; - } - - void set_video_container_size (libdcp::Size); - - bool repeat_last_video (); + void set_video_container_size (dcp::Size); + void set_approximate_size (); - /** Emitted when a video frame is ready. - * First parameter is the video image. - * Second parameter is true if the frame is the same as the last one that was emitted. - * Third parameter is the time. - */ - boost::signals2::signal<void (boost::shared_ptr<PlayerVideoFrame>, bool, Time)> Video; + PlayerStatistics const & statistics () const; - /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> 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 * a seek. @@ -85,51 +106,58 @@ public: private: friend class PlayerWrapper; friend class Piece; + friend struct player_overlaps_test; - void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame, Time); - void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame, bool); - 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 flush (); - void emit_black (); - void emit_silence (OutputAudioFrame); - boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool); void film_changed (Film::Property); - void update_subtitle (); - + std::list<PositionImage> transform_image_subtitles (std::list<ImageSubtitle>) const; + void update_subtitle_from_text (); + VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const; + DCPTime content_video_to_dcp (boost::shared_ptr<const Piece> piece, VideoFrame f) const; + AudioFrame dcp_to_content_audio (boost::shared_ptr<const Piece> piece, DCPTime t) const; + ContentTime dcp_to_content_subtitle (boost::shared_ptr<const Piece> piece, DCPTime t) const; + boost::shared_ptr<PlayerVideo> black_player_video_frame (DCPTime) const; + + /** @return Pieces of content type C that overlap a specified time range in the DCP */ + template<class C> + std::list<boost::shared_ptr<Piece> > + overlaps (DCPTime from, DCPTime to) + { + if (!_have_valid_pieces) { + setup_pieces (); + } + + std::list<boost::shared_ptr<Piece> > overlaps; + for (typename std::list<boost::shared_ptr<Piece> >::const_iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + if (!boost::dynamic_pointer_cast<C> ((*i)->content)) { + continue; + } + + if ((*i)->content->position() <= to && (*i)->content->end() >= from) { + overlaps.push_back (*i); + } + } + + return overlaps; + } + boost::shared_ptr<const Film> _film; boost::shared_ptr<const Playlist> _playlist; - - bool _video; - bool _audio; /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */ bool _have_valid_pieces; std::list<boost::shared_ptr<Piece> > _pieces; - /** The time after the last video that we emitted */ - Time _video_position; - /** The time after the last audio that we emitted */ - Time _audio_position; - - AudioMerger<Time, AudioContent::Frame> _audio_merger; - - libdcp::Size _video_container_size; - boost::shared_ptr<PlayerVideoFrame> _black_frame; - std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers; - - std::list<Subtitle> _subtitles; - -#ifdef DCPOMATIC_DEBUG - boost::shared_ptr<Content> _last_video; -#endif + dcp::Size _video_container_size; + boost::shared_ptr<Image> _black_image; - bool _last_emit_was_black; + bool _approximate_size; + bool _burn_subtitles; - IncomingVideo _last_incoming_video; + PlayerStatistics _statistics; boost::signals2::scoped_connection _playlist_changed_connection; boost::signals2::scoped_connection _playlist_content_changed_connection; diff --git a/src/lib/player_subtitles.h b/src/lib/player_subtitles.h new file mode 100644 index 000000000..46994ea3b --- /dev/null +++ b/src/lib/player_subtitles.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. + +*/ + +#ifndef DCPOMATIC_PLAYER_SUBTITLES_H +#define DCPOMATIC_PLAYER_SUBTITLES_H + +#include <dcp/subtitle_string.h> +#include "image_subtitle.h" + +class PlayerSubtitles +{ +public: + PlayerSubtitles (DCPTime f, DCPTime t) + : from (f) + , to (t) + {} + + DCPTime from; + DCPTime to; + + /** ImageSubtitles, with their rectangles transformed as specified by their content */ + std::list<ImageSubtitle> image; + std::list<dcp::SubtitleString> text; +}; + +#endif diff --git a/src/lib/player_video.cc b/src/lib/player_video.cc new file mode 100644 index 000000000..2feb52f42 --- /dev/null +++ b/src/lib/player_video.cc @@ -0,0 +1,209 @@ +/* + 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 <dcp/raw_convert.h> +#include "player_video.h" +#include "image.h" +#include "image_proxy.h" +#include "j2k_image_proxy.h" +#include "scaler.h" + +using std::string; +using std::cout; +using dcp::raw_convert; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +PlayerVideo::PlayerVideo ( + shared_ptr<const ImageProxy> in, + DCPTime time, + Crop crop, + boost::optional<float> fade, + dcp::Size inter_size, + dcp::Size out_size, + Scaler const * scaler, + Eyes eyes, + Part part, + ColourConversion colour_conversion + ) + : _in (in) + , _time (time) + , _crop (crop) + , _fade (fade) + , _inter_size (inter_size) + , _out_size (out_size) + , _scaler (scaler) + , _eyes (eyes) + , _part (part) + , _colour_conversion (colour_conversion) +{ + +} + +PlayerVideo::PlayerVideo (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket, shared_ptr<Log> log) +{ + _time = DCPTime (node->number_child<DCPTime::Type> ("Time")); + _crop = Crop (node); + _fade = node->optional_number_child<float> ("Fade"); + + _inter_size = dcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight")); + _out_size = dcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight")); + _scaler = Scaler::from_id (node->string_child ("Scaler")); + _eyes = (Eyes) node->number_child<int> ("Eyes"); + _part = (Part) node->number_child<int> ("Part"); + _colour_conversion = ColourConversion (node); + + _in = image_proxy_factory (node->node_child ("In"), socket, log); + + if (node->optional_number_child<int> ("SubtitleX")) { + + _subtitle.position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")); + + _subtitle.image.reset ( + new Image (PIX_FMT_RGBA, dcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true) + ); + + _subtitle.image->read_from_socket (socket); + } +} + +void +PlayerVideo::set_subtitle (PositionImage image) +{ + _subtitle = image; +} + +shared_ptr<Image> +PlayerVideo::image (bool burn_subtitle) const +{ + shared_ptr<Image> im = _in->image (); + + Crop total_crop = _crop; + switch (_part) { + case PART_LEFT_HALF: + total_crop.right += im->size().width / 2; + break; + case PART_RIGHT_HALF: + total_crop.left += im->size().width / 2; + break; + case PART_TOP_HALF: + total_crop.bottom += im->size().height / 2; + break; + case PART_BOTTOM_HALF: + total_crop.top += im->size().height / 2; + break; + default: + break; + } + + shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, true); + + if (burn_subtitle && _subtitle.image) { + out->alpha_blend (_subtitle.image, _subtitle.position); + } + + if (_fade) { + out->fade (_fade.get ()); + } + + return out; +} + +void +PlayerVideo::add_metadata (xmlpp::Node* node, bool send_subtitles) const +{ + node->add_child("Time")->add_child_text (raw_convert<string> (_time.get ())); + _crop.as_xml (node); + if (_fade) { + node->add_child("Fade")->add_child_text (raw_convert<string> (_fade.get ())); + } + _in->add_metadata (node->add_child ("In")); + node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width)); + node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height)); + node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width)); + node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height)); + node->add_child("Scaler")->add_child_text (_scaler->id ()); + node->add_child("Eyes")->add_child_text (raw_convert<string> (_eyes)); + node->add_child("Part")->add_child_text (raw_convert<string> (_part)); + _colour_conversion.as_xml (node); + if (send_subtitles && _subtitle.image) { + node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle.image->size().width)); + node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle.image->size().height)); + node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle.position.x)); + node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle.position.y)); + } +} + +void +PlayerVideo::send_binary (shared_ptr<Socket> socket, bool send_subtitles) const +{ + _in->send_binary (socket); + if (send_subtitles && _subtitle.image) { + _subtitle.image->write_to_socket (socket); + } +} + +bool +PlayerVideo::has_j2k () const +{ + /* XXX: burnt-in subtitle; maybe other things */ + + shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in); + if (!j2k) { + return false; + } + + return _crop == Crop () && _inter_size == j2k->size(); +} + +shared_ptr<EncodedData> +PlayerVideo::j2k () const +{ + shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in); + assert (j2k); + return j2k->j2k (); +} + +Position<int> +PlayerVideo::inter_position () const +{ + return Position<int> ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.height) / 2); +} + +/** @return true if this PlayerVideo is definitely the same as another + * (apart from _time), false if it is probably not + */ +bool +PlayerVideo::same (shared_ptr<const PlayerVideo> other) const +{ + if (_in != other->_in || + _crop != other->_crop || + _fade.get_value_or(0) != other->_fade.get_value_or(0) || + _inter_size != other->_inter_size || + _out_size != other->_out_size || + _scaler != other->_scaler || + _eyes != other->_eyes || + _part != other->_part || + _colour_conversion != other->_colour_conversion || + !_subtitle.same (other->_subtitle)) { + return false; + } + + return _in->same (other->_in); +} diff --git a/src/lib/player_video_frame.h b/src/lib/player_video.h index b085cb609..0f5e83b10 100644 --- a/src/lib/player_video_frame.h +++ b/src/lib/player_video.h @@ -21,29 +21,50 @@ #include "types.h" #include "position.h" #include "colour_conversion.h" +#include "position_image.h" class Image; class ImageProxy; class Scaler; class Socket; class Log; +class EncodedData; /** Everything needed to describe a video frame coming out of the player, but with the * bits still their raw form. We may want to combine the bits on a remote machine, * or maybe not even bother to combine them at all. */ -class PlayerVideoFrame +class PlayerVideo { public: - PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, libdcp::Size, libdcp::Size, Scaler const *, Eyes, Part, ColourConversion); - PlayerVideoFrame (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>, boost::shared_ptr<Log>); + PlayerVideo ( + boost::shared_ptr<const ImageProxy>, + DCPTime, + Crop, + boost::optional<float>, + dcp::Size, + dcp::Size, + Scaler const *, + Eyes, + Part, + ColourConversion + ); + + PlayerVideo (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>, boost::shared_ptr<Log>); - void set_subtitle (boost::shared_ptr<const Image>, Position<int>); + void set_subtitle (PositionImage); - boost::shared_ptr<Image> image () const; + boost::shared_ptr<Image> image (bool burn_subtitle) const; + + void add_metadata (xmlpp::Node* node, bool send_subtitles) const; + void send_binary (boost::shared_ptr<Socket> socket, bool send_subtitles) const; - void add_metadata (xmlpp::Node* node) const; - void send_binary (boost::shared_ptr<Socket> socket) const; + bool has_j2k () const; + boost::shared_ptr<EncodedData> j2k () const; + + DCPTime time () const { + return _time; + } Eyes eyes () const { return _eyes; @@ -53,15 +74,26 @@ public: return _colour_conversion; } + /** @return Position of the content within the overall image once it has been scaled up */ + Position<int> inter_position () const; + + /** @return Size of the content within the overall image once it has been scaled up */ + dcp::Size inter_size () const { + return _inter_size; + } + + bool same (boost::shared_ptr<const PlayerVideo> other) const; + private: boost::shared_ptr<const ImageProxy> _in; + DCPTime _time; Crop _crop; - libdcp::Size _inter_size; - libdcp::Size _out_size; + boost::optional<float> _fade; + dcp::Size _inter_size; + dcp::Size _out_size; Scaler const * _scaler; Eyes _eyes; Part _part; ColourConversion _colour_conversion; - boost::shared_ptr<const Image> _subtitle_image; - Position<int> _subtitle_position; + PositionImage _subtitle; }; diff --git a/src/lib/player_video_frame.cc b/src/lib/player_video_frame.cc deleted file mode 100644 index 94760e495..000000000 --- a/src/lib/player_video_frame.cc +++ /dev/null @@ -1,148 +0,0 @@ -/* - 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 <libdcp/raw_convert.h> -#include "player_video_frame.h" -#include "image.h" -#include "image_proxy.h" -#include "scaler.h" - -using std::string; -using std::cout; -using boost::shared_ptr; -using libdcp::raw_convert; - -PlayerVideoFrame::PlayerVideoFrame ( - shared_ptr<const ImageProxy> in, - Crop crop, - libdcp::Size inter_size, - libdcp::Size out_size, - Scaler const * scaler, - Eyes eyes, - Part part, - ColourConversion colour_conversion - ) - : _in (in) - , _crop (crop) - , _inter_size (inter_size) - , _out_size (out_size) - , _scaler (scaler) - , _eyes (eyes) - , _part (part) - , _colour_conversion (colour_conversion) -{ - -} - -PlayerVideoFrame::PlayerVideoFrame (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket, shared_ptr<Log> log) -{ - _crop = Crop (node); - - _inter_size = libdcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight")); - _out_size = libdcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight")); - _scaler = Scaler::from_id (node->string_child ("Scaler")); - _eyes = (Eyes) node->number_child<int> ("Eyes"); - _part = (Part) node->number_child<int> ("Part"); - _colour_conversion = ColourConversion (node); - - _in = image_proxy_factory (node->node_child ("In"), socket, log); - - if (node->optional_number_child<int> ("SubtitleX")) { - - _subtitle_position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")); - - shared_ptr<Image> image ( - new Image (PIX_FMT_RGBA, libdcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true) - ); - - image->read_from_socket (socket); - _subtitle_image = image; - } -} - -void -PlayerVideoFrame::set_subtitle (shared_ptr<const Image> image, Position<int> pos) -{ - _subtitle_image = image; - _subtitle_position = pos; -} - -shared_ptr<Image> -PlayerVideoFrame::image () const -{ - shared_ptr<Image> im = _in->image (); - - Crop total_crop = _crop; - switch (_part) { - case PART_LEFT_HALF: - total_crop.right += im->size().width / 2; - break; - case PART_RIGHT_HALF: - total_crop.left += im->size().width / 2; - break; - case PART_TOP_HALF: - total_crop.bottom += im->size().height / 2; - break; - case PART_BOTTOM_HALF: - total_crop.top += im->size().height / 2; - break; - default: - break; - } - - shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false); - - Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2); - - if (_subtitle_image) { - out->alpha_blend (_subtitle_image, _subtitle_position); - } - - return out; -} - -void -PlayerVideoFrame::add_metadata (xmlpp::Node* node) const -{ - _crop.as_xml (node); - _in->add_metadata (node->add_child ("In")); - node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width)); - node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height)); - node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width)); - node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height)); - node->add_child("Scaler")->add_child_text (_scaler->id ()); - node->add_child("Eyes")->add_child_text (raw_convert<string> (_eyes)); - node->add_child("Part")->add_child_text (raw_convert<string> (_part)); - _colour_conversion.as_xml (node); - if (_subtitle_image) { - node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle_image->size().width)); - node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle_image->size().height)); - node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle_position.x)); - node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle_position.y)); - } -} - -void -PlayerVideoFrame::send_binary (shared_ptr<Socket> socket) const -{ - _in->send_binary (socket); - if (_subtitle_image) { - _subtitle_image->write_to_socket (socket); - } -} diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index c3e430082..22412da4a 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -79,20 +79,20 @@ Playlist::maybe_sequence_video () _sequencing_video = true; ContentList cl = _content; - Time next_left = 0; - Time next_right = 0; + DCPTime next_left; + DCPTime next_right; for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i); if (!vc) { continue; } - + if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) { vc->set_position (next_right); - next_right = vc->end() + 1; + next_right = vc->end() + DCPTime::delta (); } else { vc->set_position (next_left); - next_left = vc->end() + 1; + next_left = vc->end() + DCPTime::delta (); } } @@ -120,7 +120,7 @@ Playlist::video_identifier () const /** @param node <Playlist> node */ void -Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version, list<string>& notes) +Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes) { list<cxml::NodePtr> c = node->node_children ("Content"); for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) { @@ -185,19 +185,6 @@ Playlist::remove (ContentList c) Changed (); } -bool -Playlist::has_subtitles () const -{ - for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { - shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i); - if (fc && !fc->subtitle_streams().empty()) { - return true; - } - } - - return false; -} - class FrameRateCandidate { public: @@ -261,12 +248,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; @@ -286,10 +273,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 ()); @@ -299,6 +286,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) { + continue; + } + + 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) { @@ -321,7 +325,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 ()); @@ -329,7 +333,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 (); @@ -363,7 +367,7 @@ Playlist::move_earlier (shared_ptr<Content> c) } - 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 ()); @@ -392,20 +396,3 @@ Playlist::move_later (shared_ptr<Content> c) c->set_position (c->position() + c->length_after_trim ()); sort (_content.begin(), _content.end(), ContentSorter ()); } - -FrameRateChange -Playlist::active_frame_rate_change (Time 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) { - continue; - } - - 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); -} diff --git a/src/lib/playlist.h b/src/lib/playlist.h index 12380696b..9e3dbb6df 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" #include "frame_rate_change.h" class Content; @@ -38,18 +39,15 @@ class Job; class Film; class Region; -/** @class Playlist - * @brief A set of content files (video and audio), with knowledge of how they should be arranged into - * a DCP. - * - * This class holds Content objects, and it knows how they should be arranged. - */ - struct ContentSorter { bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b); }; +/** @class Playlist + * @brief A set of Content objects with knowledge of how they should be arranged into + * a DCP. + */ class Playlist : public boost::noncopyable { public: @@ -57,7 +55,7 @@ public: ~Playlist (); void as_xml (xmlpp::Node *); - void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int, std::list<std::string> &); + void set_from_xml (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int, std::list<std::string> &); void add (boost::shared_ptr<Content>); void remove (boost::shared_ptr<Content>); @@ -65,17 +63,15 @@ public: void move_earlier (boost::shared_ptr<Content>); void move_later (boost::shared_ptr<Content>); - bool has_subtitles () const; - ContentList content () const; std::string video_identifier () const; - Time length () const; + DCPTime length () const; int best_dcp_frame_rate () const; - Time video_end () const; - FrameRateChange active_frame_rate_change (Time, int dcp_frame_rate) 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/position.h b/src/lib/position.h index f9bd0987c..3c561d85c 100644 --- a/src/lib/position.h +++ b/src/lib/position.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 @@ -21,7 +21,7 @@ #define DCPOMATIC_POSITION_H /** @struct Position - * @brief A position. + * @brief A position (x and y coordinates) */ template <class T> class Position @@ -50,4 +50,25 @@ operator+ (Position<T> const & a, Position<T> const & b) return Position<T> (a.x + b.x, a.y + b.y); } +template<class T> +Position<T> +operator- (Position<T> const & a, Position<T> const & b) +{ + return Position<T> (a.x - b.x, a.y - b.y); +} + +template<class T> +bool +operator== (Position<T> const & a, Position<T> const & b) +{ + return a.x == b.x && a.y == b.y; +} + +template<class T> +bool +operator!= (Position<T> const & a, Position<T> const & b) +{ + return a.x != b.x || a.y != b.y; +} + #endif diff --git a/src/lib/decoder.cc b/src/lib/position_image.cc index 3f4cda6eb..44c1262b3 100644 --- a/src/lib/decoder.cc +++ b/src/lib/position_image.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + 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 @@ -17,22 +17,21 @@ */ -/** @file src/decoder.cc - * @brief Parent class for decoders of content. - */ +#include "position_image.h" +#include "image.h" -#include "film.h" -#include "decoder.h" +using std::cout; -#include "i18n.h" - -using boost::shared_ptr; - -/** @param f Film. - * @param o Decode options. - */ -Decoder::Decoder (shared_ptr<const Film> f) - : _film (f) +bool +PositionImage::same (PositionImage const & other) const { + if (image != other.image || position != other.position) { + return false; + } + + if (!image) { + return true; + } + return *image == *(other.image); } diff --git a/src/lib/position_image.h b/src/lib/position_image.h new file mode 100644 index 000000000..c0c65d1da --- /dev/null +++ b/src/lib/position_image.h @@ -0,0 +1,44 @@ +/* + 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_POSITION_IMAGE_H +#define DCPOMATIC_POSITION_IMAGE_H + +#include "position.h" +#include <boost/shared_ptr.hpp> + +class Image; + +class PositionImage +{ +public: + PositionImage () {} + + PositionImage (boost::shared_ptr<Image> i, Position<int> p) + : image (i) + , position (p) + {} + + boost::shared_ptr<Image> image; + Position<int> position; + + bool same (PositionImage const & other) const; +}; + +#endif diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc index 554d3c36c..fc36415c5 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 ab157a9bc..69e3726c8 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/raw_image_proxy.cc b/src/lib/raw_image_proxy.cc new file mode 100644 index 000000000..7e0688d13 --- /dev/null +++ b/src/lib/raw_image_proxy.cc @@ -0,0 +1,71 @@ +/* + 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. + +*/ + +extern "C" { +#include <libavutil/pixfmt.h> +} +#include <libcxml/cxml.h> +#include <dcp/util.h> +#include <dcp/raw_convert.h> +#include "raw_image_proxy.h" +#include "image.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log) + : ImageProxy (log) + , _image (image) +{ + +} + +RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) + : ImageProxy (log) +{ + dcp::Size size ( + xml->number_child<int> ("Width"), xml->number_child<int> ("Height") + ); + + _image.reset (new Image (static_cast<AVPixelFormat> (xml->number_child<int> ("PixelFormat")), size, true)); + _image->read_from_socket (socket); +} + +shared_ptr<Image> +RawImageProxy::image () const +{ + return _image; +} + +void +RawImageProxy::add_metadata (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text (N_("Raw")); + node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_image->size().width)); + node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_image->size().height)); + node->add_child("PixelFormat")->add_child_text (dcp::raw_convert<string> (_image->pixel_format ())); +} + +void +RawImageProxy::send_binary (shared_ptr<Socket> socket) const +{ + _image->write_to_socket (socket); +} diff --git a/src/lib/raw_image_proxy.h b/src/lib/raw_image_proxy.h new file mode 100644 index 000000000..6707f689c --- /dev/null +++ b/src/lib/raw_image_proxy.h @@ -0,0 +1,34 @@ +/* + 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 "image_proxy.h" + +class RawImageProxy : public ImageProxy +{ +public: + RawImageProxy (boost::shared_ptr<Image>, boost::shared_ptr<Log> log); + RawImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + + boost::shared_ptr<Image> image () const; + void add_metadata (xmlpp::Node *) const; + void send_binary (boost::shared_ptr<Socket>) const; + +private: + boost::shared_ptr<Image> _image; +}; diff --git a/src/lib/rect.h b/src/lib/rect.h index 6f4709c08..1feb8ad4f 100644 --- a/src/lib/rect.h +++ b/src/lib/rect.h @@ -42,6 +42,13 @@ public: , height (0) {} + Rect (Position<T> p, T w_, T h_) + : x (p.x) + , y (p.y) + , width (w_) + , height (h_) + {} + Rect (T x_, T y_, T w_, T h_) : x (x_) , y (y_) @@ -54,11 +61,13 @@ public: T width; T height; - Position<T> position () const { + Position<T> position () const + { return Position<T> (x, y); } - Rect<T> intersection (Rect<T> const & other) const { + Rect<T> intersection (Rect<T> const & other) const + { T const tx = max (x, other.x); T const ty = max (y, other.y); @@ -69,7 +78,16 @@ public: ); } - bool contains (Position<T> p) const { + void extend (Rect<T> const & other) + { + x = std::min (x, other.x); + y = std::min (y, other.y); + width = std::max (x + width, other.x + other.width) - x; + height = std::max (y + height, other.y + other.height) - y; + } + + bool contains (Position<T> p) const + { return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height)); } }; diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc new file mode 100644 index 000000000..6f103246c --- /dev/null +++ b/src/lib/render_subtitles.cc @@ -0,0 +1,152 @@ +/* + 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 std::pair; +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 * target_height - offset; + case dcp::CENTER: + return (0.5 + v_position) * target_height - offset; + case dcp::BOTTOM: + return (1.0 - v_position) * target_height - offset; + } + + return 0; +} + +PositionImage +render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target) +{ + if (subtitles.empty ()) { + return PositionImage (); + } + + /* 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; + + shared_ptr<Image> image (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 (); + } + } + + return PositionImage (image, Position<int> (0, top.get ())); +} + diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h new file mode 100644 index 000000000..d83dc119a --- /dev/null +++ b/src/lib/render_subtitles.h @@ -0,0 +1,24 @@ +/* + 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_image.h" + +PositionImage render_subtitles (std::list<dcp::SubtitleString>, dcp::Size); diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc index e7f50c4b7..e414436e8 100644 --- a/src/lib/resampler.cc +++ b/src/lib/resampler.cc @@ -60,11 +60,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)); @@ -80,7 +78,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/safe_stringstream.h b/src/lib/safe_stringstream.h index e455de964..0ffcb6224 100644 --- a/src/lib/safe_stringstream.h +++ b/src/lib/safe_stringstream.h @@ -84,6 +84,11 @@ public: _stream.width (w); } + void fill (int f) + { + _stream.fill (f); + } + void precision (int p) { _stream.precision (p); diff --git a/src/lib/send_kdm_email_job.cc b/src/lib/send_kdm_email_job.cc index 164dfe987..541307f5a 100644 --- a/src/lib/send_kdm_email_job.cc +++ b/src/lib/send_kdm_email_job.cc @@ -34,7 +34,7 @@ SendKDMEmailJob::SendKDMEmailJob ( boost::filesystem::path dcp, boost::posix_time::ptime from, boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation + dcp::Formulation formulation ) : Job (f) , _screens (screens) diff --git a/src/lib/send_kdm_email_job.h b/src/lib/send_kdm_email_job.h index 778d3927a..af84a13af 100644 --- a/src/lib/send_kdm_email_job.h +++ b/src/lib/send_kdm_email_job.h @@ -18,7 +18,7 @@ */ #include <boost/filesystem.hpp> -#include <libdcp/kdm.h> +#include <dcp/types.h> #include "job.h" class Screen; @@ -32,7 +32,7 @@ public: boost::filesystem::path, boost::posix_time::ptime, boost::posix_time::ptime, - libdcp::KDM::Formulation + dcp::Formulation ); std::string name () const; @@ -43,5 +43,5 @@ private: boost::filesystem::path _dcp; boost::posix_time::ptime _from; boost::posix_time::ptime _to; - libdcp::KDM::Formulation _formulation; + dcp::Formulation _formulation; }; diff --git a/src/lib/server.cc b/src/lib/server.cc index 9428ba611..a699be577 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -28,15 +28,16 @@ #include <boost/algorithm/string.hpp> #include <boost/scoped_array.hpp> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "server.h" #include "util.h" #include "scaler.h" #include "image.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "config.h" #include "cross.h" -#include "player_video_frame.h" +#include "player_video.h" +#include "encoded_data.h" #include "safe_stringstream.h" #include "i18n.h" @@ -61,16 +62,37 @@ using boost::thread; using boost::bind; using boost::scoped_array; using boost::optional; -using libdcp::Size; -using libdcp::raw_convert; +using dcp::Size; +using dcp::raw_convert; Server::Server (shared_ptr<Log> log, bool verbose) - : _log (log) + : _terminate (false) + , _log (log) , _verbose (verbose) + , _acceptor (_io_service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), Config::instance()->server_port_base())) { } +Server::~Server () +{ + { + boost::mutex::scoped_lock lm (_worker_mutex); + _terminate = true; + _empty_condition.notify_all (); + } + + for (vector<boost::thread*>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) { + (*i)->join (); + delete *i; + } + + _io_service.stop (); + + _broadcast.io_service.stop (); + _broadcast.thread->join (); +} + /** @param after_read Filled in with gettimeofday() after reading the input from the network. * @param after_encode Filled in with gettimeofday() after encoding the image. */ @@ -90,9 +112,9 @@ Server::process (shared_ptr<Socket> socket, struct timeval& after_read, struct t return -1; } - shared_ptr<PlayerVideoFrame> pvf (new PlayerVideoFrame (xml, socket, _log)); + shared_ptr<PlayerVideo> pvf (new PlayerVideo (xml, socket, _log)); - DCPVideoFrame dcp_video_frame (pvf, xml, _log); + DCPVideo dcp_video_frame (pvf, xml, _log); gettimeofday (&after_read, 0); @@ -116,10 +138,14 @@ Server::worker_thread () { while (true) { boost::mutex::scoped_lock lock (_worker_mutex); - while (_queue.empty ()) { + while (_queue.empty () && !_terminate) { _empty_condition.wait (lock); } + if (_terminate) { + return; + } + shared_ptr<Socket> socket = _queue.front (); _queue.pop_front (); @@ -186,39 +212,18 @@ Server::run (int num_threads) _broadcast.thread = new thread (bind (&Server::broadcast_thread, this)); - boost::asio::io_service io_service; - - boost::asio::ip::tcp::acceptor acceptor ( - io_service, - boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), Config::instance()->server_port_base ()) - ); - - while (true) { - shared_ptr<Socket> socket (new Socket); - acceptor.accept (socket->socket ()); - - boost::mutex::scoped_lock lock (_worker_mutex); - - /* Wait until the queue has gone down a bit */ - while (int (_queue.size()) >= num_threads * 2) { - _full_condition.wait (lock); - } - - _queue.push_back (socket); - _empty_condition.notify_all (); - } + start_accept (); + _io_service.run (); } void Server::broadcast_thread () try { - boost::asio::io_service io_service; - boost::asio::ip::address address = boost::asio::ip::address_v4::any (); boost::asio::ip::udp::endpoint listen_endpoint (address, Config::instance()->server_port_base() + 1); - _broadcast.socket = new boost::asio::ip::udp::socket (io_service); + _broadcast.socket = new boost::asio::ip::udp::socket (_broadcast.io_service); _broadcast.socket->open (listen_endpoint.protocol ()); _broadcast.socket->bind (listen_endpoint); @@ -228,7 +233,7 @@ try boost::bind (&Server::broadcast_received, this) ); - io_service.run (); + _broadcast.io_service.run (); } catch (...) { @@ -265,3 +270,35 @@ Server::broadcast_received () _broadcast.send_endpoint, boost::bind (&Server::broadcast_received, this) ); } + +void +Server::start_accept () +{ + if (_terminate) { + return; + } + + shared_ptr<Socket> socket (new Socket); + _acceptor.async_accept (socket->socket (), boost::bind (&Server::handle_accept, this, socket, boost::asio::placeholders::error)); +} + +void +Server::handle_accept (shared_ptr<Socket> socket, boost::system::error_code const & error) +{ + if (error) { + return; + } + + boost::mutex::scoped_lock lock (_worker_mutex); + + /* Wait until the queue has gone down a bit */ + while (_queue.size() >= _worker_threads.size() * 2 && !_terminate) { + _full_condition.wait (lock); + } + + _queue.push_back (socket); + _empty_condition.notify_all (); + + start_accept (); +} + diff --git a/src/lib/server.h b/src/lib/server.h index b925031eb..e2e1d46ec 100644 --- a/src/lib/server.h +++ b/src/lib/server.h @@ -90,6 +90,7 @@ class Server : public ExceptionStore, public boost::noncopyable { public: Server (boost::shared_ptr<Log> log, bool verbose); + ~Server (); void run (int num_threads); @@ -98,6 +99,10 @@ private: int process (boost::shared_ptr<Socket> socket, struct timeval &, struct timeval &); void broadcast_thread (); void broadcast_received (); + void start_accept (); + void handle_accept (boost::shared_ptr<Socket>, boost::system::error_code const &); + + bool _terminate; std::vector<boost::thread *> _worker_threads; std::list<boost::shared_ptr<Socket> > _queue; @@ -107,6 +112,9 @@ private: boost::shared_ptr<Log> _log; bool _verbose; + boost::asio::io_service _io_service; + boost::asio::ip::tcp::acceptor _acceptor; + struct Broadcast { Broadcast () @@ -118,6 +126,7 @@ private: boost::asio::ip::udp::socket* socket; char buffer[64]; boost::asio::ip::udp::endpoint send_endpoint; + boost::asio::io_service io_service; } _broadcast; }; diff --git a/src/lib/server_finder.cc b/src/lib/server_finder.cc index a082f3bab..637103591 100644 --- a/src/lib/server_finder.cc +++ b/src/lib/server_finder.cc @@ -18,7 +18,7 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "server_finder.h" #include "exceptions.h" #include "util.h" @@ -32,7 +32,7 @@ using std::vector; using std::cout; using boost::shared_ptr; using boost::scoped_array; -using libdcp::raw_convert; +using dcp::raw_convert; ServerFinder* ServerFinder::_instance = 0; diff --git a/src/lib/single_stream_audio_content.cc b/src/lib/single_stream_audio_content.cc new file mode 100644 index 000000000..521597606 --- /dev/null +++ b/src/lib/single_stream_audio_content.cc @@ -0,0 +1,106 @@ +/* + 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/raw_convert.h> +#include "single_stream_audio_content.h" +#include "audio_examiner.h" +#include "film.h" + +using std::string; +using std::cout; +using boost::shared_ptr; +using dcp::raw_convert; + +SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f) + : Content (f) + , AudioContent (f) + , _audio_channels (0) + , _audio_length (0) + , _audio_frame_rate (0) +{ + +} + +SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , AudioContent (f, p) + , _audio_channels (0) + , _audio_length (0) + , _audio_frame_rate (0) +{ + +} + +SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) + : Content (f, node) + , AudioContent (f, node) + , _audio_mapping (node->node_child ("AudioMapping"), version) +{ + _audio_channels = node->number_child<int> ("AudioChannels"); + _audio_length = ContentTime (node->number_child<ContentTime::Type> ("AudioLength")); + _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); +} + +void +SingleStreamAudioContent::set_audio_mapping (AudioMapping m) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_mapping = m; + } + + AudioContent::set_audio_mapping (m); +} + + +void +SingleStreamAudioContent::as_xml (xmlpp::Node* node) const +{ + AudioContent::as_xml (node); + node->add_child("AudioChannels")->add_child_text (raw_convert<string> (audio_channels ())); + node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length().get ())); + node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio_frame_rate ())); + _audio_mapping.as_xml (node->add_child("AudioMapping")); +} + +void +SingleStreamAudioContent::take_from_audio_examiner (shared_ptr<AudioExaminer> examiner) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_channels = examiner->audio_channels (); + _audio_length = examiner->audio_length (); + _audio_frame_rate = examiner->audio_frame_rate (); + } + + signal_changed (AudioContentProperty::AUDIO_CHANNELS); + signal_changed (AudioContentProperty::AUDIO_LENGTH); + signal_changed (AudioContentProperty::AUDIO_FRAME_RATE); + + int const p = processed_audio_channels (); + + { + boost::mutex::scoped_lock lm (_mutex); + /* XXX: do this in signal_changed...? */ + _audio_mapping = AudioMapping (p); + _audio_mapping.make_default (); + } + + signal_changed (AudioContentProperty::AUDIO_MAPPING); +} diff --git a/src/lib/single_stream_audio_content.h b/src/lib/single_stream_audio_content.h new file mode 100644 index 000000000..fcfaf14ca --- /dev/null +++ b/src/lib/single_stream_audio_content.h @@ -0,0 +1,74 @@ +/* + 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. + +*/ + +/** @file src/lib/single_stream_audio_content.h + * @brief SingleStreamAudioContent class. + */ + +#ifndef DCPOMATIC_SINGLE_STREAM_AUDIO_CONTENT_H +#define DCPOMATIC_SINGLE_STREAM_AUDIO_CONTENT_H + +#include "audio_content.h" + +class AudioExaminer; + +/** @class SingleStreamAudioContent + * @brief A piece of AudioContent that has a single audio stream. + */ +class SingleStreamAudioContent : public AudioContent +{ +public: + SingleStreamAudioContent (boost::shared_ptr<const Film>); + SingleStreamAudioContent (boost::shared_ptr<const Film>, boost::filesystem::path); + SingleStreamAudioContent (boost::shared_ptr<const Film> f, cxml::ConstNodePtr node, int version); + + void as_xml (xmlpp::Node* node) const; + + int audio_channels () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_channels; + } + + ContentTime audio_length () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_length; + } + + int audio_frame_rate () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_frame_rate; + } + + AudioMapping audio_mapping () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_mapping; + } + + void set_audio_mapping (AudioMapping); + + void take_from_audio_examiner (boost::shared_ptr<AudioExaminer>); + +protected: + int _audio_channels; + ContentTime _audio_length; + int _audio_frame_rate; + AudioMapping _audio_mapping; +}; + +#endif diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index ba3bd0a77..1a1797665 100644 --- a/src/lib/sndfile_content.cc +++ b/src/lib/sndfile_content.cc @@ -18,7 +18,7 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "sndfile_content.h" #include "sndfile_decoder.h" #include "film.h" @@ -32,28 +32,31 @@ using std::string; using std::cout; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) - , AudioContent (f, p) - , _audio_channels (0) - , _audio_length (0) - , _audio_frame_rate (0) + , SingleStreamAudioContent (f, p) { } -SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version) +SndfileContent::SndfileContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) : Content (f, node) - , AudioContent (f, node) - , _audio_mapping (node->node_child ("AudioMapping"), version) + , SingleStreamAudioContent (f, node, version) { - _audio_channels = node->number_child<int> ("AudioChannels"); - _audio_length = node->number_child<AudioContent::Frame> ("AudioLength"); - _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); + } +void +SndfileContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("Sndfile"); + Content::as_xml (node); + SingleStreamAudioContent::as_xml (node); +} + + string SndfileContent::summary () const { @@ -81,8 +84,8 @@ SndfileContent::information () const s << String::compose ( _("%1 channels, %2kHz, %3 samples"), audio_channels(), - content_audio_frame_rate() / 1000.0, - audio_length() + audio_frame_rate() / 1000.0, + audio_length().frames (audio_frame_rate ()) ); return s.str (); @@ -102,69 +105,15 @@ SndfileContent::examine (shared_ptr<Job> job) { job->set_progress_unknown (); Content::examine (job); - - shared_ptr<const Film> film = _film.lock (); - assert (film); - - SndfileDecoder dec (film, shared_from_this()); - - { - boost::mutex::scoped_lock lm (_mutex); - _audio_channels = dec.audio_channels (); - _audio_length = dec.audio_length (); - _audio_frame_rate = dec.audio_frame_rate (); - } - - signal_changed (AudioContentProperty::AUDIO_CHANNELS); - signal_changed (AudioContentProperty::AUDIO_LENGTH); - signal_changed (AudioContentProperty::AUDIO_FRAME_RATE); - - { - boost::mutex::scoped_lock lm (_mutex); - /* XXX: do this in signal_changed...? */ - _audio_mapping = AudioMapping (_audio_channels); - _audio_mapping.make_default (); - } - - signal_changed (AudioContentProperty::AUDIO_MAPPING); -} - -void -SndfileContent::as_xml (xmlpp::Node* node) const -{ - node->add_child("Type")->add_child_text ("Sndfile"); - Content::as_xml (node); - AudioContent::as_xml (node); - - node->add_child("AudioChannels")->add_child_text (raw_convert<string> (audio_channels ())); - node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length ())); - node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (content_audio_frame_rate ())); - _audio_mapping.as_xml (node->add_child("AudioMapping")); + shared_ptr<AudioExaminer> dec (new SndfileDecoder (shared_from_this())); + take_from_audio_examiner (dec); } -Time +DCPTime SndfileContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); - - FrameRateChange frc = film->active_frame_rate_change (position ()); - - OutputAudioFrame const len = divide_with_round ( - audio_length() * output_audio_frame_rate() * frc.source, - content_audio_frame_rate() * film->video_frame_rate() - ); - - return film->audio_frames_to_time (len); + return DCPTime (audio_length(), film->active_frame_rate_change (position ())); } -void -SndfileContent::set_audio_mapping (AudioMapping m) -{ - { - boost::mutex::scoped_lock lm (_mutex); - _audio_mapping = m; - } - - signal_changed (AudioContentProperty::AUDIO_MAPPING); -} diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index a32043c5c..75c723518 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -23,59 +23,31 @@ extern "C" { #include <libavutil/audioconvert.h> } -#include "audio_content.h" +#include "single_stream_audio_content.h" namespace cxml { class Node; } -class SndfileContent : public AudioContent +class SndfileContent : public SingleStreamAudioContent { public: SndfileContent (boost::shared_ptr<const Film>, boost::filesystem::path); - SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int); + SndfileContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int); boost::shared_ptr<SndfileContent> shared_from_this () { return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ()); } + DCPTime full_length () const; + 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; - Time full_length () const; - - /* AudioContent */ - int audio_channels () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_channels; - } - - AudioContent::Frame audio_length () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_length; - } - - int content_audio_frame_rate () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_frame_rate; - } - - AudioMapping audio_mapping () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_mapping; - } - - void set_audio_mapping (AudioMapping); static bool valid_file (boost::filesystem::path); - -private: - int _audio_channels; - AudioContent::Frame _audio_length; - int _audio_frame_rate; - AudioMapping _audio_mapping; }; #endif diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index f66a7c7dc..602014d58 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,13 +64,17 @@ 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. */ - sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2; + sf_count_t const block = _sndfile_content->audio_frame_rate() / 2; sf_count_t const this_time = min (block, _remaining); int const channels = _sndfile_content->audio_channels (); @@ -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,11 @@ 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 (); + 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..41d5faf08 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -20,27 +20,29 @@ #include <sndfile.h> #include "decoder.h" #include "audio_decoder.h" +#include "audio_examiner.h" class SndfileContent; -class SndfileDecoder : public AudioDecoder +class SndfileDecoder : public AudioDecoder, public AudioExaminer { public: - SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>); + SndfileDecoder (boost::shared_ptr<const SndfileContent> c); ~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..8a7423698 --- /dev/null +++ b/src/lib/subrip.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 "subrip.h" +#include "cross.h" +#include "exceptions.h" +#include "subrip_content.h" +#include <libsub/subrip_reader.h> +#include <libsub/collect.h> + +#include "i18n.h" + +using std::vector; +using boost::shared_ptr; + +SubRip::SubRip (shared_ptr<const SubRipContent> content) +{ + FILE* f = fopen_boost (content->path (0), "r"); + if (!f) { + throw OpenFileError (content->path (0)); + } + + sub::SubripReader reader (f); + _subtitles = sub::collect<vector<sub::Subtitle> > (reader.subtitles ()); +} + +ContentTime +SubRip::length () const +{ + if (_subtitles.empty ()) { + return ContentTime (); + } + + return ContentTime::from_seconds (_subtitles.back().to.metric().get().all_as_seconds ()); +} diff --git a/src/lib/subrip.h b/src/lib/subrip.h new file mode 100644 index 000000000..14bc360c0 --- /dev/null +++ b/src/lib/subrip.h @@ -0,0 +1,43 @@ +/* + 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" +#include <libsub/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<sub::Subtitle> _subtitles; +}; + +#endif diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc new file mode 100644 index 000000000..14cb50b86 --- /dev/null +++ b/src/lib/subrip_content.cc @@ -0,0 +1,98 @@ +/* + 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 <dcp/raw_convert.h> + +#include "i18n.h" + +using std::string; +using std::cout; +using dcp::raw_convert; +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, cxml::ConstNodePtr node, int version) + : Content (film, node) + , SubtitleContent (film, node, version) + , _length (node->number_child<DCPTime::Type> ("Length")) +{ + +} + +void +SubRipContent::examine (boost::shared_ptr<Job> job) +{ + Content::examine (job); + SubRip s (shared_from_this ()); + + shared_ptr<const Film> film = _film.lock (); + assert (film); + + 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 +{ + node->add_child("Type")->add_child_text ("SubRip"); + Content::as_xml (node); + SubtitleContent::as_xml (node); + node->add_child("Length")->add_child_text (raw_convert<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; +} diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h new file mode 100644 index 000000000..d2dcdee00 --- /dev/null +++ b/src/lib/subrip_content.h @@ -0,0 +1,47 @@ +/* + 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>, cxml::ConstNodePtr, int); + + boost::shared_ptr<SubRipContent> shared_from_this () { + return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ()); + } + + /* Content */ + 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; + + /* SubtitleContent */ + bool has_subtitles () const { + return true; + } + +private: + DCPTime _length; +}; diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc new file mode 100644 index 000000000..411e7542d --- /dev/null +++ b/src/lib/subrip_decoder.cc @@ -0,0 +1,105 @@ +/* + 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" +#include "subrip_content.h" + +using std::list; +using std::vector; +using boost::shared_ptr; + +SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content) + : SubtitleDecoder (content) + , SubRip (content) + , _next (0) +{ + +} + +void +SubRipDecoder::seek (ContentTime time, bool accurate) +{ + SubtitleDecoder::seek (time, accurate); + + _next = 0; + while (_next < _subtitles.size() && ContentTime::from_seconds (_subtitles[_next].from.metric().get().all_as_seconds ()) < time) { + ++_next; + } +} + +bool +SubRipDecoder::pass () +{ + if (_next >= _subtitles.size ()) { + return true; + } + + /* XXX: we are ignoring positioning specified in the file */ + + list<dcp::SubtitleString> out; + for (list<sub::Line>::const_iterator i = _subtitles[_next].lines.begin(); i != _subtitles[_next].lines.end(); ++i) { + for (list<sub::Block>::const_iterator j = i->blocks.begin(); j != i->blocks.end(); ++j) { + out.push_back ( + dcp::SubtitleString ( + "Arial", + j->italic, + dcp::Color (255, 255, 255), + /* .srt files don't specify size, so this is an arbitrary value */ + 48, + dcp::Time (rint (_subtitles[_next].from.metric().get().all_as_milliseconds() / 4)), + dcp::Time (rint (_subtitles[_next].to.metric().get().all_as_milliseconds() / 4)), + i->vertical_position.line.get() * (1.5 / 22) + 0.8, + dcp::TOP, + j->text, + dcp::NONE, + dcp::Color (255, 255, 255), + 0, + 0 + ) + ); + } + } + + text_subtitle (out); + ++_next; + return false; +} + +list<ContentTimePeriod> +SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +{ + /* XXX: inefficient */ + + list<ContentTimePeriod> d; + + for (vector<sub::Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + + ContentTimePeriod t ( + ContentTime::from_seconds (i->from.metric().get().all_as_seconds()), + ContentTime::from_seconds (i->to.metric().get().all_as_seconds()) + ); + + if ((starting && p.contains (t.from)) || (!starting && p.overlaps (t))) { + d.push_back (t); + } + } + + return d; +} diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h new file mode 100644 index 000000000..ad9d04e40 --- /dev/null +++ b/src/lib/subrip_decoder.h @@ -0,0 +1,43 @@ +/* + 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: + void seek (ContentTime time, bool accurate); + bool pass (); + +private: + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + size_t _next; +}; + +#endif diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h new file mode 100644 index 000000000..646fc1f7a --- /dev/null +++ b/src/lib/subrip_subtitle.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_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 +{ + ContentTimePeriod period; + 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.h b/src/lib/subtitle.h deleted file mode 100644 index c74f5c1b9..000000000 --- a/src/lib/subtitle.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - 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 <boost/shared_ptr.hpp> -#include <boost/weak_ptr.hpp> -#include <boost/optional.hpp> -#include <libdcp/util.h> -#include "rect.h" -#include "types.h" - -class Film; -class Piece; -class Image; - -class Subtitle -{ -public: - - Subtitle (boost::shared_ptr<const Film>, libdcp::Size, boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time); - - void update (boost::shared_ptr<const Film>, libdcp::Size); - void set_stop (Time t) { - _stop = t; - check_out_to (); - } - - bool covers (Time t) const; - bool ends_before (Time t) const { - return _out_to < t; - } - - boost::shared_ptr<Image> out_image () const { - return _out_image; - } - - Position<int> out_position () const { - return _out_position; - } - -private: - void check_out_to (); - - boost::weak_ptr<Piece> _piece; - boost::shared_ptr<Image> _in_image; - dcpomatic::Rect<double> _in_rect; - Time _in_from; - Time _in_to; - - boost::shared_ptr<Image> _out_image; - Position<int> _out_position; - Time _out_from; - Time _out_to; - - /** Time at which this subtitle should stop (overriding _out_to) */ - boost::optional<Time> _stop; -}; diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc index 3702eef41..5b370847b 100644 --- a/src/lib/subtitle_content.cc +++ b/src/lib/subtitle_content.cc @@ -18,26 +18,41 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "subtitle_content.h" #include "util.h" #include "exceptions.h" +#include "safe_stringstream.h" #include "i18n.h" using std::string; using std::vector; +using std::cout; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; int const SubtitleContentProperty::SUBTITLE_X_OFFSET = 500; int const SubtitleContentProperty::SUBTITLE_Y_OFFSET = 501; int const SubtitleContentProperty::SUBTITLE_X_SCALE = 502; int const SubtitleContentProperty::SUBTITLE_Y_SCALE = 503; +int const SubtitleContentProperty::USE_SUBTITLES = 504; + +SubtitleContent::SubtitleContent (shared_ptr<const Film> f) + : Content (f) + , _use_subtitles (false) + , _subtitle_x_offset (0) + , _subtitle_y_offset (0) + , _subtitle_x_scale (1) + , _subtitle_y_scale (1) +{ + +} SubtitleContent::SubtitleContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) + , _use_subtitles (false) , _subtitle_x_offset (0) , _subtitle_y_offset (0) , _subtitle_x_scale (1) @@ -46,13 +61,20 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, boost::filesystem::p } -SubtitleContent::SubtitleContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version) +SubtitleContent::SubtitleContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) : Content (f, node) + , _use_subtitles (false) , _subtitle_x_offset (0) , _subtitle_y_offset (0) , _subtitle_x_scale (1) , _subtitle_y_scale (1) { + if (version >= 32) { + _use_subtitles = node->bool_child ("UseSubtitles"); + } else { + _use_subtitles = false; + } + if (version >= 7) { _subtitle_x_offset = node->number_child<float> ("SubtitleXOffset"); _subtitle_y_offset = node->number_child<float> ("SubtitleYOffset"); @@ -77,6 +99,10 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Co for (size_t i = 0; i < c.size(); ++i) { shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c[i]); + if (sc->use_subtitles() != ref->use_subtitles()) { + throw JoinError (_("Content to be joined must have the same 'use subtitles' setting.")); + } + if (sc->subtitle_x_offset() != ref->subtitle_x_offset()) { throw JoinError (_("Content to be joined must have the same subtitle X offset.")); } @@ -94,6 +120,7 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Co } } + _use_subtitles = ref->use_subtitles (); _subtitle_x_offset = ref->subtitle_x_offset (); _subtitle_y_offset = ref->subtitle_y_offset (); _subtitle_x_scale = ref->subtitle_x_scale (); @@ -103,6 +130,7 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Co void SubtitleContent::as_xml (xmlpp::Node* root) const { + root->add_child("UseSubtitles")->add_child_text (raw_convert<string> (_use_subtitles)); root->add_child("SubtitleXOffset")->add_child_text (raw_convert<string> (_subtitle_x_offset)); root->add_child("SubtitleYOffset")->add_child_text (raw_convert<string> (_subtitle_y_offset)); root->add_child("SubtitleXScale")->add_child_text (raw_convert<string> (_subtitle_x_scale)); @@ -110,6 +138,16 @@ SubtitleContent::as_xml (xmlpp::Node* root) const } void +SubtitleContent::set_use_subtitles (bool u) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _use_subtitles = u; + } + signal_changed (SubtitleContentProperty::USE_SUBTITLES); +} + +void SubtitleContent::set_subtitle_x_offset (double o) { { @@ -148,3 +186,16 @@ SubtitleContent::set_subtitle_y_scale (double s) } signal_changed (SubtitleContentProperty::SUBTITLE_Y_SCALE); } + +string +SubtitleContent::identifier () const +{ + SafeStringStream s; + s << Content::identifier() + << "_" << raw_convert<string> (subtitle_x_scale()) + << "_" << raw_convert<string> (subtitle_y_scale()) + << "_" << raw_convert<string> (subtitle_x_offset()) + << "_" << raw_convert<string> (subtitle_y_offset()); + + return s.str (); +} diff --git a/src/lib/subtitle_content.h b/src/lib/subtitle_content.h index 329368e44..c3c25232f 100644 --- a/src/lib/subtitle_content.h +++ b/src/lib/subtitle_content.h @@ -29,22 +29,39 @@ public: static int const SUBTITLE_Y_OFFSET; static int const SUBTITLE_X_SCALE; static int const SUBTITLE_Y_SCALE; + static int const USE_SUBTITLES; }; +/** @class SubtitleContent + * @brief Parent for content which has the potential to include subtitles. + * + * Although inheriting from this class indicates that the content could + * have subtitles, it may not. ::has_subtitles() will tell you. + */ class SubtitleContent : public virtual Content { public: + SubtitleContent (boost::shared_ptr<const Film>); SubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path); - SubtitleContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version); + SubtitleContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int version); SubtitleContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); - + void as_xml (xmlpp::Node *) const; + std::string identifier () const; + + virtual bool has_subtitles () const = 0; + void set_use_subtitles (bool); void set_subtitle_x_offset (double); void set_subtitle_y_offset (double); void set_subtitle_x_scale (double); void set_subtitle_y_scale (double); + bool use_subtitles () const { + boost::mutex::scoped_lock lm (_mutex); + return _use_subtitles; + } + double subtitle_x_offset () const { boost::mutex::scoped_lock lm (_mutex); return _subtitle_x_offset; @@ -64,10 +81,11 @@ public: boost::mutex::scoped_lock lm (_mutex); return _subtitle_y_scale; } - + private: - friend class ffmpeg_pts_offset_test; + friend struct ffmpeg_pts_offset_test; + bool _use_subtitles; /** x offset for placing subtitles, as a proportion of the container width; * +ve is further right, -ve is further left. */ diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index c06f3d718..e1485e221 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -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 @@ -19,21 +19,84 @@ #include <boost/shared_ptr.hpp> #include "subtitle_decoder.h" +#include "subtitle_content.h" +using std::list; +using std::cout; using boost::shared_ptr; +using boost::optional; -SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f) - : Decoder (f) +SubtitleDecoder::SubtitleDecoder (shared_ptr<const SubtitleContent> c) + : _subtitle_content (c) { } - -/** 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 (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect) +{ + _decoded_image_subtitles.push_back (ContentImageSubtitle (period, image, rect)); +} + +void +SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s) +{ + _decoded_text_subtitles.push_back (ContentTextSubtitle (s)); +} + +template <class T> +list<T> +SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool starting) +{ + /* Get the full periods of the subtitles that are showing or starting during the specified period */ + list<ContentTimePeriod> sp = subtitles_during (period, starting); + if (sp.empty ()) { + /* Nothing in this period */ + return list<T> (); + } + + /* Seek if what we want is before what we have, or more than a reasonable amount after */ + if (subs.empty() || sp.back().to < subs.front().period().from || sp.front().from > (subs.back().period().to + ContentTime::from_seconds (5))) { + seek (sp.front().from, true); + } + + /* Now enough pass() calls will either: + * (a) give us what we want, or + * (b) hit the end of the decoder. + */ + while (!pass() && (subs.empty() || (subs.back().period().to < sp.back().to))) {} + + /* Now look for what we wanted in the data we have collected */ + /* XXX: inefficient */ + + list<T> out; + for (typename list<T>::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if ((starting && period.contains (i->period().from)) || (!starting && period.overlaps (i->period ()))) { + out.push_back (*i); + } + } + + return out; +} + +list<ContentTextSubtitle> +SubtitleDecoder::get_text_subtitles (ContentTimePeriod period, bool starting) +{ + return get<ContentTextSubtitle> (_decoded_text_subtitles, period, starting); +} + +list<ContentImageSubtitle> +SubtitleDecoder::get_image_subtitles (ContentTimePeriod period, bool starting) +{ + return get<ContentImageSubtitle> (_decoded_image_subtitles, period, starting); +} + +void +SubtitleDecoder::seek (ContentTime, bool) { - Subtitle (image, rect, from, to); + _decoded_text_subtitles.clear (); + _decoded_image_subtitles.clear (); } diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index eeeadbd3f..142cfa42b 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -17,22 +17,43 @@ */ -#include <boost/signals2.hpp> +#ifndef DCPOMATIC_SUBTITLE_DECODER_H +#define DCPOMATIC_SUBTITLE_DECODER_H + +#include <dcp/subtitle_string.h> #include "decoder.h" #include "rect.h" #include "types.h" +#include "content_subtitle.h" class Film; -class TimedSubtitle; +class DCPTimedSubtitle; class Image; class SubtitleDecoder : public virtual Decoder { public: - SubtitleDecoder (boost::shared_ptr<const Film>); + SubtitleDecoder (boost::shared_ptr<const SubtitleContent>); - boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle; + std::list<ContentImageSubtitle> get_image_subtitles (ContentTimePeriod period, bool starting); + std::list<ContentTextSubtitle> get_text_subtitles (ContentTimePeriod period, bool starting); protected: - void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time); + void seek (ContentTime, bool); + + void image_subtitle (ContentTimePeriod period, boost::shared_ptr<Image>, dcpomatic::Rect<double>); + void text_subtitle (std::list<dcp::SubtitleString>); + + std::list<ContentImageSubtitle> _decoded_image_subtitles; + std::list<ContentTextSubtitle> _decoded_text_subtitles; + +private: + template <class T> + std::list<T> get (std::list<T> const & subs, ContentTimePeriod period, bool starting); + + virtual std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const = 0; + + boost::shared_ptr<const SubtitleContent> _subtitle_content; }; + +#endif diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 831c74b3b..23a46d06d 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -37,6 +37,7 @@ using std::string; using std::fixed; using std::setprecision; +using std::cout; using boost::shared_ptr; /** @param s Film to use. @@ -100,6 +101,7 @@ TranscodeJob::status () const return s.str (); } +/** @return Approximate remaining time in seconds */ int TranscodeJob::remaining_time () const { @@ -117,6 +119,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 b11ce8be5..9d8eebe25 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -33,51 +33,52 @@ #include "audio_decoder.h" #include "player.h" #include "job.h" +#include "writer.h" using std::string; +using std::cout; +using std::list; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; -static void -video_proxy (weak_ptr<Encoder> encoder, shared_ptr<PlayerVideoFrame> pvf, bool same) -{ - shared_ptr<Encoder> e = encoder.lock (); - if (e) { - e->process_video (pvf, same); - } -} - -static void -audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio) -{ - shared_ptr<Encoder> e = encoder.lock (); - if (e) { - e->process_audio (audio); - } -} - /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. * @param e Encoder to use. */ Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j) - : _player (f->make_player ()) - , _encoder (new Encoder (f, j)) + : _film (f) + , _player (f->make_player ()) + , _writer (new Writer (f, j)) + , _encoder (new Encoder (f, j, _writer)) , _finishing (false) { - _player->Video.connect (bind (video_proxy, _encoder, _1, _2)); - _player->Audio.connect (bind (audio_proxy, _encoder, _1)); + } void Transcoder::go () { - _encoder->process_begin (); - while (!_player->pass ()) {} + _encoder->begin (); + + DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ()); + DCPTime const length = _film->length (); + for (DCPTime t; t < length; t += frame) { + list<shared_ptr<PlayerVideo> > v = _player->get_video (t, true); + for (list<shared_ptr<PlayerVideo> >::const_iterator i = v.begin(); i != v.end(); ++i) { + _encoder->enqueue (*i); + } + _writer->write (_player->get_audio (t, frame, true)); + if (!_film->burn_subtitles ()) { + _writer->write (_player->get_subtitles (t, frame, true)); + } + } _finishing = true; - _encoder->process_end (); + _encoder->end (); + _writer->finish (); + + _player->statistics().dump (_film->log ()); } float diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index d7736d4e8..ed0a6b1b5 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -42,7 +42,9 @@ public: } private: + boost::shared_ptr<const Film> _film; boost::shared_ptr<Player> _player; + boost::shared_ptr<Writer> _writer; boost::shared_ptr<Encoder> _encoder; bool _finishing; }; diff --git a/src/lib/types.cc b/src/lib/types.cc index 83bbf41e4..d052b2a9a 100644 --- a/src/lib/types.cc +++ b/src/lib/types.cc @@ -19,14 +19,14 @@ #include <libxml++/libxml++.h> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "types.h" using std::max; using std::min; using std::string; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; bool operator== (Crop const & a, Crop const & b) { diff --git a/src/lib/types.h b/src/lib/types.h index 4eb3d927e..9a6a30b86 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -23,7 +23,9 @@ #include <vector> #include <stdint.h> #include <boost/shared_ptr.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> +#include "dcpomatic_time.h" +#include "position.h" class Content; class VideoContent; @@ -46,31 +48,29 @@ namespace xmlpp { */ #define SERVER_LINK_VERSION 2 -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> +typedef int64_t VideoFrame; +typedef int64_t AudioFrame; + +/* XXX -> DCPAudio */ 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 @@ -120,7 +120,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/update.cc b/src/lib/update.cc index 7bec061e9..c50022091 100644 --- a/src/lib/update.cc +++ b/src/lib/update.cc @@ -21,7 +21,7 @@ #include <boost/algorithm/string.hpp> #include <curl/curl.h> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "update.h" #include "version.h" #include "ui_signaller.h" @@ -32,8 +32,9 @@ using std::cout; using std::min; using std::string; -using libdcp::raw_convert; +using dcp::raw_convert; +/** Singleton instance */ UpdateChecker* UpdateChecker::_instance = 0; static size_t @@ -42,6 +43,9 @@ write_callback_wrapper (void* data, size_t size, size_t nmemb, void* user) return reinterpret_cast<UpdateChecker*>(user)->write_callback (data, size, nmemb); } +/** Construct an UpdateChecker. This sets things up and starts a thread to + * do the work. + */ UpdateChecker::UpdateChecker () : _buffer (new char[BUFFER_SIZE]) , _offset (0) @@ -73,6 +77,7 @@ UpdateChecker::~UpdateChecker () delete[] _buffer; } +/** Start running the update check */ void UpdateChecker::run () { @@ -85,6 +90,7 @@ void UpdateChecker::thread () { while (true) { + /* Block until there is something to do */ boost::mutex::scoped_lock lock (_process_mutex); while (_to_do == 0) { _condition.wait (lock); @@ -94,12 +100,16 @@ UpdateChecker::thread () try { _offset = 0; + + /* Perform the request */ int r = curl_easy_perform (_curl); if (r != CURLE_OK) { set_state (FAILED); return; } + + /* Parse the reply */ _buffer[_offset] = '\0'; string s (_buffer); diff --git a/src/lib/update.h b/src/lib/update.h index e96ccec31..c86adb873 100644 --- a/src/lib/update.h +++ b/src/lib/update.h @@ -17,12 +17,17 @@ */ +/** @file src/lib/update.h + * @brief UpdateChecker class. + */ + #include <boost/signals2.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/condition.hpp> #include <boost/thread.hpp> #include <curl/curl.h> +/** Class to check for the existance of an update for DCP-o-matic on a remote server */ class UpdateChecker { public: @@ -32,28 +37,31 @@ public: void run (); enum State { - YES, - FAILED, - NO, - NOT_RUN + YES, ///< there is an update + FAILED, ///< the check failed, so we don't know + NO, ///< there is no update + NOT_RUN ///< the check has not been run (yet) }; + /** @return state of the checker */ State state () { boost::mutex::scoped_lock lm (_data_mutex); return _state; } + /** @return the version string of the latest stable version (if _state == YES or NO) */ std::string stable () { boost::mutex::scoped_lock lm (_data_mutex); return _stable; } + /** @return the version string of the latest test version (if _state == YES or NO) */ std::string test () { boost::mutex::scoped_lock lm (_data_mutex); return _test; } - /** @return true if the list signal emission was the first */ + /** @return true if the last signal emission was the first */ bool last_emit_was_first () const { boost::mutex::scoped_lock lm (_data_mutex); return _emits == 1; diff --git a/src/lib/upmixer_a.cc b/src/lib/upmixer_a.cc new file mode 100644 index 000000000..dce08fe37 --- /dev/null +++ b/src/lib/upmixer_a.cc @@ -0,0 +1,109 @@ +/* + 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 "upmixer_a.h" +#include "audio_buffers.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +UpmixerA::UpmixerA (int sampling_rate) + : _left (0.02, 1900.0 / sampling_rate, 4800.0 / sampling_rate) + , _right (0.02, 1900.0 / sampling_rate, 4800.0 / sampling_rate) + , _centre (0.02, 150.0 / sampling_rate, 1900.0 / sampling_rate) + , _lfe (0.02, 20.0 / sampling_rate, 150.0 / sampling_rate) + , _ls (0.02, 4800.0 / sampling_rate, 20000.0 / sampling_rate) + , _rs (0.02, 4800.0 / sampling_rate, 20000.0 / sampling_rate) +{ + +} + +string +UpmixerA::name () const +{ + return _("Stereo to 5.1 up-mixer A"); +} + + +string +UpmixerA::id () const +{ + return N_("stereo-5.1-upmix-a"); +} + +ChannelCount +UpmixerA::in_channels () const +{ + return ChannelCount (2); +} + +int +UpmixerA::out_channels (int) const +{ + return 6; +} + +shared_ptr<AudioProcessor> +UpmixerA::clone (int sampling_rate) const +{ + return shared_ptr<AudioProcessor> (new UpmixerA (sampling_rate)); +} + +shared_ptr<AudioBuffers> +UpmixerA::run (shared_ptr<const AudioBuffers> in) +{ + /* Input L and R */ + shared_ptr<AudioBuffers> in_L = in->channel (0); + shared_ptr<AudioBuffers> in_R = in->channel (1); + + /* Mix of L and R */ + shared_ptr<AudioBuffers> in_LR = in_L->clone (); + in_LR->accumulate_frames (in_R.get(), 0, 0, in_R->frames ()); + in_LR->apply_gain (0.5); + + /* Run filters */ + shared_ptr<AudioBuffers> L = _left.run (in_L); + shared_ptr<AudioBuffers> R = _right.run (in_R); + shared_ptr<AudioBuffers> C = _centre.run (in_LR); + shared_ptr<AudioBuffers> Lfe = _lfe.run (in_LR); + shared_ptr<AudioBuffers> Ls = _ls.run (in_L); + shared_ptr<AudioBuffers> Rs = _rs.run (in_R); + + shared_ptr<AudioBuffers> out (new AudioBuffers (6, in->frames ())); + out->copy_channel_from (L.get(), 0, 0); + out->copy_channel_from (R.get(), 0, 1); + out->copy_channel_from (C.get(), 0, 2); + out->copy_channel_from (Lfe.get(), 0, 3); + out->copy_channel_from (Ls.get(), 0, 4); + out->copy_channel_from (Rs.get(), 0, 5); + return out; +} + +void +UpmixerA::flush () +{ + _left.flush (); + _right.flush (); + _centre.flush (); + _lfe.flush (); + _ls.flush (); + _rs.flush (); +} diff --git a/src/lib/upmixer_a.h b/src/lib/upmixer_a.h new file mode 100644 index 000000000..32e3f5fb6 --- /dev/null +++ b/src/lib/upmixer_a.h @@ -0,0 +1,43 @@ +/* + 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 "audio_processor.h" +#include "audio_filter.h" + +class UpmixerA : public AudioProcessor +{ +public: + UpmixerA (int sampling_rate); + + std::string name () const; + std::string id () const; + ChannelCount in_channels () const; + int out_channels (int) const; + boost::shared_ptr<AudioProcessor> clone (int) const; + boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<const AudioBuffers>); + void flush (); + +private: + BandPassAudioFilter _left; + BandPassAudioFilter _right; + BandPassAudioFilter _centre; + BandPassAudioFilter _lfe; + BandPassAudioFilter _ls; + BandPassAudioFilter _rs; +}; diff --git a/src/lib/util.cc b/src/lib/util.cc index 290dd20ef..e0db5de2e 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -43,13 +43,13 @@ #endif #include <glib.h> #include <openjpeg.h> +#include <pangomm/init.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 <libdcp/raw_convert.h> +#include <dcp/version.h> +#include <dcp/util.h> +#include <dcp/signer.h> +#include <dcp/raw_convert.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> @@ -62,13 +62,15 @@ extern "C" { #include "scaler.h" #include "dcp_content_type.h" #include "filter.h" -#include "sound_processor.h" +#include "cinema_sound_processor.h" #include "config.h" #include "ratio.h" #include "job.h" #include "cross.h" #include "video_content.h" +#include "rect.h" #include "md5_digester.h" +#include "audio_processor.h" #include "safe_stringstream.h" #ifdef DCPOMATIC_WINDOWS #include "stack.hpp" @@ -99,8 +101,8 @@ using std::set_terminate; using boost::shared_ptr; using boost::thread; using boost::optional; -using libdcp::Size; -using libdcp::raw_convert; +using dcp::Size; +using dcp::raw_convert; static boost::thread::id ui_thread; static boost::filesystem::path backtrace_file; @@ -265,24 +267,6 @@ ffmpeg_version_to_string (int v) return s.str (); } -/** Return a user-readable string summarising the versions of our dependencies */ -string -dependency_version_summary () -{ - SafeStringStream s; - s << N_("libopenjpeg ") << opj_version () << N_(", ") - << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ") - << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ") - << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ") - << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ") - << 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; - - return s.str (); -} - double seconds (struct timeval t) { @@ -371,14 +355,16 @@ dcpomatic_setup () set_terminate (terminate); - libdcp::init (); + Pango::init (); + dcp::init (); Ratio::setup_ratios (); VideoContentScale::setup_scales (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); Filter::setup_filters (); - SoundProcessor::setup_sound_processors (); + CinemaSoundProcessor::setup_cinema_sound_processors (); + AudioProcessor::setup_audio_processors (); ui_thread = boost::this_thread::get_id (); } @@ -401,7 +387,7 @@ mo_path () boost::filesystem::path mo_path () { - return "DCP-o-matic.app/Contents/Resources"; + return "DCP-o-matic 2.app/Contents/Resources"; } #endif @@ -486,7 +472,10 @@ md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job) while (remaining > 0) { int const t = min (remaining, buffer_size); - fread (buffer, 1, t, f); + int const r = fread (buffer, 1, t, f); + if (r != t) { + throw ReadFileError (files[i], errno); + } digester.add (buffer, t); remaining -= t; @@ -656,6 +645,17 @@ stride_round_up (int c, int const * stride, int t) return a - (a % t); } +/** @param n A number. + * @param r Rounding `boundary' (must be a power of 2) + * @return n rounded to the nearest r + */ +int +round_to (float n, int r) +{ + assert (r == 1 || r == 2 || r == 4); + return int (n + float(r) / 2) &~ (r - 1); +} + /** Read a sequence of key / value pairs from a text stream; * the keys are the first words on the line, and the values are * the remainder of the line following the key. Lines beginning @@ -760,17 +760,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) { @@ -821,59 +810,6 @@ tidy_for_filename (string f) return t; } -shared_ptr<const libdcp::Signer> -make_signer () -{ - boost::filesystem::path const sd = Config::instance()->signer_chain_directory (); - - /* Remake the chain if any of it is missing */ - - list<boost::filesystem::path> files; - files.push_back ("ca.self-signed.pem"); - files.push_back ("intermediate.signed.pem"); - files.push_back ("leaf.signed.pem"); - files.push_back ("leaf.key"); - - list<boost::filesystem::path>::const_iterator i = files.begin(); - while (i != files.end()) { - boost::filesystem::path p (sd); - p /= *i; - if (!boost::filesystem::exists (p)) { - boost::filesystem::remove_all (sd); - boost::filesystem::create_directories (sd); - libdcp::make_signer_chain (sd, openssl_path ()); - break; - } - - ++i; - } - - libdcp::CertificateChain chain; - - { - boost::filesystem::path p (sd); - p /= "ca.self-signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); - } - - { - boost::filesystem::path p (sd); - p /= "intermediate.signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); - } - - { - boost::filesystem::path p (sd); - p /= "leaf.signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); - } - - boost::filesystem::path signer_key (sd); - signer_key /= "leaf.key"; - - return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key)); -} - map<string, string> split_get_request (string url) { @@ -920,14 +856,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, int round) { if (ratio < full_frame.ratio ()) { - return libdcp::Size (rint (full_frame.height * ratio), full_frame.height); + return dcp::Size (round_to (full_frame.height * ratio, round), full_frame.height); } - return libdcp::Size (full_frame.width, rint (full_frame.width / ratio)); + return dcp::Size (full_frame.width, round_to (full_frame.width / ratio, round)); } void * @@ -958,12 +894,34 @@ divide_with_round (int64_t a, int64_t b) } } +/** Return a user-readable string summarising the versions of our dependencies */ +string +dependency_version_summary () +{ + SafeStringStream s; + s << N_("libopenjpeg ") << opj_version () << N_(", ") + << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ") + << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ") + << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ") + << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ") + << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ") + << MagickVersion << N_(", ") + << N_("libssh ") << ssh_version (0) << N_(", ") + << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit; + + return s.str (); +} + +/** Construct a ScopedTemporary. A temporary filename is decided but the file is not opened + * until ::open() is called. + */ ScopedTemporary::ScopedTemporary () : _open (0) { _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path (); } +/** Close and delete the temporary file */ ScopedTemporary::~ScopedTemporary () { close (); @@ -971,12 +929,16 @@ ScopedTemporary::~ScopedTemporary () boost::filesystem::remove (_file, ec); } +/** @return temporary filename */ char const * ScopedTemporary::c_str () const { return _file.string().c_str (); } +/** Open the temporary file. + * @return File's FILE pointer. + */ FILE* ScopedTemporary::open (char const * params) { @@ -984,6 +946,7 @@ ScopedTemporary::open (char const * params) return _open; } +/** Close the file */ void ScopedTemporary::close () { @@ -992,3 +955,16 @@ ScopedTemporary::close () _open = 0; } } + +ContentTimePeriod +subtitle_period (AVSubtitle const & sub) +{ + ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE); + + ContentTimePeriod period ( + packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3), + packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3) + ); + + return period; +} diff --git a/src/lib/util.h b/src/lib/util.h index 675c8d03e..724e8937c 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -31,7 +31,7 @@ #include <boost/asio.hpp> #include <boost/optional.hpp> #include <boost/filesystem.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> @@ -47,11 +47,8 @@ extern "C" { #define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way" #define HISTORY_SIZE 10 -namespace libdcp { - class Signer; -} - class Job; +struct AVSubtitle; extern std::string seconds_to_hms (int); extern std::string seconds_to_approximate_hms (int); @@ -69,12 +66,12 @@ 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 dcp::Size fit_ratio_within (float ratio, dcp::Size, int); extern std::string entities_to_text (std::string e); extern std::map<std::string, std::string> split_get_request (std::string url); extern int dcp_audio_frame_rate (int); extern int stride_round_up (int, int const *, int); +extern int round_to (float n, int r); extern std::multimap<std::string, std::string> read_key_value (std::istream& s); extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k); extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k); @@ -83,6 +80,7 @@ extern int get_optional_int (std::multimap<std::string, std::string> const & kv, extern std::string get_optional_string (std::multimap<std::string, std::string> const & kv, std::string k); extern void* wrapped_av_malloc (size_t); extern int64_t divide_with_round (int64_t a, int64_t b); +extern ContentTimePeriod subtitle_period (AVSubtitle const &); /** @class Socket * @brief A class to wrap a boost::asio::ip::tcp::socket with some things @@ -125,16 +123,20 @@ private: extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second); +/** @class ScopedTemporary + * @brief A temporary file which is deleted when the ScopedTemporary object goes out of scope. + */ class ScopedTemporary { public: ScopedTemporary (); ~ScopedTemporary (); + /** @return temporary filename */ boost::filesystem::path file () const { return _file; } - + char const * c_str () const; FILE* open (char const *); void close (); diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 13f2cf516..ba656e4c2 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -19,8 +19,8 @@ #include <iomanip> #include <libcxml/cxml.h> -#include <libdcp/colour_matrix.h> -#include <libdcp/raw_convert.h> +#include <dcp/colour_matrix.h> +#include <dcp/raw_convert.h> #include "video_content.h" #include "video_examiner.h" #include "compose.hpp" @@ -31,16 +31,21 @@ #include "film.h" #include "exceptions.h" #include "frame_rate_change.h" +#include "log.h" #include "safe_stringstream.h" #include "i18n.h" +#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); + int const VideoContentProperty::VIDEO_SIZE = 0; int const VideoContentProperty::VIDEO_FRAME_RATE = 1; int const VideoContentProperty::VIDEO_FRAME_TYPE = 2; int const VideoContentProperty::VIDEO_CROP = 3; int const VideoContentProperty::VIDEO_SCALE = 4; int const VideoContentProperty::COLOUR_CONVERSION = 5; +int const VideoContentProperty::VIDEO_FADE_IN = 6; +int const VideoContentProperty::VIDEO_FADE_OUT = 7; using std::string; using std::setprecision; @@ -51,12 +56,11 @@ using std::max; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; VideoContent::VideoContent (shared_ptr<const Film> f) : Content (f) , _video_length (0) - , _original_video_frame_rate (0) , _video_frame_rate (0) , _video_frame_type (VIDEO_FRAME_TYPE_2D) , _scale (Config::instance()->default_scale ()) @@ -64,10 +68,9 @@ 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) - , _original_video_frame_rate (0) , _video_frame_rate (0) , _video_frame_type (VIDEO_FRAME_TYPE_2D) , _scale (Config::instance()->default_scale ()) @@ -78,7 +81,6 @@ VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Fram VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) , _video_length (0) - , _original_video_frame_rate (0) , _video_frame_rate (0) , _video_frame_type (VIDEO_FRAME_TYPE_2D) , _scale (Config::instance()->default_scale ()) @@ -86,14 +88,20 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p) setup_default_colour_conversion (); } -VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version) +VideoContent::VideoContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) : Content (f, node) { - _video_length = node->number_child<VideoContent::Frame> ("VideoLength"); _video_size.width = node->number_child<int> ("VideoWidth"); _video_size.height = node->number_child<int> ("VideoHeight"); _video_frame_rate = node->number_child<float> ("VideoFrameRate"); - _original_video_frame_rate = node->optional_number_child<float> ("OriginalVideoFrameRate").get_value_or (_video_frame_rate); + + if (version < 32) { + /* DCP-o-matic 1.0 branch */ + _video_length = ContentTime::from_frames (node->number_child<int64_t> ("VideoLength"), _video_frame_rate); + } else { + _video_length = ContentTime (node->number_child<ContentTime::Type> ("VideoLength")); + } + _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType")); _crop.left = node->number_child<int> ("LeftCrop"); _crop.right = node->number_child<int> ("RightCrop"); @@ -110,6 +118,10 @@ VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod } _colour_conversion = ColourConversion (node->node_child ("ColourConversion")); + if (version >= 32) { + _fade_in = ContentTime (node->number_child<int64_t> ("FadeIn")); + _fade_out = ContentTime (node->number_child<int64_t> ("FadeOut")); + } } VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c) @@ -146,55 +158,67 @@ VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> throw JoinError (_("Content to be joined must have the same colour conversion.")); } + if (vc->fade_in() != ref->fade_in() || vc->fade_out() != ref->fade_out()) { + throw JoinError (_("Content to be joined must have the same fades.")); + } + _video_length += vc->video_length (); } _video_size = ref->video_size (); - _original_video_frame_rate = ref->original_video_frame_rate (); _video_frame_rate = ref->video_frame_rate (); _video_frame_type = ref->video_frame_type (); _crop = ref->crop (); _scale = ref->scale (); _colour_conversion = ref->colour_conversion (); + _fade_in = ref->fade_in (); + _fade_out = ref->fade_out (); } void VideoContent::as_xml (xmlpp::Node* node) const { boost::mutex::scoped_lock lm (_mutex); - node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length)); + node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length.get ())); node->add_child("VideoWidth")->add_child_text (raw_convert<string> (_video_size.width)); node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height)); node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate)); - node->add_child("OriginalVideoFrameRate")->add_child_text (raw_convert<string> (_original_video_frame_rate)); node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type))); _crop.as_xml (node); _scale.as_xml (node->add_child("Scale")); _colour_conversion.as_xml (node->add_child("ColourConversion")); + node->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in.get ())); + node->add_child("FadeOut")->add_child_text (raw_convert<string> (_fade_out.get ())); } 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 (); - + ContentTime vl = d->video_length (); + { boost::mutex::scoped_lock lm (_mutex); _video_size = vs; _video_frame_rate = vfr; - _original_video_frame_rate = vfr; + _video_length = vl; } + + shared_ptr<const Film> film = _film.lock (); + assert (film); + LOG_GENERAL ("Video length obtained from header as %1 frames", _video_length.frames (_video_frame_rate)); signal_changed (VideoContentProperty::VIDEO_SIZE); signal_changed (VideoContentProperty::VIDEO_FRAME_RATE); + signal_changed (ContentProperty::LENGTH); } @@ -325,14 +349,17 @@ VideoContent::technical_summary () const { return String::compose ( "video: length %1, size %2x%3, rate %4", - video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate() + video_length_after_3d_combine().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: case VIDEO_FRAME_TYPE_3D_ALTERNATE: @@ -340,9 +367,9 @@ VideoContent::video_size_after_3d_split () const case VIDEO_FRAME_TYPE_3D_RIGHT: 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); @@ -359,29 +386,44 @@ VideoContent::set_colour_conversion (ColourConversion c) signal_changed (VideoContentProperty::COLOUR_CONVERSION); } +void +VideoContent::set_fade_in (ContentTime t) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _fade_in = t; + } + + signal_changed (VideoContentProperty::VIDEO_FADE_IN); +} + +void +VideoContent::set_fade_out (ContentTime t) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _fade_out = t; + } + + signal_changed (VideoContentProperty::VIDEO_FADE_OUT); +} + /** @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); - - FrameRateChange 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())); } void @@ -425,3 +467,19 @@ VideoContent::set_video_frame_rate (float r) signal_changed (VideoContentProperty::VIDEO_FRAME_RATE); } +optional<float> +VideoContent::fade (VideoFrame f) const +{ + assert (f >= 0); + + if (f < fade_in().frames (video_frame_rate ())) { + return float (f) / _fade_in.frames (video_frame_rate ()); + } + + VideoFrame fade_out_start = ContentTime (video_length() - fade_out()).frames (video_frame_rate ()); + if (f >= fade_out_start) { + return 1 - float (f - fade_out_start) / fade_out().frames (video_frame_rate ()); + } + + return optional<float> (); +} diff --git a/src/lib/video_content.h b/src/lib/video_content.h index 3a7b44306..e88fb0227 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -36,6 +36,8 @@ public: static int const VIDEO_CROP; static int const VIDEO_SCALE; static int const COLOUR_CONVERSION; + static int const VIDEO_FADE_IN; + static int const VIDEO_FADE_OUT; }; class VideoContent : public virtual Content @@ -44,9 +46,9 @@ 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>, cxml::ConstNodePtr, int); VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >); void as_xml (xmlpp::Node *) const; @@ -54,21 +56,21 @@ 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; } - VideoContent::Frame video_length_after_3d_combine () const { + ContentTime video_length_after_3d_combine () const { boost::mutex::scoped_lock lm (_mutex); if (_video_frame_type == VIDEO_FRAME_TYPE_3D_ALTERNATE) { - return _video_length / 2; + return ContentTime (_video_length.get() / 2); } return _video_length; } - libdcp::Size video_size () const { + dcp::Size video_size () const { boost::mutex::scoped_lock lm (_mutex); return _video_size; } @@ -78,11 +80,6 @@ public: return _video_frame_rate; } - float original_video_frame_rate () const { - boost::mutex::scoped_lock lm (_mutex); - return _original_video_frame_rate; - } - void set_video_frame_type (VideoFrameType); void set_video_frame_rate (float); @@ -93,6 +90,9 @@ public: void set_scale (VideoContentScale); void set_colour_conversion (ColourConversion); + + void set_fade_in (ContentTime); + void set_fade_out (ContentTime); VideoFrameType video_frame_type () const { boost::mutex::scoped_lock lm (_mutex); @@ -135,10 +135,22 @@ public: return _colour_conversion; } - libdcp::Size video_size_after_3d_split () const; - libdcp::Size video_size_after_crop () const; + ContentTime fade_in () const { + boost::mutex::scoped_lock lm (_mutex); + return _fade_in; + } + + ContentTime fade_out () const { + boost::mutex::scoped_lock lm (_mutex); + return _fade_out; + } + + dcp::Size video_size_after_3d_split () const; + dcp::Size video_size_after_crop () const; + + ContentTime dcp_time_to_content_time (DCPTime) const; - VideoContent::Frame time_to_content_video_frames (Time) const; + boost::optional<float> fade (VideoFrame) const; void scale_and_crop_to_fit_width (); void scale_and_crop_to_fit_height (); @@ -146,23 +158,24 @@ public: protected: void take_from_video_examiner (boost::shared_ptr<VideoExaminer>); - VideoContent::Frame _video_length; - float _original_video_frame_rate; + ContentTime _video_length; float _video_frame_rate; private: - friend class ffmpeg_pts_offset_test; - friend class best_dcp_frame_rate_test_single; - friend class best_dcp_frame_rate_test_double; - friend class audio_sampling_rate_test; + friend struct ffmpeg_pts_offset_test; + friend struct best_dcp_frame_rate_test_single; + friend struct best_dcp_frame_rate_test_double; + friend struct audio_sampling_rate_test; void setup_default_colour_conversion (); - libdcp::Size _video_size; + dcp::Size _video_size; VideoFrameType _video_frame_type; Crop _crop; VideoContentScale _scale; ColourConversion _colour_conversion; + ContentTime _fade_in; + ContentTime _fade_out; }; #endif diff --git a/src/lib/video_content_scale.cc b/src/lib/video_content_scale.cc index e603582b8..418c46eec 100644 --- a/src/lib/video_content_scale.cc +++ b/src/lib/video_content_scale.cc @@ -123,24 +123,24 @@ VideoContentScale::from_id (string id) /** @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, int round) const { if (_ratio) { - return fit_ratio_within (_ratio->ratio (), display_container); + return fit_ratio_within (_ratio->ratio (), display_container, round); } - libdcp::Size const ac = c->video_size_after_crop (); + dcp::Size const ac = c->video_size_after_crop (); /* Force scale if the film_container is smaller than the content's image */ if (_scale || film_container.width < ac.width || film_container.height < ac.height) { - return fit_ratio_within (ac.ratio (), display_container); + return fit_ratio_within (ac.ratio (), display_container, round); } /* Scale the image so that it will be in the right place in film_container, even if display_container is a different size. */ - return libdcp::Size ( + return dcp::Size ( c->video_size().width * float(display_container.width) / film_container.width, c->video_size().height * float(display_container.height) / film_container.height ); diff --git a/src/lib/video_content_scale.h b/src/lib/video_content_scale.h index 87dd2f1fa..6b718d574 100644 --- a/src/lib/video_content_scale.h +++ b/src/lib/video_content_scale.h @@ -22,7 +22,7 @@ #include <vector> #include <boost/shared_ptr.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> namespace cxml { class Node; @@ -43,7 +43,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, int round) const; std::string id () const; std::string name () const; void as_xml (xmlpp::Node *) const; diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 5867ac925..64c66ea55 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -19,46 +19,164 @@ #include "video_decoder.h" #include "image.h" +#include "image_proxy.h" +#include "content_video.h" #include "i18n.h" using std::cout; +using std::list; +using std::max; using boost::shared_ptr; +using boost::optional; -VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c) - : Decoder (f) +VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c) +#ifdef DCPOMATIC_DEBUG + : test_gaps (0) , _video_content (c) - , _video_position (0) +#else + : _video_content (c) +#endif + , _same (false) { } +list<ContentVideo> +VideoDecoder::decoded_video (VideoFrame frame) +{ + list<ContentVideo> output; + + for (list<ContentVideo>::const_iterator i = _decoded_video.begin(); i != _decoded_video.end(); ++i) { + if (i->frame == frame) { + output.push_back (*i); + } + } + + return output; +} + +/** Get all frames which exist in the content at a given frame index. + * @param frame Frame index. + * @param accurate true to try hard to return frames at the precise time that was requested, otherwise frames nearby may be returned. + * @return Frames; there may be none (if there is no video there), 1 for 2D or 2 for 3D. + */ +list<ContentVideo> +VideoDecoder::get_video (VideoFrame frame, bool accurate) +{ + /* At this stage, if we have get_video()ed before, _decoded_video will contain the last frame that this + method returned (and possibly a few more). If the requested frame is not in _decoded_video and it is not the next + one after the end of _decoded_video we need to seek. + */ + + if (_decoded_video.empty() || frame < _decoded_video.front().frame || frame > (_decoded_video.back().frame + 1)) { + seek (ContentTime::from_frames (frame, _video_content->video_frame_rate()), accurate); + } + + list<ContentVideo> dec; + + /* Now enough pass() calls should either: + * (a) give us what we want, or + * (b) give us something after what we want, indicating that we will never get what we want, or + * (c) hit the end of the decoder. + */ + if (accurate) { + /* We are being accurate, so we want the right frame. + * This could all be one statement but it's split up for clarity. + */ + while (true) { + if (!decoded_video(frame).empty ()) { + /* We got what we want */ + break; + } + + if (pass ()) { + /* The decoder has nothing more for us */ + break; + } + + if (!_decoded_video.empty() && _decoded_video.front().frame > frame) { + /* We're never going to get the frame we want. Perhaps the caller is asking + * for a video frame before the content's video starts (if its audio + * begins before its video, for example). + */ + break; + } + } + + dec = decoded_video (frame); + } else { + /* Any frame will do: use the first one that comes out of pass() */ + while (_decoded_video.empty() && !pass ()) {} + if (!_decoded_video.empty ()) { + dec.push_back (_decoded_video.front ()); + } + } + + /* Clean up _decoded_video; keep the frame we are returning, but nothing before that */ + while (!_decoded_video.empty() && _decoded_video.front().frame < dec.front().frame) { + _decoded_video.pop_front (); + } + + return dec; +} + + +/** Called by subclasses when they have a video frame ready */ void -VideoDecoder::video (shared_ptr<const ImageProxy> image, bool same, VideoContent::Frame frame) +VideoDecoder::video (shared_ptr<const ImageProxy> image, VideoFrame frame) { + /* We may receive the same frame index twice for 3D, and we need to know + when that happens. + */ + _same = (!_decoded_video.empty() && frame == _decoded_video.back().frame); + + /* Fill in gaps */ + /* XXX: 3D */ + + while (!_decoded_video.empty () && (_decoded_video.back().frame + 1) < frame) { +#ifdef DCPOMATIC_DEBUG + test_gaps++; +#endif + _decoded_video.push_back ( + ContentVideo ( + _decoded_video.back().image, + _decoded_video.back().eyes, + _decoded_video.back().part, + _decoded_video.back().frame + 1 + ) + ); + } + switch (_video_content->video_frame_type ()) { case VIDEO_FRAME_TYPE_2D: - Video (image, EYES_BOTH, PART_WHOLE, same, frame); + _decoded_video.push_back (ContentVideo (image, EYES_BOTH, PART_WHOLE, frame)); break; case VIDEO_FRAME_TYPE_3D_ALTERNATE: - Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, same, frame / 2); + _decoded_video.push_back (ContentVideo (image, _same ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, frame)); break; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: - Video (image, EYES_LEFT, PART_LEFT_HALF, same, frame); - Video (image, EYES_RIGHT, PART_RIGHT_HALF, same, frame); + _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_LEFT_HALF, frame)); + _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_RIGHT_HALF, frame)); break; case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: - Video (image, EYES_LEFT, PART_TOP_HALF, same, frame); - Video (image, EYES_RIGHT, PART_BOTTOM_HALF, same, frame); + _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_TOP_HALF, frame)); + _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_BOTTOM_HALF, frame)); break; case VIDEO_FRAME_TYPE_3D_LEFT: - Video (image, EYES_LEFT, PART_WHOLE, same, frame); + _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_WHOLE, frame)); break; case VIDEO_FRAME_TYPE_3D_RIGHT: - Video (image, EYES_RIGHT, PART_WHOLE, same, frame); + _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_WHOLE, frame)); break; + default: + assert (false); } - - _video_position = frame + 1; +} + +void +VideoDecoder::seek (ContentTime, bool) +{ + _decoded_video.clear (); } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 42add42aa..f5c3cd743 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/video_decoder.h + * @brief VideoDecoder class. + */ + #ifndef DCPOMATIC_VIDEO_DECODER_H #define DCPOMATIC_VIDEO_DECODER_H @@ -25,37 +29,38 @@ #include "decoder.h" #include "video_content.h" #include "util.h" +#include "content_video.h" class VideoContent; class ImageProxy; +/** @class VideoDecoder + * @brief Parent for classes which decode video. + */ 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 the part of this image that should be used. - * Fourth 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 ImageProxy>, Eyes, Part, bool, VideoContent::Frame)> Video; - + VideoDecoder (boost::shared_ptr<const VideoContent> c); + + std::list<ContentVideo> get_video (VideoFrame frame, bool accurate); + + boost::shared_ptr<const VideoContent> video_content () const { + return _video_content; + } + +#ifdef DCPOMATIC_DEBUG + int test_gaps; +#endif + protected: - void video (boost::shared_ptr<const ImageProxy>, bool, VideoContent::Frame); + void seek (ContentTime time, bool accurate); + void video (boost::shared_ptr<const ImageProxy>, VideoFrame frame); + std::list<ContentVideo> decoded_video (VideoFrame frame); + boost::shared_ptr<const VideoContent> _video_content; - /** This is in frames without taking 3D into account (e.g. if we are doing 3D alternate, - * this would equal 2 on the left-eye second frame (not 1)). - */ - VideoContent::Frame _video_position; + std::list<ContentVideo> _decoded_video; + bool _same; }; #endif diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h index 039c494b5..87e9a0428 100644 --- a/src/lib/video_examiner.h +++ b/src/lib/video_examiner.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,15 +17,22 @@ */ -#include <libdcp/types.h> +/** @file src/lib/video_examiner.h + * @brief VideoExaminer class. + */ + +#include <dcp/types.h> #include "types.h" #include "video_content.h" +/** @class VideoExaminer + * @brief Parent for classes which examine video sources and obtain information about them. + */ 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 5af1aea1e..6262525c8 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -19,25 +19,32 @@ #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/reel_subtitle_asset.h> +#include <dcp/dcp.h> +#include <dcp/cpl.h> +#include <dcp/signer.h> #include "writer.h" #include "compose.hpp" #include "film.h" #include "ratio.h" #include "log.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "dcp_content_type.h" -#include "player.h" #include "audio_mapping.h" #include "config.h" #include "job.h" #include "cross.h" +#include "audio_buffers.h" #include "md5_digester.h" +#include "encoded_data.h" #include "version.h" #include "i18n.h" @@ -57,6 +64,7 @@ using std::list; using std::cout; 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; @@ -71,7 +79,6 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j) , _last_written_eyes (EYES_RIGHT) , _full_written (0) , _fake_written (0) - , _repeat_written (0) , _pushed_to_disk (0) { /* Remove any old DCP */ @@ -89,36 +96,39 @@ 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 + ); if (_film->audio_channels ()) { - _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); + } + + /* Check that the signer is OK if we need one */ + if (_film->is_signed() && !Config::instance()->signer()->valid ()) { + throw InvalidSignerError (); } _thread = new boost::thread (boost::bind (&Writer::thread, this)); @@ -175,7 +185,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; @@ -200,8 +210,8 @@ Writer::fake_write (int frame, Eyes eyes) void Writer::write (shared_ptr<const AudioBuffers> audio) { - if (_sound_asset) { - _sound_asset_writer->write (audio->data(), audio->frames()); + if (_sound_mxf_writer) { + _sound_mxf_writer->write (audio->data(), audio->frames()); } } @@ -277,7 +287,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; @@ -285,39 +295,27 @@ try } case QueueItem::FAKE: LOG_GENERAL (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: - { - LOG_GENERAL (N_("Writer REPEAT-writes %1 to MXF"), qi.frame); - libdcp::FrameInfo fin = _picture_asset_writer->write ( - _last_written[qi.eyes]->data(), - _last_written[qi.eyes]->size() - ); - - _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin); - ++_repeat_written; - break; - } } lock.lock (); _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; - } - job->set_progress (float (_full_written + _fake_written + _repeat_written) / total); + 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) / total); } } @@ -392,15 +390,11 @@ Writer::finish () terminate_thread (true); - _picture_asset_writer->finalize (); - if (_sound_asset_writer) { - _sound_asset_writer->finalize (); + _picture_mxf_writer->finalize (); + if (_sound_mxf_writer) { + _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(); @@ -421,14 +415,11 @@ Writer::finish () } } - /* And update the asset */ - - _picture_asset->set_directory (_film->dir (_film->dcp_name ())); - _picture_asset->set_file_name (_film->video_mxf_filename ()); + _picture_mxf->set_file (video_to); /* Move the audio MXF into the DCP */ - if (_sound_asset) { + if (_sound_mxf) { boost::filesystem::path audio_to; audio_to /= _film->dir (_film->dcp_name ()); audio_to /= _film->audio_mxf_filename (); @@ -439,80 +430,86 @@ Writer::finish () String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ()) ); } - - _sound_asset->set_directory (_film->dir (_film->dcp_name ())); - _sound_asset->set_duration (frames); + + _sound_mxf->set_file (audio_to); } - - libdcp::DCP dcp (_film->dir (_film->dcp_name())); - shared_ptr<libdcp::CPL> cpl ( - new libdcp::CPL ( - _film->dir (_film->dcp_name()), + dcp::DCP dcp (_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))); + dcp.add (mono); + } + + 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))); + dcp.add (stereo); + } + + if (_sound_mxf) { + reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0))); + dcp.add (_sound_mxf); + } - cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel ( - _picture_asset, - _sound_asset, - shared_ptr<libdcp::SubtitleAsset> () - ) - )); + if (_subtitle_content) { + _subtitle_content->write_xml (_film->dir (_film->dcp_name ()) / _film->subtitle_xml_filename ()); + reel->add (shared_ptr<dcp::ReelSubtitleAsset> ( + new dcp::ReelSubtitleAsset ( + _subtitle_content, + dcp::Fraction (_film->video_frame_rate(), 1), + _picture_mxf->intrinsic_duration (), + 0 + ) + )); + + dcp.add (_subtitle_content); + } + + 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)); - if (_sound_asset) { + if (_sound_mxf) { 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; + dcp::XMLMetadata meta; meta.issuer = Config::instance()->dcp_issuer (); meta.creator = String::compose ("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit); meta.set_issue_date_now (); - dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ()); - - LOG_GENERAL ( - N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk - ); -} -/** Tell the writer that frame `f' should be a repeat of the frame before it */ -void -Writer::repeat (int f, Eyes e) -{ - boost::mutex::scoped_lock lock (_mutex); - - while (_queued_full_in_memory > _maximum_frames_in_memory) { - /* The queue is too big; wait until that is sorted out */ - _full_condition.wait (lock); - } - - QueueItem qi; - qi.type = QueueItem::REPEAT; - qi.frame = f; - if (_film->three_d() && e == EYES_BOTH) { - qi.eyes = EYES_LEFT; - _queue.push_back (qi); - qi.eyes = EYES_RIGHT; - _queue.push_back (qi); - } else { - qi.eyes = e; - _queue.push_back (qi); + shared_ptr<const dcp::Signer> signer; + if (_film->is_signed ()) { + signer = Config::instance()->signer (); + /* We did check earlier, but check again here to be on the safe side */ + if (!signer->valid ()) { + throw InvalidSignerError (); + } } - /* Now there's something to do: wake anything wait()ing on _empty_condition */ - _empty_condition.notify_all (); + dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer); + + LOG_GENERAL ( + N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk + ); } bool @@ -525,7 +522,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) { LOG_GENERAL ("Existing frame %1 has no info file", f); @@ -610,6 +607,24 @@ Writer::can_fake_write (int frame) const return (frame != 0 && frame < _first_nonexistant_frame); } +void +Writer::write (PlayerSubtitles subs) +{ + if (subs.text.empty ()) { + return; + } + + if (!_subtitle_content) { + _subtitle_content.reset ( + new dcp::SubtitleContent (_film->name(), _film->isdcf_metadata().subtitle_language) + ); + } + + for (list<dcp::SubtitleString>::const_iterator i = subs.text.begin(); i != subs.text.end(); ++i) { + _subtitle_content->add (*i); + } +} + bool operator< (QueueItem const & a, QueueItem const & b) { diff --git a/src/lib/writer.h b/src/lib/writer.h index c0699ad44..66fe98ec7 100644 --- a/src/lib/writer.h +++ b/src/lib/writer.h @@ -17,27 +17,33 @@ */ +/** @file src/lib/writer.h + * @brief Writer class. + */ + #include <list> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> #include <boost/thread/condition.hpp> +#include <dcp/subtitle_content.h> #include "exceptions.h" #include "types.h" +#include "player_subtitles.h" class Film; 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 @@ -51,8 +57,6 @@ public: state but we use the data that is already on disk. */ FAKE, - /** this is a repeat of the last frame to be written */ - REPEAT } type; /** encoded data for FULL */ @@ -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: @@ -78,6 +93,7 @@ public: void write (boost::shared_ptr<const EncodedData>, int, Eyes); void fake_write (int, Eyes); void write (boost::shared_ptr<const AudioBuffers>); + void write (PlayerSubtitles); void repeat (int f, Eyes); void finish (); @@ -123,15 +139,14 @@ private: int _full_written; /** number of FAKE written frames */ int _fake_written; - /** number of REPEAT written frames */ - int _repeat_written; /** number of frames pushed to disk and then recovered due to the limit of frames to be held in memory. */ 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; + boost::shared_ptr<dcp::SubtitleContent> _subtitle_content; }; diff --git a/src/lib/wscript b/src/lib/wscript index 6c1da1772..d62c22bae 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -7,26 +7,39 @@ sources = """ audio_buffers.cc audio_content.cc audio_decoder.cc + audio_filter.cc audio_mapping.cc + audio_processor.cc cinema.cc + cinema_sound_processor.cc colour_conversion.cc config.cc content.cc content_factory.cc + content_subtitle.cc cross.cc + dcp_content.cc dcp_content_type.cc - dcp_video_frame.cc - decoder.cc + dcp_decoder.cc + dcp_examiner.cc + dcp_subtitle_content.cc + dcp_subtitle_decoder.cc + dcp_video.cc + dcpomatic_time.cc dolby_cp750.cc encoder.cc + encoded_data.cc examine_content_job.cc exceptions.cc file_group.cc filter_graph.cc ffmpeg.cc + ffmpeg_audio_stream.cc ffmpeg_content.cc ffmpeg_decoder.cc ffmpeg_examiner.cc + ffmpeg_stream.cc + ffmpeg_subtitle_stream.cc film.cc filter.cc frame_rate_change.cc @@ -37,16 +50,21 @@ sources = """ image_examiner.cc image_proxy.cc isdcf_metadata.cc + j2k_image_proxy.cc job.cc job_manager.cc kdm.cc log.cc + magick_image_proxy.cc md5_digester.cc - piece.cc + mid_side_decoder.cc player.cc - player_video_frame.cc + player_video.cc playlist.cc + position_image.cc ratio.cc + raw_image_proxy.cc + render_subtitles.cc resampler.cc safe_stringstream.cc scp_dcp_job.cc @@ -54,10 +72,12 @@ sources = """ send_kdm_email_job.cc server.cc server_finder.cc + single_stream_audio_content.cc sndfile_content.cc sndfile_decoder.cc - sound_processor.cc - subtitle.cc + subrip.cc + subrip_content.cc + subrip_decoder.cc subtitle_content.cc subtitle_decoder.cc timer.cc @@ -66,6 +86,7 @@ sources = """ types.cc ui_signaller.cc update.cc + upmixer_a.cc util.cc video_content.cc video_content_scale.cc @@ -79,13 +100,13 @@ def build(bld): else: obj = bld(features = 'cxx cxxshlib') - obj.name = 'libdcpomatic' + obj.name = 'libdcpomatic2' obj.export_includes = ['..'] obj.uselib = """ AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 - SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XMLPP - CURL ZIP QUICKMAIL XMLSEC + SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++ + CURL ZIP QUICKMAIL PANGOMM CAIROMM XMLSEC SUB """ if bld.env.TARGET_OSX: @@ -99,9 +120,9 @@ def build(bld): if bld.env.BUILD_STATIC: obj.uselib += ' XMLPP' - obj.target = 'dcpomatic' + obj.target = 'dcpomatic2' - i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld) + i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic2', bld) def pot(bld): i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic') |
