summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/analyse_audio_job.cc4
-rw-r--r--src/lib/analyse_audio_job.h4
-rw-r--r--src/lib/audio_content.cc26
-rw-r--r--src/lib/audio_content.h6
-rw-r--r--src/lib/audio_decoder.cc53
-rw-r--r--src/lib/audio_decoder.h20
-rw-r--r--src/lib/audio_mapping.cc14
-rw-r--r--src/lib/audio_mapping.h4
-rw-r--r--src/lib/audio_merger.h11
-rw-r--r--src/lib/cinema.cc2
-rw-r--r--src/lib/cinema.h4
-rw-r--r--src/lib/colour_conversion.cc2
-rw-r--r--src/lib/config.cc8
-rw-r--r--src/lib/config.h6
-rw-r--r--src/lib/content.cc25
-rw-r--r--src/lib/content.h30
-rw-r--r--src/lib/content_factory.cc8
-rw-r--r--src/lib/dcp_content_type.cc22
-rw-r--r--src/lib/dcp_content_type.h6
-rw-r--r--src/lib/dcp_video_frame.cc18
-rw-r--r--src/lib/dcp_video_frame.h8
-rw-r--r--src/lib/decoded.h145
-rw-r--r--src/lib/decoder.cc41
-rw-r--r--src/lib/decoder.h24
-rw-r--r--src/lib/encoder.cc2
-rw-r--r--src/lib/exceptions.cc8
-rw-r--r--src/lib/exceptions.h7
-rw-r--r--src/lib/ffmpeg.cc4
-rw-r--r--src/lib/ffmpeg_content.cc15
-rw-r--r--src/lib/ffmpeg_content.h4
-rw-r--r--src/lib/ffmpeg_decoder.cc275
-rw-r--r--src/lib/ffmpeg_decoder.h13
-rw-r--r--src/lib/ffmpeg_examiner.cc8
-rw-r--r--src/lib/ffmpeg_examiner.h4
-rw-r--r--src/lib/film.cc60
-rw-r--r--src/lib/film.h27
-rw-r--r--src/lib/filter_graph.cc9
-rw-r--r--src/lib/filter_graph.h6
-rw-r--r--src/lib/image.cc51
-rw-r--r--src/lib/image.h10
-rw-r--r--src/lib/image_content.cc6
-rw-r--r--src/lib/image_content.h4
-rw-r--r--src/lib/image_decoder.cc27
-rw-r--r--src/lib/image_decoder.h9
-rw-r--r--src/lib/image_examiner.cc4
-rw-r--r--src/lib/image_examiner.h8
-rw-r--r--src/lib/job.cc4
-rw-r--r--src/lib/kdm.cc8
-rw-r--r--src/lib/player.cc614
-rw-r--r--src/lib/player.h128
-rw-r--r--src/lib/playlist.cc35
-rw-r--r--src/lib/playlist.h6
-rw-r--r--src/lib/render_subtitles.cc150
-rw-r--r--src/lib/render_subtitles.h27
-rw-r--r--src/lib/resampler.cc8
-rw-r--r--src/lib/resampler.h2
-rw-r--r--src/lib/server.cc4
-rw-r--r--src/lib/sndfile_content.cc6
-rw-r--r--src/lib/sndfile_content.h6
-rw-r--r--src/lib/sndfile_decoder.cc22
-rw-r--r--src/lib/sndfile_decoder.h11
-rw-r--r--src/lib/subrip.cc236
-rw-r--r--src/lib/subrip.h53
-rw-r--r--src/lib/subrip_content.cc100
-rw-r--r--src/lib/subrip_content.h42
-rw-r--r--src/lib/subrip_decoder.cc66
-rw-r--r--src/lib/subrip_decoder.h39
-rw-r--r--src/lib/subrip_subtitle.h58
-rw-r--r--src/lib/subtitle_decoder.cc14
-rw-r--r--src/lib/subtitle_decoder.h14
-rw-r--r--src/lib/transcode_job.cc2
-rw-r--r--src/lib/transcoder.cc5
-rw-r--r--src/lib/transcoder.h1
-rw-r--r--src/lib/types.h11
-rw-r--r--src/lib/util.cc45
-rw-r--r--src/lib/util.h17
-rw-r--r--src/lib/video_content.cc34
-rw-r--r--src/lib/video_content.h16
-rw-r--r--src/lib/video_decoder.cc20
-rw-r--r--src/lib/video_decoder.h21
-rw-r--r--src/lib/video_examiner.h4
-rw-r--r--src/lib/writer.cc109
-rw-r--r--src/lib/writer.h26
-rw-r--r--src/lib/wscript6
84 files changed, 2094 insertions, 928 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
index 8186f9de4..872947b55 100644
--- a/src/lib/analyse_audio_job.cc
+++ b/src/lib/analyse_audio_job.cc
@@ -69,7 +69,7 @@ AnalyseAudioJob::run ()
_analysis.reset (new AudioAnalysis (_film->audio_channels ()));
_done = 0;
- OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
+ AudioFrame const len = _film->time_to_audio_frames (_film->length ());
while (!player->pass ()) {
set_progress (double (_done) / len);
}
@@ -81,7 +81,7 @@ AnalyseAudioJob::run ()
}
void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, DCPTime)
{
for (int i = 0; i < b->frames(); ++i) {
for (int j = 0; j < b->channels(); ++j) {
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
index 3d4881983..6ed236d85 100644
--- a/src/lib/analyse_audio_job.h
+++ b/src/lib/analyse_audio_job.h
@@ -33,10 +33,10 @@ public:
void run ();
private:
- void audio (boost::shared_ptr<const AudioBuffers>, Time);
+ void audio (boost::shared_ptr<const AudioBuffers>, DCPTime);
boost::weak_ptr<AudioContent> _content;
- OutputAudioFrame _done;
+ AudioFrame _done;
int64_t _samples_per_point;
std::vector<AudioPoint> _current;
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
index b4c4f34b6..3c0d13ba9 100644
--- a/src/lib/audio_content.cc
+++ b/src/lib/audio_content.cc
@@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
int const AudioContentProperty::AUDIO_DELAY = 204;
int const AudioContentProperty::AUDIO_MAPPING = 205;
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
: Content (f, s)
, _audio_gain (0)
, _audio_delay (Config::instance()->default_audio_delay ())
@@ -149,3 +149,27 @@ 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());
}
+
+/** Note: this is not particularly fast, as the FrameRateChange lookup
+ * is not very intelligent.
+ *
+ * @param t Some duration to convert.
+ * @param at The time within the DCP to get the active frame rate change from; i.e. a point at which
+ * the `controlling' video content is active.
+ */
+AudioFrame
+AudioContent::time_to_content_audio_frames (DCPTime t, DCPTime at) const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ /* Consider the case where we're running a 25fps video at 24fps (i.e. slow)
+ Our audio is at 44.1kHz. We will resample it to 48000 * 25 / 24 and then
+ run it at 48kHz (i.e. slow, to match).
+
+ After 1 second, we'll have run the equivalent of 44.1kHz * 24 / 25 samples
+ in the source.
+ */
+
+ return rint (t * content_audio_frame_rate() * film->active_frame_rate_change(at).speed_up / TIME_HZ);
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
index ca4a1f234..0b2ee2e46 100644
--- a/src/lib/audio_content.h
+++ b/src/lib/audio_content.h
@@ -43,7 +43,7 @@ class AudioContent : public virtual Content
public:
typedef int64_t Frame;
- AudioContent (boost::shared_ptr<const Film>, Time);
+ AudioContent (boost::shared_ptr<const Film>, DCPTime);
AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -52,7 +52,7 @@ public:
std::string technical_summary () const;
virtual int audio_channels () const = 0;
- virtual AudioContent::Frame audio_length () const = 0;
+ virtual AudioFrame audio_length () const = 0;
virtual int content_audio_frame_rate () const = 0;
virtual int output_audio_frame_rate () const = 0;
virtual AudioMapping audio_mapping () const = 0;
@@ -74,6 +74,8 @@ public:
return _audio_delay;
}
+ Frame time_to_content_audio_frames (DCPTime, DCPTime) const;
+
private:
/** Gain to apply to audio in dB */
float _audio_gain;
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index c0ef02f65..8d3b0e128 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -22,6 +22,8 @@
#include "exceptions.h"
#include "log.h"
#include "resampler.h"
+#include "util.h"
+#include "film.h"
#include "i18n.h"
@@ -35,24 +37,53 @@ using boost::shared_ptr;
AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
: Decoder (film)
, _audio_content (content)
- , _audio_position (0)
{
+ if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) {
+ _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ()));
+ }
+}
+
+/** Audio timestamping is made hard by many factors, but the final nail in the coffin is resampling.
+ * We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ * data out. Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ * The time is passed in here so that after a seek we can set up our _audio_position. The
+ * time is ignored once this has been done.
+ */
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+ if (_resampler) {
+ data = _resampler->run (data);
+ }
+ if (!_audio_position) {
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ FrameRateChange frc = film->active_frame_rate_change (_audio_content->position ());
+ _audio_position = (double (time) / frc.speed_up) * film->audio_frame_rate() / TIME_HZ;
+ }
+
+ _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, _audio_position.get ())));
+ _audio_position = _audio_position.get() + data->frames ();
}
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::flush ()
{
- Audio (data, frame);
- _audio_position = frame + data->frames ();
+ if (!_resampler) {
+ return;
+ }
+
+ shared_ptr<const AudioBuffers> b = _resampler->flush ();
+ if (b) {
+ _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (b, _audio_position.get ())));
+ _audio_position = _audio_position.get() + b->frames ();
+ }
}
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- * The player needs to know that there is no audio otherwise it will keep trying to
- * pass() the decoder to get it to emit audio.
- */
-bool
-AudioDecoder::has_audio () const
+void
+AudioDecoder::seek (ContentTime, bool)
{
- return _audio_content->audio_channels () > 0;
+ _audio_position.reset ();
}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index ab6c4b8a9..bb3aafccd 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -27,8 +27,10 @@
#include "decoder.h"
#include "content.h"
#include "audio_content.h"
+#include "decoded.h"
class AudioBuffers;
+class Resampler;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
@@ -37,17 +39,21 @@ class AudioDecoder : public virtual Decoder
{
public:
AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
+
+ boost::shared_ptr<const AudioContent> audio_content () const {
+ return _audio_content;
+ }
- bool has_audio () const;
-
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
-
+ void seek (ContentTime time, bool accurate);
+
protected:
- void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+ void flush ();
+
boost::shared_ptr<const AudioContent> _audio_content;
- AudioContent::Frame _audio_position;
+ boost::shared_ptr<Resampler> _resampler;
+ boost::optional<AudioFrame> _audio_position;
};
#endif
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
index ae7702998..1db827046 100644
--- a/src/lib/audio_mapping.cc
+++ b/src/lib/audio_mapping.cc
@@ -68,11 +68,11 @@ AudioMapping::make_default ()
if (_content_channels == 1) {
/* Mono -> Centre */
- set (0, libdcp::CENTRE, 1);
+ set (0, dcp::CENTRE, 1);
} else {
/* 1:1 mapping */
for (int i = 0; i < _content_channels; ++i) {
- set (i, static_cast<libdcp::Channel> (i), 1);
+ set (i, static_cast<dcp::Channel> (i), 1);
}
}
}
@@ -85,14 +85,14 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version
/* Old-style: on/off mapping */
list<cxml::NodePtr> const c = node->node_children ("Map");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
- set ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
+ set ((*i)->number_child<int> ("ContentIndex"), static_cast<dcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
}
} else {
list<cxml::NodePtr> const c = node->node_children ("Gain");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
set (
(*i)->number_attribute<int> ("Content"),
- static_cast<libdcp::Channel> ((*i)->number_attribute<int> ("DCP")),
+ static_cast<dcp::Channel> ((*i)->number_attribute<int> ("DCP")),
lexical_cast<float> ((*i)->content ())
);
}
@@ -100,13 +100,13 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version
}
void
-AudioMapping::set (int c, libdcp::Channel d, float g)
+AudioMapping::set (int c, dcp::Channel d, float g)
{
_gain[c][d] = g;
}
float
-AudioMapping::get (int c, libdcp::Channel d) const
+AudioMapping::get (int c, dcp::Channel d) const
{
return _gain[c][d];
}
@@ -121,7 +121,7 @@ AudioMapping::as_xml (xmlpp::Node* node) const
xmlpp::Element* t = node->add_child ("Gain");
t->set_attribute ("Content", lexical_cast<string> (c));
t->set_attribute ("DCP", lexical_cast<string> (d));
- t->add_child_text (lexical_cast<string> (get (c, static_cast<libdcp::Channel> (d))));
+ t->add_child_text (lexical_cast<string> (get (c, static_cast<dcp::Channel> (d))));
}
}
}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
index 26087bfff..d3f4c74ab 100644
--- a/src/lib/audio_mapping.h
+++ b/src/lib/audio_mapping.h
@@ -50,8 +50,8 @@ public:
void make_default ();
- void set (int, libdcp::Channel, float);
- float get (int, libdcp::Channel) const;
+ void set (int, dcp::Channel, float);
+ float get (int, dcp::Channel) const;
int content_channels () const {
return _content_channels;
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
index 226601e0e..f068b504e 100644
--- a/src/lib/audio_merger.h
+++ b/src/lib/audio_merger.h
@@ -37,6 +37,8 @@ public:
TimedAudioBuffers<T>
pull (T time)
{
+ assert (time >= _last_pull);
+
TimedAudioBuffers<T> out;
F const to_return = _t_to_f (time - _last_pull);
@@ -97,9 +99,16 @@ public:
if (_buffers->frames() == 0) {
return TimedAudioBuffers<T> ();
}
-
+
return TimedAudioBuffers<T> (_buffers, _last_pull);
}
+
+ void
+ clear (DCPTime t)
+ {
+ _last_pull = t;
+ _buffers.reset (new AudioBuffers (_buffers->channels(), 0));
+ }
private:
boost::shared_ptr<AudioBuffers> _buffers;
diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc
index fca6b6afd..43a432239 100644
--- a/src/lib/cinema.cc
+++ b/src/lib/cinema.cc
@@ -70,7 +70,7 @@ Cinema::remove_screen (shared_ptr<Screen> s)
Screen::Screen (shared_ptr<const cxml::Node> node)
{
name = node->string_child ("Name");
- certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate")));
+ certificate = shared_ptr<dcp::Certificate> (new dcp::Certificate (node->string_child ("Certificate")));
}
void
diff --git a/src/lib/cinema.h b/src/lib/cinema.h
index 40dc15ae0..b4a4551b0 100644
--- a/src/lib/cinema.h
+++ b/src/lib/cinema.h
@@ -29,7 +29,7 @@ namespace cxml {
class Screen
{
public:
- Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert)
+ Screen (std::string const & n, boost::shared_ptr<dcp::Certificate> cert)
: name (n)
, certificate (cert)
{}
@@ -40,7 +40,7 @@ public:
boost::shared_ptr<Cinema> cinema;
std::string name;
- boost::shared_ptr<libdcp::Certificate> certificate;
+ boost::shared_ptr<dcp::Certificate> certificate;
};
class Cinema : public boost::enable_shared_from_this<Cinema>
diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc
index c3fa05426..88c4d635a 100644
--- a/src/lib/colour_conversion.cc
+++ b/src/lib/colour_conversion.cc
@@ -43,7 +43,7 @@ ColourConversion::ColourConversion ()
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
- matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+ matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j];
}
}
}
diff --git a/src/lib/config.cc b/src/lib/config.cc
index 30f85850d..2b30c0d80 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -78,9 +78,9 @@ Config::Config ()
_allowed_dcp_frame_rates.push_back (50);
_allowed_dcp_frame_rates.push_back (60);
- _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
- _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
- _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
}
void
@@ -164,7 +164,7 @@ Config::read ()
/* Loading version 0 (before Rec. 709 was added as a preset).
Add it in.
*/
- _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
}
list<cxml::NodePtr> cin = f.node_children ("Cinema");
diff --git a/src/lib/config.h b/src/lib/config.h
index d77969b3e..f5e7687bb 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -135,7 +135,7 @@ public:
return _default_dcp_content_type;
}
- libdcp::XMLMetadata dcp_metadata () const {
+ dcp::XMLMetadata dcp_metadata () const {
return _dcp_metadata;
}
@@ -249,7 +249,7 @@ public:
_default_dcp_content_type = t;
}
- void set_dcp_metadata (libdcp::XMLMetadata m) {
+ void set_dcp_metadata (dcp::XMLMetadata m) {
_dcp_metadata = m;
}
@@ -335,7 +335,7 @@ private:
int _default_still_length;
Ratio const * _default_container;
DCPContentType const * _default_dcp_content_type;
- libdcp::XMLMetadata _dcp_metadata;
+ dcp::XMLMetadata _dcp_metadata;
int _default_j2k_bandwidth;
int _default_audio_delay;
std::vector<PresetColourConversion> _colour_conversions;
diff --git a/src/lib/content.cc b/src/lib/content.cc
index ccca46bc0..ea1c19acd 100644
--- a/src/lib/content.cc
+++ b/src/lib/content.cc
@@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f)
}
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
: _film (f)
, _position (p)
, _trim_start (0)
@@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
_paths.push_back ((*i)->content ());
}
_digest = node->string_child ("Digest");
- _position = node->number_child<Time> ("Position");
- _trim_start = node->number_child<Time> ("TrimStart");
- _trim_end = node->number_child<Time> ("TrimEnd");
+ _position = node->number_child<DCPTime> ("Position");
+ _trim_start = node->number_child<DCPTime> ("TrimStart");
+ _trim_end = node->number_child<DCPTime> ("TrimEnd");
}
Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -146,7 +146,7 @@ Content::signal_changed (int p)
}
void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -161,7 +161,7 @@ Content::set_position (Time p)
}
void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -172,7 +172,7 @@ Content::set_trim_start (Time t)
}
void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -204,21 +204,12 @@ Content::technical_summary () const
return String::compose ("%1 %2 %3", path_summary(), digest(), position());
}
-Time
+DCPTime
Content::length_after_trim () const
{
return full_length() - trim_start() - trim_end();
}
-/** @param t A time relative to the start of this content (not the position).
- * @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
- return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
/** @return string which includes everything about how this content affects
* its playlist.
*/
diff --git a/src/lib/content.h b/src/lib/content.h
index 4ee7c267f..3172b9c8d 100644
--- a/src/lib/content.h
+++ b/src/lib/content.h
@@ -49,7 +49,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no
{
public:
Content (boost::shared_ptr<const Film>);
- Content (boost::shared_ptr<const Film>, Time);
+ Content (boost::shared_ptr<const Film>, DCPTime);
Content (boost::shared_ptr<const Film>, boost::filesystem::path);
Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -60,7 +60,7 @@ public:
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
- virtual Time full_length () const = 0;
+ virtual DCPTime full_length () const = 0;
virtual std::string identifier () const;
boost::shared_ptr<Content> clone () const;
@@ -92,42 +92,40 @@ public:
return _digest;
}
- void set_position (Time);
+ void set_position (DCPTime);
- /** Time that this content starts; i.e. the time that the first
+ /** DCPTime that this content starts; i.e. the time that the first
* bit of the content (trimmed or not) will happen.
*/
- Time position () const {
+ DCPTime position () const {
boost::mutex::scoped_lock lm (_mutex);
return _position;
}
- void set_trim_start (Time);
+ void set_trim_start (DCPTime);
- Time trim_start () const {
+ DCPTime trim_start () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_start;
}
- void set_trim_end (Time);
+ void set_trim_end (DCPTime);
- Time trim_end () const {
+ DCPTime trim_end () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_end;
}
- Time end () const {
+ DCPTime end () const {
return position() + length_after_trim() - 1;
}
- Time length_after_trim () const;
+ DCPTime length_after_trim () const;
void set_change_signals_frequent (bool f) {
_change_signals_frequent = f;
}
- bool trimmed (Time) const;
-
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
protected:
@@ -145,9 +143,9 @@ protected:
private:
std::string _digest;
- Time _position;
- Time _trim_start;
- Time _trim_end;
+ DCPTime _position;
+ DCPTime _trim_start;
+ DCPTime _trim_end;
bool _change_signals_frequent;
};
diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc
index bab22b8eb..825c80498 100644
--- a/src/lib/content_factory.cc
+++ b/src/lib/content_factory.cc
@@ -21,6 +21,7 @@
#include "ffmpeg_content.h"
#include "image_content.h"
#include "sndfile_content.h"
+#include "subrip_content.h"
#include "util.h"
using std::string;
@@ -39,6 +40,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version)
content.reset (new ImageContent (film, node, version));
} else if (type == "Sndfile") {
content.reset (new SndfileContent (film, node, version));
+ } else if (type == "SubRip") {
+ content.reset (new SubRipContent (film, node, version));
}
return content;
@@ -48,11 +51,16 @@ shared_ptr<Content>
content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
{
shared_ptr<Content> content;
+
+ string ext = path.extension().string ();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
if (valid_image_file (path)) {
content.reset (new ImageContent (film, path));
} else if (SndfileContent::valid_file (path)) {
content.reset (new SndfileContent (film, path));
+ } else if (ext == ".srt") {
+ content.reset (new SubRipContent (film, path));
} else {
content.reset (new FFmpegContent (film, path));
}
diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc
index 82bd5fa01..b3a45e40e 100644
--- a/src/lib/dcp_content_type.cc
+++ b/src/lib/dcp_content_type.cc
@@ -30,7 +30,7 @@ using namespace std;
vector<DCPContentType const *> DCPContentType::_dcp_content_types;
-DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
+DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d)
: _pretty_name (p)
, _libdcp_kind (k)
, _dci_name (d)
@@ -41,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
void
DCPContentType::setup_dcp_content_types ()
{
- _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
- _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
- _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
- _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
- _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
- _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
- _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
- _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
- _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
- _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
+ _dcp_content_types.push_back (new DCPContentType (_("Feature"), dcp::FEATURE, N_("FTR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Short"), dcp::SHORT, N_("SHR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Trailer"), dcp::TRAILER, N_("TLR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Test"), dcp::TEST, N_("TST")));
+ _dcp_content_types.push_back (new DCPContentType (_("Transitional"), dcp::TRANSITIONAL, N_("XSN")));
+ _dcp_content_types.push_back (new DCPContentType (_("Rating"), dcp::RATING, N_("RTG")));
+ _dcp_content_types.push_back (new DCPContentType (_("Teaser"), dcp::TEASER, N_("TSR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Policy"), dcp::POLICY, N_("POL")));
+ _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), dcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
+ _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), dcp::ADVERTISEMENT, N_("ADV")));
}
DCPContentType const *
diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h
index 965c16347..09d22b8f7 100644
--- a/src/lib/dcp_content_type.h
+++ b/src/lib/dcp_content_type.h
@@ -34,14 +34,14 @@
class DCPContentType : public boost::noncopyable
{
public:
- DCPContentType (std::string, libdcp::ContentKind, std::string);
+ DCPContentType (std::string, dcp::ContentKind, std::string);
/** @return user-visible `pretty' name */
std::string pretty_name () const {
return _pretty_name;
}
- libdcp::ContentKind libdcp_kind () const {
+ dcp::ContentKind libdcp_kind () const {
return _libdcp_kind;
}
@@ -58,7 +58,7 @@ public:
private:
std::string _pretty_name;
- libdcp::ContentKind _libdcp_kind;
+ dcp::ContentKind _libdcp_kind;
std::string _dci_name;
/** All available DCP content types */
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index 78d73ad00..2842b6325 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -43,8 +43,6 @@
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
-#include <libdcp/rec709_linearised_gamma_lut.h>
-#include <libdcp/srgb_linearised_gamma_lut.h>
#include <libdcp/gamma_lut.h>
#include <libdcp/xyz_frame.h>
#include <libdcp/rgb_xyz.h>
@@ -68,7 +66,7 @@ using std::stringstream;
using std::cout;
using boost::shared_ptr;
using boost::lexical_cast;
-using libdcp::Size;
+using dcp::Size;
#define DCI_COEFFICENT (48.0 / 52.37)
@@ -120,12 +118,8 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const Image> image, shared_ptr<const cx
shared_ptr<EncodedData>
DCPVideoFrame::encode_locally ()
{
- shared_ptr<libdcp::LUT> in_lut;
- if (_conversion.input_gamma_linearised) {
- in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma);
- } else {
- in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma);
- }
+ shared_ptr<dcp::GammaLUT> in_lut;
+ in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised);
/* XXX: libdcp should probably use boost */
@@ -136,10 +130,10 @@ DCPVideoFrame::encode_locally ()
}
}
- shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+ shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
_image,
in_lut,
- libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma),
+ dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false),
matrix
);
@@ -397,7 +391,7 @@ EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const
}
void
-EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const
{
boost::filesystem::path const info = film->info_path (frame, eyes);
FILE* h = fopen_boost (info, "w");
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 40f758c74..44cb23e1e 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
Taken from code Copyright (C) 2010-2011 Terrence Meiczinger
This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,7 @@
*/
-#include <openjpeg.h>
-#include <libdcp/picture_asset.h>
-#include <libdcp/picture_asset_writer.h>
+#include <libdcp/picture_mxf_writer.h>
#include "util.h"
/** @file src/dcp_video_frame.h
@@ -49,7 +47,7 @@ public:
void send (boost::shared_ptr<Socket> socket);
void write (boost::shared_ptr<const Film>, int, Eyes) const;
- void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
+ void write_info (boost::shared_ptr<const Film>, int, Eyes, dcp::FrameInfo) const;
/** @return data */
uint8_t* data () const {
diff --git a/src/lib/decoded.h b/src/lib/decoded.h
new file mode 100644
index 000000000..db62dfbe1
--- /dev/null
+++ b/src/lib/decoded.h
@@ -0,0 +1,145 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_LIB_DECODED_H
+#define DCPOMATIC_LIB_DECODED_H
+
+#include <libdcp/subtitle_string.h>
+#include "types.h"
+#include "rect.h"
+#include "util.h"
+
+class Image;
+
+class Decoded
+{
+public:
+ Decoded ()
+ : dcp_time (0)
+ {}
+
+ virtual ~Decoded () {}
+
+ virtual void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange, DCPTime) = 0;
+
+ DCPTime dcp_time;
+};
+
+/** One frame of video from a VideoDecoder */
+class DecodedVideo : public Decoded
+{
+public:
+ DecodedVideo ()
+ : eyes (EYES_BOTH)
+ , same (false)
+ , frame (0)
+ {}
+
+ DecodedVideo (boost::shared_ptr<const Image> im, Eyes e, bool s, VideoFrame f)
+ : image (im)
+ , eyes (e)
+ , same (s)
+ , frame (f)
+ {}
+
+ void set_dcp_times (VideoFrame video_frame_rate, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ dcp_time = frame * TIME_HZ * frc.factor() / video_frame_rate + offset;
+ }
+
+ boost::shared_ptr<const Image> image;
+ Eyes eyes;
+ bool same;
+ VideoFrame frame;
+};
+
+class DecodedAudio : public Decoded
+{
+public:
+ DecodedAudio (boost::shared_ptr<const AudioBuffers> d, AudioFrame f)
+ : data (d)
+ , frame (f)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame audio_frame_rate, FrameRateChange, DCPTime offset)
+ {
+ dcp_time = frame * TIME_HZ / audio_frame_rate + offset;
+ }
+
+ boost::shared_ptr<const AudioBuffers> data;
+ AudioFrame frame;
+};
+
+class DecodedImageSubtitle : public Decoded
+{
+public:
+ DecodedImageSubtitle ()
+ : content_time (0)
+ , content_time_to (0)
+ , dcp_time_to (0)
+ {}
+
+ DecodedImageSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t)
+ : image (im)
+ , rect (r)
+ , content_time (f)
+ , content_time_to (t)
+ , dcp_time_to (0)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ dcp_time = rint (content_time / frc.speed_up) + offset;
+ dcp_time_to = rint (content_time_to / frc.speed_up) + offset;
+ }
+
+ boost::shared_ptr<Image> image;
+ dcpomatic::Rect<double> rect;
+ ContentTime content_time;
+ ContentTime content_time_to;
+ DCPTime dcp_time_to;
+};
+
+class DecodedTextSubtitle : public Decoded
+{
+public:
+ DecodedTextSubtitle ()
+ : dcp_time_to (0)
+ {}
+
+ DecodedTextSubtitle (std::list<dcp::SubtitleString> s)
+ : subs (s)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ if (subs.empty ()) {
+ return;
+ }
+
+ /* Assuming that all subs are at the same time */
+ dcp_time = rint (subs.front().in().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+ dcp_time_to = rint (subs.front().out().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+ }
+
+ std::list<dcp::SubtitleString> subs;
+ DCPTime dcp_time_to;
+};
+
+#endif
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 3f4cda6eb..53a0c31e1 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -23,9 +23,11 @@
#include "film.h"
#include "decoder.h"
+#include "decoded.h"
#include "i18n.h"
+using std::cout;
using boost::shared_ptr;
/** @param f Film.
@@ -33,6 +35,45 @@ using boost::shared_ptr;
*/
Decoder::Decoder (shared_ptr<const Film> f)
: _film (f)
+ , _done (false)
{
}
+
+struct DecodedSorter
+{
+ bool operator() (shared_ptr<Decoded> a, shared_ptr<Decoded> b)
+ {
+ return a->dcp_time < b->dcp_time;
+ }
+};
+
+shared_ptr<Decoded>
+Decoder::peek ()
+{
+ while (!_done && _pending.empty ()) {
+ _done = pass ();
+ }
+
+ if (_done && _pending.empty ()) {
+ return shared_ptr<Decoded> ();
+ }
+
+ _pending.sort (DecodedSorter ());
+ return _pending.front ();
+}
+
+void
+Decoder::consume ()
+{
+ if (!_pending.empty ()) {
+ _pending.pop_front ();
+ }
+}
+
+void
+Decoder::seek (ContentTime, bool)
+{
+ _pending.clear ();
+ _done = false;
+}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index d67592ed8..6646b0e76 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -27,8 +27,10 @@
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
+#include "types.h"
class Film;
+class Decoded;
/** @class Decoder.
* @brief Parent class for decoders of content.
@@ -39,18 +41,32 @@ 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.
+ /** Seek so that the next get_*() will yield the next thing
+ * (video/sound frame, subtitle etc.) at or after the requested
+ * time. Pass accurate = true to try harder to get close to
+ * the request.
*/
- virtual void pass () = 0;
- virtual bool done () const = 0;
+ virtual void seek (ContentTime time, bool accurate);
+
+ boost::shared_ptr<Decoded> peek ();
+ void consume ();
protected:
+ /** Perform one decode pass of the content, which may or may not
+ * result in a complete quantum (Decoded object) of decoded stuff
+ * being made ready.
+ * @return true if the decoder is done (i.e. no more data will be
+ * produced by any future calls to pass() without a seek() first).
+ */
+ virtual bool pass () = 0;
virtual void flush () {};
/** The Film that we are decoding in */
boost::weak_ptr<const Film> _film;
+
+ std::list<boost::shared_ptr<Decoded> > _pending;
+ bool _done;
};
#endif
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 92b4763be..f1c3e7e61 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -217,7 +217,7 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
TIMING ("adding to queue of %1", _queue.size ());
_queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- image->image(), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
+ image->image(PIX_FMT_RGB24, false), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
_film->j2k_bandwidth(), _film->resolution(), _film->log()
)
));
diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc
index 8144f41b9..e05ac4ff0 100644
--- a/src/lib/exceptions.cc
+++ b/src/lib/exceptions.cc
@@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s)
}
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
: StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
{
}
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+ : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f)
+{
+
+}
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index 3423a5754..213be6186 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -230,6 +230,13 @@ public:
PixelFormatError (std::string o, AVPixelFormat f);
};
+/** An error that occurs while parsing a SubRip file */
+class SubRipError : public FileError
+{
+public:
+ SubRipError (std::string, std::string, boost::filesystem::path);
+};
+
/** A parent class for classes which have a need to catch and
* re-throw exceptions. This is intended for classes
* which run their own thread; they should do something like
diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc
index fae9baa2b..5fc333489 100644
--- a/src/lib/ffmpeg.cc
+++ b/src/lib/ffmpeg.cc
@@ -193,6 +193,10 @@ FFmpeg::video_codec_context () const
AVCodecContext *
FFmpeg::audio_codec_context () const
{
+ if (!_ffmpeg_content->audio_stream ()) {
+ return 0;
+ }
+
return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
}
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
index 2c5fcf70e..3bee49146 100644
--- a/src/lib/ffmpeg_content.cc
+++ b/src/lib/ffmpeg_content.cc
@@ -163,7 +163,7 @@ FFmpegContent::examine (shared_ptr<Job> job)
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
- VideoContent::Frame video_length = 0;
+ VideoFrame video_length = 0;
video_length = examiner->video_length ();
film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
@@ -262,12 +262,12 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
signal_changed (FFmpegContentProperty::AUDIO_STREAM);
}
-AudioContent::Frame
+AudioFrame
FFmpegContent::audio_length () const
{
int const cafr = content_audio_frame_rate ();
int const vfr = video_frame_rate ();
- VideoContent::Frame const vl = video_length ();
+ VideoFrame const vl = video_length ();
boost::mutex::scoped_lock lm (_mutex);
if (!_audio_stream) {
@@ -310,16 +310,15 @@ FFmpegContent::output_audio_frame_rate () const
/* Resample to a DCI-approved sample rate */
double t = dcp_audio_frame_rate (content_audio_frame_rate ());
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
/* Compensate if the DCP is being run at a different frame rate
to the source; that is, if the video is run such that it will
look different in the DCP compared to the source (slower or faster).
- skip/repeat doesn't come into effect here.
*/
if (frc.change_speed) {
- t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+ t /= frc.speed_up;
}
return rint (t);
@@ -446,13 +445,13 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
FFmpegStream::as_xml (root);
}
-Time
+DCPTime
FFmpegContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+ FrameRateChange frc (video_frame_rate (), film->video_frame_rate ());
return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
index b1f2abcea..e637faf47 100644
--- a/src/lib/ffmpeg_content.h
+++ b/src/lib/ffmpeg_content.h
@@ -136,13 +136,13 @@ public:
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
/* AudioContent */
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ AudioFrame audio_length () const;
int content_audio_frame_rate () const;
int output_audio_frame_rate () const;
AudioMapping audio_mapping () const;
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index c3709166e..fff982489 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -56,7 +56,7 @@ using std::pair;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-using libdcp::Size;
+using dcp::Size;
FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
: Decoder (f)
@@ -69,7 +69,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
, _decode_video (video)
, _decode_audio (audio)
, _pts_offset (0)
- , _just_sought (false)
{
setup_subtitle ();
@@ -82,13 +81,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
Then we remove big initial gaps in PTS and we allow our
insertion of black frames to work.
- We will do:
- audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
- video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+ We will do pts_to_use = pts_from_ffmpeg + pts_offset;
*/
bool const have_video = video && c->first_video();
- bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+ bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio;
/* First, make one of them start at 0 */
@@ -141,12 +138,10 @@ FFmpegDecoder::flush ()
decode_audio_packet ();
}
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length ();
- _audio_position = _ffmpeg_content->audio_length ();
+ AudioDecoder::flush ();
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
@@ -162,7 +157,7 @@ FFmpegDecoder::pass ()
}
flush ();
- return;
+ return true;
}
shared_ptr<const Film> film = _film.lock ();
@@ -179,6 +174,7 @@ FFmpegDecoder::pass ()
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
@@ -293,77 +289,135 @@ FFmpegDecoder::bytes_per_audio_sample () const
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+ optional<ContentTime> last_video;
+ optional<ContentTime> last_audio;
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
- */
- int initial = frame;
+ while (!finished (last_video, last_audio, frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ /* We should flush our decoders here, possibly yielding a few more frames,
+ but the consequence of having to do that is too hideous to contemplate.
+ Instead we give up and say that you can't seek too close to the end
+ of a file.
+ */
+ return frames_read;
+ }
- if (accurate) {
- initial -= 5;
+ ++frames_read;
+
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+ if (_packet.stream_index == _video_stream) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int finished = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ last_video = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * TIME_HZ
+ );
+ }
+
+ } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
+ AVPacket copy_packet = _packet;
+ while (copy_packet.size > 0) {
+
+ int finished;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ last_audio = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * TIME_HZ
+ );
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- if (initial < 0) {
- initial = 0;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+ return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+ int64_t s = ((double (t) / TIME_HZ) - _pts_offset) /
+ av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ if (_ffmpeg_content->audio_stream ()) {
+ s = min (
+ s, int64_t (
+ ((double (t) / TIME_HZ) - _pts_offset) /
+ av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)
+ )
+ );
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+ /* Ridiculous empirical hack */
+ s--;
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ av_seek_frame (_format_context, _video_stream, s, AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+}
- /* This !accurate is piling hack upon hack; setting _just_sought to true
- even with accurate == true defeats our attempt to align the start
- of the video and audio. Here we disable that defeat when accurate == true
- i.e. when we are making a DCP rather than just previewing one.
- Ewww. This should be gone in 2.0.
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ Decoder::seek (time, accurate);
+ AudioDecoder::seek (time, accurate);
+
+ /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+ a number plucked from the air) earlier than we want to end up. The loop below
+ will hopefully then step through to where we want to be.
*/
- if (!accurate) {
- _just_sought = true;
+
+ ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0;
+ ContentTime initial_seek = time - pre_roll;
+ if (initial_seek < 0) {
+ initial_seek = 0;
}
-
- _video_position = frame;
-
- if (frame == 0 || !accurate) {
- /* We're already there, or we're as close as we need to be */
+
+ /* Initial seek time in the video stream's timebase */
+
+ seek_and_flush (initial_seek);
+
+ if (!accurate) {
+ /* That'll do */
return;
}
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
-
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
- _video_position = rint (
- (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate()
- );
+ int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ seek_and_flush (initial_seek);
+ if (N > 0) {
+ minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
}
}
@@ -380,6 +434,7 @@ FFmpegDecoder::decode_audio_packet ()
int frame_finished;
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
if (decode_result < 0) {
shared_ptr<const Film> film = _film.lock ();
assert (film);
@@ -388,31 +443,17 @@ FFmpegDecoder::decode_audio_packet ()
}
if (frame_finished) {
-
- if (_audio_position == 0) {
- /* Where we are in the source, in seconds */
- double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
- if (pts > 0) {
- /* Emit some silence */
- shared_ptr<AudioBuffers> silence (
- new AudioBuffers (
- _ffmpeg_content->audio_channels(),
- pts * _ffmpeg_content->content_audio_frame_rate()
- )
- );
-
- silence->make_silent ();
- audio (silence, _audio_position);
- }
- }
+ ContentTime const ct = (
+ av_frame_get_best_effort_timestamp (_frame) *
+ av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base)
+ + _pts_offset
+ ) * TIME_HZ;
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;
@@ -433,7 +474,7 @@ 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;
}
@@ -441,7 +482,7 @@ FFmpegDecoder::decode_video_packet ()
shared_ptr<const Film> film = _film.lock ();
assert (film);
- graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
_filter_graphs.push_back (graph);
film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
@@ -461,45 +502,9 @@ FFmpegDecoder::decode_video_packet ()
}
if (i->second != AV_NOPTS_VALUE) {
-
double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
- if (_just_sought) {
- /* We just did a seek, so disable any attempts to correct for where we
- are / should be.
- */
- _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
- _just_sought = false;
- }
-
- double const next = _video_position / _ffmpeg_content->video_frame_rate();
- double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
- double delta = pts - next;
-
- while (delta > one_frame) {
- /* This PTS is more than one frame forward in time of where we think we should be; emit
- a black frame.
- */
-
- /* XXX: I think this should be a copy of the last frame... */
- boost::shared_ptr<Image> black (
- new Image (
- static_cast<AVPixelFormat> (_frame->format),
- libdcp::Size (video_codec_context()->width, video_codec_context()->height),
- true
- )
- );
-
- black->make_black ();
- video (image, false, _video_position);
- delta -= one_frame;
- }
-
- if (delta > -one_frame) {
- /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (image, false, _video_position);
- }
-
+ VideoFrame const f = rint (pts * _ffmpeg_content->video_frame_rate ());
+ video (image, false, f);
} else {
shared_ptr<const Film> film = _film.lock ();
assert (film);
@@ -532,14 +537,6 @@ FFmpegDecoder::setup_subtitle ()
}
}
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
-
void
FFmpegDecoder::decode_subtitle_packet ()
{
@@ -553,7 +550,7 @@ FFmpegDecoder::decode_subtitle_packet ()
indicate that the previous subtitle should stop.
*/
if (sub.num_rects <= 0) {
- subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+ image_subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
return;
} else if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
@@ -562,11 +559,11 @@ FFmpegDecoder::decode_subtitle_packet ()
/* Subtitle PTS in seconds (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;
-
+ 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;
+ ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+ ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
AVSubtitleRect const * rect = sub.rects[0];
@@ -577,7 +574,7 @@ FFmpegDecoder::decode_subtitle_packet ()
/* Note RGBA is expressed little-endian, so the first byte in the word is R, second
G, third B, fourth A.
*/
- shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
/* Start of the first line in the subtitle */
uint8_t* sub_p = rect->pict.data[0];
@@ -599,9 +596,9 @@ FFmpegDecoder::decode_subtitle_packet ()
out_p += image->stride()[0] / sizeof (uint32_t);
}
- libdcp::Size const vs = _ffmpeg_content->video_size ();
+ dcp::Size const vs = _ffmpeg_content->video_size ();
- subtitle (
+ image_subtitle (
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 63a8f0c71..ee725b20c 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -51,15 +51,12 @@ 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;
+ void seek (ContentTime time, bool);
private:
friend class ::ffmpeg_pts_offset_test;
- static double compute_pts_offset (double, double, float);
-
+ bool pass ();
void flush ();
void setup_subtitle ();
@@ -74,6 +71,11 @@ private:
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
+ bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+ bool seek_final_finished (int, int) const;
+ int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
+ void seek_and_flush (int64_t);
+
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
@@ -84,5 +86,4 @@ private:
bool _decode_audio;
double _pts_offset;
- bool _just_sought;
};
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
index a63090d12..86dec9a8f 100644
--- a/src/lib/ffmpeg_examiner.cc
+++ b/src/lib/ffmpeg_examiner.cc
@@ -127,17 +127,17 @@ FFmpegExaminer::video_frame_rate () const
return av_q2d (s->r_frame_rate);
}
-libdcp::Size
+dcp::Size
FFmpegExaminer::video_size () const
{
- return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+ return dcp::Size (video_codec_context()->width, video_codec_context()->height);
}
/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+VideoFrame
FFmpegExaminer::video_length () const
{
- VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
+ VideoFrame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
return max (1, length);
}
diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h
index 4de475d2a..40d7dbf1d 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;
+ VideoFrame video_length () const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
return _subtitle_streams;
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 8690d3ee0..774c1b392 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -78,8 +78,8 @@ using boost::to_upper_copy;
using boost::ends_with;
using boost::starts_with;
using boost::optional;
-using libdcp::Size;
-using libdcp::Signer;
+using dcp::Size;
+using dcp::Signer;
/* 5 -> 6
* AudioMapping XML changed.
@@ -420,7 +420,7 @@ Film::read_metadata ()
_sequence_video = f.bool_child ("SequenceVideo");
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
- _key = libdcp::Key (f.string_child ("Key"));
+ _key = dcp::Key (f.string_child ("Key"));
_playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), version);
_dirty = false;
@@ -746,7 +746,7 @@ Film::j2c_path (int f, Eyes e, bool t) const
return file (p);
}
-/** @return List of subdirectories (not full paths) containing DCPs that can be successfully libdcp::DCP::read() */
+/** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */
list<boost::filesystem::path>
Film::dcps () const
{
@@ -760,7 +760,7 @@ Film::dcps () const
) {
try {
- libdcp::DCP dcp (*i);
+ dcp::DCP dcp (*i);
dcp.read ();
out.push_back (i->path().leaf ());
} catch (...) {
@@ -855,7 +855,7 @@ Film::move_content_later (shared_ptr<Content> c)
_playlist->move_later (c);
}
-Time
+DCPTime
Film::length () const
{
return _playlist->length ();
@@ -867,12 +867,18 @@ Film::has_subtitles () const
return _playlist->has_subtitles ();
}
-OutputVideoFrame
+VideoFrame
Film::best_video_frame_rate () const
{
return _playlist->best_dcp_frame_rate ();
}
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
@@ -891,31 +897,31 @@ Film::playlist_changed ()
signal_changed (CONTENT);
}
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
+AudioFrame
+Film::time_to_audio_frames (DCPTime t) const
{
return t * audio_frame_rate () / TIME_HZ;
}
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
+VideoFrame
+Film::time_to_video_frames (DCPTime t) const
{
return t * video_frame_rate () / TIME_HZ;
}
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
+DCPTime
+Film::audio_frames_to_time (AudioFrame f) const
{
return f * TIME_HZ / audio_frame_rate ();
}
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
+DCPTime
+Film::video_frames_to_time (VideoFrame f) const
{
return f * TIME_HZ / video_frame_rate ();
}
-OutputAudioFrame
+AudioFrame
Film::audio_frame_rate () const
{
/* XXX */
@@ -930,23 +936,23 @@ Film::set_sequence_video (bool s)
signal_changed (SEQUENCE_VIDEO);
}
-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 ();
}
-libdcp::KDM
+dcp::KDM
Film::make_kdm (
- shared_ptr<libdcp::Certificate> target,
+ shared_ptr<dcp::Certificate> target,
boost::filesystem::path dcp_dir,
boost::posix_time::ptime from,
boost::posix_time::ptime until
@@ -954,7 +960,7 @@ Film::make_kdm (
{
shared_ptr<const Signer> signer = make_signer ();
- libdcp::DCP dcp (dir (dcp_dir.string ()));
+ dcp::DCP dcp (dir (dcp_dir.string ()));
try {
dcp.read ();
@@ -964,14 +970,14 @@ Film::make_kdm (
time_t now = time (0);
struct tm* tm = localtime (&now);
- string const issue_date = libdcp::tm_to_string (tm);
+ string const issue_date = dcp::tm_to_string (tm);
dcp.cpls().front()->set_mxf_keys (key ());
- return libdcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
+ return dcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
}
-list<libdcp::KDM>
+list<dcp::KDM>
Film::make_kdms (
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
@@ -979,7 +985,7 @@ Film::make_kdms (
boost::posix_time::ptime until
) const
{
- list<libdcp::KDM> kdms;
+ list<dcp::KDM> kdms;
for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until));
diff --git a/src/lib/film.h b/src/lib/film.h
index 0a747193e..68916c7b0 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -94,19 +94,19 @@ public:
return _dirty;
}
- libdcp::Size full_frame () const;
+ dcp::Size full_frame () const;
std::list<boost::filesystem::path> dcps () const;
boost::shared_ptr<Player> make_player () const;
boost::shared_ptr<Playlist> playlist () const;
- OutputAudioFrame audio_frame_rate () const;
+ AudioFrame 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;
+ AudioFrame time_to_audio_frames (DCPTime) const;
+ VideoFrame time_to_video_frames (DCPTime) const;
+ DCPTime video_frames_to_time (VideoFrame) const;
+ DCPTime audio_frames_to_time (AudioFrame) const;
uint64_t required_disk_space () const;
bool should_be_enough_disk_space (double &, double &) const;
@@ -114,26 +114,27 @@ public:
/* Proxies for some Playlist methods */
ContentList content () const;
- Time length () const;
+ DCPTime length () const;
bool has_subtitles () const;
- OutputVideoFrame best_video_frame_rate () const;
+ VideoFrame best_video_frame_rate () const;
+ FrameRateChange active_frame_rate_change (DCPTime) const;
- libdcp::KDM
+ dcp::KDM
make_kdm (
- boost::shared_ptr<libdcp::Certificate> target,
+ boost::shared_ptr<dcp::Certificate> target,
boost::filesystem::path dcp,
boost::posix_time::ptime from,
boost::posix_time::ptime until
) const;
- std::list<libdcp::KDM> make_kdms (
+ std::list<dcp::KDM> make_kdms (
std::list<boost::shared_ptr<Screen> >,
boost::filesystem::path dcp,
boost::posix_time::ptime from,
boost::posix_time::ptime until
) const;
- libdcp::Key key () const {
+ dcp::Key key () const {
return _key;
}
@@ -322,7 +323,7 @@ private:
bool _three_d;
bool _sequence_video;
bool _interop;
- libdcp::Key _key;
+ dcp::Key _key;
/** true if our state has changed since we last saved it */
mutable bool _dirty;
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index cd5d19807..48d94e175 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -45,14 +45,14 @@ using std::make_pair;
using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
/** Construct a FilterGraph for the settings in a piece of content.
* @param content Content.
* @param s Size of the images to process.
* @param p Pixel format of the images to process.
*/
-FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
@@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size
throw DecodeError (N_("could not configure filter graph."));
}
- /* XXX: leaking `inputs' / `outputs' ? */
+ avfilter_inout_free (&inputs);
+ avfilter_inout_free (&outputs);
}
FilterGraph::~FilterGraph ()
@@ -159,7 +160,7 @@ FilterGraph::process (AVFrame* frame)
* @return true if this chain can process images with `s' and `p', otherwise false.
*/
bool
-FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (dcp::Size s, AVPixelFormat p) const
{
return (_size == s && _pixel_format == p);
}
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index 9b403c2bc..45ad5d998 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -36,16 +36,16 @@ class FFmpegContent;
class FilterGraph : public boost::noncopyable
{
public:
- FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p);
~FilterGraph ();
- bool can_process (libdcp::Size s, AVPixelFormat p) const;
+ bool can_process (dcp::Size s, AVPixelFormat p) const;
std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame);
private:
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
- libdcp::Size _size; ///< size of the images that this chain can process
+ dcp::Size _size; ///< size of the images that this chain can process
AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
AVFrame* _frame;
};
diff --git a/src/lib/image.cc b/src/lib/image.cc
index 4722563c4..b706f7fdc 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -31,12 +31,13 @@ extern "C" {
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
+#include "timer.h"
using std::string;
using std::min;
using std::cout;
using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
int
Image::line_factor (int n) const
@@ -80,7 +81,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
@@ -91,12 +92,12 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
shared_ptr<Image> out (new Image (out_format, out_size, out_aligned));
out->make_black ();
- libdcp::Size cropped_size = crop.apply (size ());
+ dcp::Size cropped_size = crop.apply (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
);
uint8_t* scale_in_data[components()];
@@ -124,7 +125,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
@@ -200,7 +201,7 @@ Image::post_process (string pp, bool aligned) const
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) {
@@ -375,8 +376,18 @@ Image::make_black ()
void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* Only implemented for RGBA onto RGB24 so far */
- assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+ int this_bpp = 0;
+ int other_bpp = 0;
+
+ if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 4;
+ other_bpp = 4;
+ } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 3;
+ other_bpp = 4;
+ } else {
+ assert (false);
+ }
int start_tx = position.x;
int start_ox = 0;
@@ -395,15 +406,15 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
}
for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
- uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+ uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
- tp += 3;
- op += 4;
+ tp += this_bpp;
+ op += other_bpp;
}
}
}
@@ -487,8 +498,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)
{
@@ -525,7 +536,7 @@ Image::allocate ()
}
Image::Image (Image const & other)
- : libdcp::Image (other)
+ : dcp::Image (other)
, _pixel_format (other._pixel_format)
, _aligned (other._aligned)
{
@@ -543,7 +554,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)
{
@@ -562,7 +573,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)
{
@@ -595,7 +606,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);
@@ -638,7 +649,7 @@ Image::stride () const
return _stride;
}
-libdcp::Size
+dcp::Size
Image::size () const
{
return _size;
diff --git a/src/lib/image.h b/src/lib/image.h
index b12db3a14..1c096be71 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -37,10 +37,10 @@ extern "C" {
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,18 +50,18 @@ 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> post_process (std::string, bool aligned) const;
boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
- boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
void make_black ();
void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
index 2d29df0c4..a7f951bea 100644
--- a/src/lib/image_content.cc
+++ b/src/lib/image_content.cc
@@ -110,7 +110,7 @@ ImageContent::examine (shared_ptr<Job> job)
}
void
-ImageContent::set_video_length (VideoContent::Frame len)
+ImageContent::set_video_length (VideoFrame len)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -120,13 +120,13 @@ ImageContent::set_video_length (VideoContent::Frame len)
signal_changed (ContentProperty::LENGTH);
}
-Time
+DCPTime
ImageContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate ());
return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
}
diff --git a/src/lib/image_content.h b/src/lib/image_content.h
index e5a0311d9..f929e2b6f 100644
--- a/src/lib/image_content.h
+++ b/src/lib/image_content.h
@@ -41,11 +41,11 @@ public:
std::string summary () const;
std::string technical_summary () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
- void set_video_length (VideoContent::Frame);
+ void set_video_length (VideoFrame);
bool still () const;
void set_video_frame_rate (float);
};
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
index a7999c02a..d8d551ef7 100644
--- a/src/lib/image_decoder.cc
+++ b/src/lib/image_decoder.cc
@@ -30,26 +30,28 @@
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)
, _image_content (c)
+ , _video_position (0)
{
}
-void
+bool
ImageDecoder::pass ()
{
if (_video_position >= _image_content->video_length ()) {
- return;
+ return true;
}
if (_image && _image_content->still ()) {
video (_image, true, _video_position);
- return;
+ ++_video_position;
+ return false;
}
Magick::Image* magick_image = 0;
@@ -60,7 +62,7 @@ ImageDecoder::pass ()
throw OpenFileError (path);
}
- libdcp::Size size (magick_image->columns(), magick_image->rows());
+ dcp::Size size (magick_image->columns(), magick_image->rows());
_image.reset (new Image (PIX_FMT_RGB24, size, true));
@@ -81,16 +83,15 @@ ImageDecoder::pass ()
delete magick_image;
video (_image, false, _video_position);
-}
+ ++_video_position;
-void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
- _video_position = frame;
+ return false;
}
-bool
-ImageDecoder::done () const
+void
+ImageDecoder::seek (ContentTime time, bool accurate)
{
- return _video_position >= _image_content->video_length ();
+ Decoder::seek (time, accurate);
+
+ _video_position = rint (time * _video_content->video_frame_rate() / TIME_HZ);
}
diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h
index c7500243e..63b4c58e3 100644
--- a/src/lib/image_decoder.h
+++ b/src/lib/image_decoder.h
@@ -34,14 +34,13 @@ public:
return _image_content;
}
- /* Decoder */
-
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
+ void seek (ContentTime, bool);
private:
+ bool pass ();
+
boost::shared_ptr<const ImageContent> _image_content;
boost::shared_ptr<Image> _image;
+ VideoFrame _video_position;
};
diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc
index 12fe2b8a6..47b220da7 100644
--- a/src/lib/image_examiner.cc
+++ b/src/lib/image_examiner.cc
@@ -43,7 +43,7 @@ ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const Imag
{
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 ()) {
@@ -53,7 +53,7 @@ ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const Imag
}
}
-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..1800f1ea3 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;
+ VideoFrame 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;
+ VideoFrame _video_length;
};
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 66fa3755d..ce97ba2b2 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -66,7 +66,7 @@ Job::run_wrapper ()
run ();
- } catch (libdcp::FileError& e) {
+ } catch (dcp::FileError& e) {
string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
@@ -204,7 +204,7 @@ Job::set_state (State s)
}
}
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
int
Job::elapsed_time () const
{
diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc
index cf551285b..35969a224 100644
--- a/src/lib/kdm.cc
+++ b/src/lib/kdm.cc
@@ -36,13 +36,13 @@ using boost::shared_ptr;
struct ScreenKDM
{
- ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
+ ScreenKDM (shared_ptr<Screen> s, dcp::KDM k)
: screen (s)
, kdm (k)
{}
shared_ptr<Screen> screen;
- libdcp::KDM kdm;
+ dcp::KDM kdm;
};
static string
@@ -107,12 +107,12 @@ make_screen_kdms (
boost::posix_time::ptime to
)
{
- list<libdcp::KDM> kdms = film->make_kdms (screens, dcp, from, to);
+ list<dcp::KDM> kdms = film->make_kdms (screens, dcp, from, to);
list<ScreenKDM> screen_kdms;
list<shared_ptr<Screen> >::iterator i = screens.begin ();
- list<libdcp::KDM>::iterator j = kdms.begin ();
+ list<dcp::KDM>::iterator j = kdms.begin ();
while (i != screens.end() && j != kdms.end ()) {
screen_kdms.push_back (ScreenKDM (*i, *j));
++i;
diff --git a/src/lib/player.cc b/src/lib/player.cc
index 59db923be..a8ba7cc53 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -18,6 +18,7 @@
*/
#include <stdint.h>
+#include <algorithm>
#include "player.h"
#include "film.h"
#include "ffmpeg_decoder.h"
@@ -27,13 +28,15 @@
#include "sndfile_decoder.h"
#include "sndfile_content.h"
#include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
#include "playlist.h"
#include "job.h"
#include "image.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
using std::list;
using std::cout;
@@ -45,71 +48,20 @@ using std::map;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
class Piece
{
public:
- Piece (shared_ptr<Content> c)
- : content (c)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
- {}
-
- Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+ Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
: content (c)
, decoder (d)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
+ , frc (f)
{}
- /** Set this piece to repeat a video frame a given number of times */
- void set_repeat (IncomingVideo video, int num)
- {
- repeat_video = video;
- repeat_to_do = num;
- repeat_done = 0;
- }
-
- void reset_repeat ()
- {
- repeat_video.image.reset ();
- repeat_to_do = 0;
- repeat_done = 0;
- }
-
- bool repeating () const
- {
- return repeat_done != repeat_to_do;
- }
-
- void repeat (Player* player)
- {
- player->process_video (
- repeat_video.weak_piece,
- repeat_video.image,
- repeat_video.eyes,
- repeat_done > 0,
- repeat_video.frame,
- (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
- );
-
- ++repeat_done;
- }
-
shared_ptr<Content> content;
shared_ptr<Decoder> decoder;
- /** Time of the last video we emitted relative to the start of the DCP */
- Time video_position;
- /** Time of the last audio we emitted relative to the start of the DCP */
- Time audio_position;
-
- IncomingVideo repeat_video;
- int repeat_to_do;
- int repeat_done;
+ FrameRateChange frc;
};
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
@@ -122,6 +74,8 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
, _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)
+ , _just_did_inaccurate_seek (false)
+ , _approximate_size (false)
{
_playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
@@ -148,111 +102,162 @@ Player::pass ()
setup_pieces ();
}
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
+ /* Interrogate all our pieces to find the one with the earliest decoded data */
+
+ shared_ptr<Piece> earliest_piece;
+ shared_ptr<Decoded> earliest_decoded;
+ DCPTime earliest_time = TIME_MAX;
+ DCPTime earliest_audio = TIME_MAX;
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
+
+ bool done = false;
+ shared_ptr<Decoded> dec;
+ while (!done) {
+ dec = (*i)->decoder->peek ();
+ if (!dec) {
+ /* Decoder has nothing else to give us */
+ break;
+ }
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
+ dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
+ DCPTime const t = dec->dcp_time - offset;
+ if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
+ /* In the end-trimmed part; decoder has nothing else to give us */
+ dec.reset ();
+ done = true;
+ } else if (t >= (*i)->content->trim_start ()) {
+ /* Within the un-trimmed part; everything's ok */
+ done = true;
+ } else {
+ /* Within the start-trimmed part; get something else */
+ (*i)->decoder->consume ();
}
}
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
- }
+ if (!dec) {
+ continue;
}
- }
- if (!earliest) {
+ if (dec->dcp_time < earliest_time) {
+ earliest_piece = *i;
+ earliest_decoded = dec;
+ earliest_time = dec->dcp_time;
+ }
+
+ if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
+ earliest_audio = dec->dcp_time;
+ }
+ }
+
+ if (!earliest_piece) {
flush ();
return true;
}
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
- } else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
+ if (earliest_audio != TIME_MAX) {
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
+ Audio (tb.audio, tb.time);
+ /* This assumes that the audio_frames_to_time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ }
+
+ /* Emit the earliest thing */
+
+ shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+ shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+ shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
+ shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
+
+ /* Will be set to false if we shouldn't consume the peeked DecodedThing */
+ bool consume = true;
+
+ if (dv && _video) {
+
+ if (_just_did_inaccurate_seek) {
+
+ /* Just emit; no subtlety */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+
+ } else if (dv->dcp_time > _video_position) {
+
+ /* Too far ahead */
+
+ list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+ while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+ ++i;
+ }
+
+ if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+ /* We're outside all video content */
+ emit_black ();
+ _statistics.video.black++;
} else {
- earliest->decoder->pass ();
+ /* We're inside some video; repeat the frame */
+ _last_incoming_video.video->dcp_time = _video_position;
+ emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
+ step_video_position (_last_incoming_video.video);
+ _statistics.video.repeat++;
}
- }
- break;
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+ consume = false;
+
+ } else if (dv->dcp_time == _video_position) {
+ /* We're ok */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+ _statistics.video.good++;
} else {
- 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 ());
- }
- }
- }
+ /* Too far behind: skip */
+ _statistics.video.skip++;
}
- 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;
- }
+ _just_did_inaccurate_seek = false;
- 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);
- }
- }
+ } else if (da && _audio) {
- 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 ());
+ if (da->dcp_time > _audio_position) {
+ /* Too far ahead */
+ emit_silence (da->dcp_time - _audio_position);
+ consume = false;
+ _statistics.audio.silence += (da->dcp_time - _audio_position);
+ } else if (da->dcp_time == _audio_position) {
+ /* We're ok */
+ emit_audio (earliest_piece, da);
+ _statistics.audio.good += da->data->frames();
+ } else {
+ /* Too far behind: skip */
+ _statistics.audio.skip += da->data->frames();
}
- }
+ } else if (dis && _video) {
+ _image_subtitle.piece = earliest_piece;
+ _image_subtitle.subtitle = dis;
+ update_subtitle_from_image ();
+ } else if (dts && _video) {
+ _text_subtitle.piece = earliest_piece;
+ _text_subtitle.subtitle = dts;
+ update_subtitle_from_text ();
+ }
+
+ if (consume) {
+ earliest_piece->decoder->consume ();
+ }
+
return false;
}
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
{
/* Keep a note of what came in so that we can repeat it if required */
_last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.image = image;
- _last_incoming_video.eyes = eyes;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
+ _last_incoming_video.video = video;
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
@@ -262,23 +267,18 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
assert (content);
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
- if (frc.skip && (frame % 2) == 1) {
- return;
- }
+ FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
- return;
- }
-
- Time const time = content->position() + relative_time + extra - content->trim_start ();
float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
- libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
+ dcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
+ }
shared_ptr<PlayerImage> pi (
new PlayerImage (
- image,
+ video->image,
content->crop(),
image_size,
_video_container_size,
@@ -286,11 +286,15 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
)
);
- if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+ if (
+ _film->with_subtitles () &&
+ _out_subtitle.image &&
+ video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
+ ) {
Position<int> const container_offset (
(_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
+ (_video_container_size.height - image_size.height) / 2
);
pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
@@ -301,18 +305,25 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
_last_video = piece->content;
#endif
- Video (pi, eyes, content->colour_conversion(), same, time);
-
+ Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
+
_last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+}
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+void
+Player::step_video_position (shared_ptr<DecodedVideo> video)
+{
+ /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
+ if (video->eyes != EYES_LEFT) {
+ /* This assumes that the video_frames_to_time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _video_position += _film->video_frames_to_time (1);
}
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
{
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
@@ -324,69 +335,52 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
/* Gain */
if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
gain->apply_gain (content->audio_gain ());
- audio = gain;
- }
-
- /* Resample */
- if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
- }
-
- Time const relative_time = _film->audio_frames_to_time (frame);
-
- if (content->trimmed (relative_time)) {
- return;
+ audio->data = gain;
}
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-
/* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
dcp_mapped->make_silent ();
-
AudioMapping map = content->audio_mapping ();
for (int i = 0; i < map.content_channels(); ++i) {
for (int j = 0; j < _film->audio_channels(); ++j) {
- if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
+ if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
dcp_mapped->accumulate_channel (
- audio.get(),
+ audio->data.get(),
i,
- static_cast<libdcp::Channel> (j),
- map.get (i, static_cast<libdcp::Channel> (j))
+ static_cast<dcp::Channel> (j),
+ map.get (i, static_cast<dcp::Channel> (j))
);
}
}
}
- audio = dcp_mapped;
+ audio->data = dcp_mapped;
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
+ /* Delay */
+ audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+ if (audio->dcp_time < 0) {
+ int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+ if (frames >= audio->data->frames ()) {
return;
}
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+ trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
- audio = trimmed;
- time = 0;
+ audio->data = trimmed;
+ audio->dcp_time = 0;
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ _audio_merger.push (audio->data, audio->dcp_time);
}
void
Player::flush ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
if (_audio && tb.audio) {
Audio (tb.audio, tb.time);
_audio_position += _film->audio_frames_to_time (tb.audio->frames ());
@@ -397,7 +391,7 @@ Player::flush ()
}
while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ emit_silence (_video_position - _audio_position);
}
}
@@ -407,7 +401,7 @@ Player::flush ()
* @return true on error
*/
void
-Player::seek (Time t, bool accurate)
+Player::seek (DCPTime t, bool accurate)
{
if (!_have_valid_pieces) {
setup_pieces ();
@@ -418,96 +412,126 @@ Player::seek (Time t, bool accurate)
}
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
- if (!vc) {
- continue;
- }
-
/* s is the offset of t from the start position of this content */
- Time s = t - vc->position ();
- s = max (static_cast<Time> (0), s);
- s = min (vc->length_after_trim(), s);
+ DCPTime s = t - (*i)->content->position ();
+ s = max (static_cast<DCPTime> (0), s);
+ s = min ((*i)->content->length_after_trim(), s);
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
+ /* Convert this to the content time */
+ ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
/* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
-
- (*i)->reset_repeat ();
+ (*i)->decoder->seek (ct, accurate);
}
- _video_position = _audio_position = t;
+ _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
+ _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
+
+ _audio_merger.clear (_audio_position);
- /* XXX: don't seek audio because we don't need to... */
+ if (!accurate) {
+ /* We just did an inaccurate seek, so it's likely that the next thing seen
+ out of pass() will be a fair distance from _{video,audio}_position. Setting
+ this flag stops pass() from trying to fix that: we assume that if it
+ was an inaccurate seek then the caller does not care too much about
+ inserting black/silence to keep the time tidy.
+ */
+ _just_did_inaccurate_seek = true;
+ }
}
void
Player::setup_pieces ()
{
list<shared_ptr<Piece> > old_pieces = _pieces;
-
_pieces.clear ();
ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
if (!(*i)->paths_valid ()) {
continue;
}
+
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
+
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ DCPTime best_overlap_t = 0;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+ } else {
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
+ /* FFmpeg */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
- fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
- piece->decoder = fd;
+ decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
-
+
+ /* ImageContent */
shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
if (ic) {
- bool reusing = false;
-
/* See if we can re-use an old ImageDecoder */
for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
if (imd && imd->content() == ic) {
- piece = *j;
- reusing = true;
+ decoder = imd;
}
}
- if (!reusing) {
- shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- piece->decoder = id;
+ if (!decoder) {
+ decoder.reset (new ImageDecoder (_film, ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
+ /* SndfileContent */
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
- shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+ decoder.reset (new SndfileDecoder (_film, sc));
+ frc = best_overlap_frc;
+ }
- piece->decoder = sd;
+ /* SubRipContent */
+ shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+ if (rc) {
+ decoder.reset (new SubRipDecoder (_film, rc));
+ frc = best_overlap_frc;
}
- _pieces.push_back (piece);
+ ContentTime st = (*i)->trim_start() * frc->speed_up;
+ decoder->seek (st, true);
+
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
_have_valid_pieces = true;
+
+ /* The Piece for the _last_incoming_video will no longer be valid */
+ _last_incoming_video.video.reset ();
+
+ _video_position = _audio_position = 0;
}
void
@@ -533,7 +557,8 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
property == SubtitleContentProperty::SUBTITLE_SCALE
) {
- update_subtitle ();
+ update_subtitle_from_image ();
+ update_subtitle_from_text ();
Changed (frequent);
} else if (
@@ -558,7 +583,7 @@ Player::playlist_changed ()
}
void
-Player::set_video_container_size (libdcp::Size s)
+Player::set_video_container_size (dcp::Size s)
{
_video_container_size = s;
@@ -576,29 +601,6 @@ Player::set_video_container_size (libdcp::Size s)
);
}
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
-
- if (!create) {
- return shared_ptr<Resampler> ();
- }
-
- _film->log()->log (
- String::compose (
- "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
- )
- );
-
- shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
- _resamplers[c] = r;
- return r;
-}
-
void
Player::emit_black ()
{
@@ -612,17 +614,18 @@ Player::emit_black ()
}
void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
{
if (most == 0) {
return;
}
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+ DCPTime t = min (most, TIME_HZ / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
silence->make_silent ();
Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
+
+ _audio_position += t;
}
void
@@ -639,26 +642,14 @@ Player::film_changed (Film::Property p)
}
void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
-{
- _in_subtitle.piece = weak_piece;
- _in_subtitle.image = image;
- _in_subtitle.rect = rect;
- _in_subtitle.from = from;
- _in_subtitle.to = to;
-
- update_subtitle ();
-}
-
-void
-Player::update_subtitle ()
+Player::update_subtitle_from_image ()
{
- shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+ shared_ptr<Piece> piece = _image_subtitle.piece.lock ();
if (!piece) {
return;
}
- if (!_in_subtitle.image) {
+ if (!_image_subtitle.subtitle->image) {
_out_subtitle.image.reset ();
return;
}
@@ -666,8 +657,8 @@ Player::update_subtitle ()
shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
assert (sc);
- dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
- libdcp::Size scaled_size;
+ dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect;
+ dcp::Size scaled_size;
in_rect.x += sc->subtitle_x_offset ();
in_rect.y += sc->subtitle_y_offset ();
@@ -691,24 +682,15 @@ Player::update_subtitle ()
_out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
_out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
- _out_subtitle.image = _in_subtitle.image->scale (
+ _out_subtitle.image = _image_subtitle.subtitle->image->scale (
scaled_size,
Scaler::from_id ("bicubic"),
- _in_subtitle.image->pixel_format (),
+ _image_subtitle.subtitle->image->pixel_format (),
true
);
-
- /* XXX: hack */
- Time from = _in_subtitle.from;
- Time to = _in_subtitle.to;
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
- if (vc) {
- from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
- to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
- }
- _out_subtitle.from = from + piece->content->position ();
- _out_subtitle.to = to + piece->content->position ();
+ _out_subtitle.from = _image_subtitle.subtitle->dcp_time + piece->content->position ();
+ _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to + piece->content->position ();
}
/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -717,27 +699,40 @@ Player::update_subtitle ()
bool
Player::repeat_last_video ()
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
+ if (!_last_incoming_video.video || !_have_valid_pieces) {
return false;
}
- process_video (
+ emit_video (
_last_incoming_video.weak_piece,
- _last_incoming_video.image,
- _last_incoming_video.eyes,
- _last_incoming_video.same,
- _last_incoming_video.frame,
- _last_incoming_video.extra
+ _last_incoming_video.video
);
return true;
}
+void
+Player::update_subtitle_from_text ()
+{
+ if (_text_subtitle.subtitle->subs.empty ()) {
+ _out_subtitle.image.reset ();
+ return;
+ }
+
+ render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position);
+}
+
+void
+Player::set_approximate_size ()
+{
+ _approximate_size = true;
+}
+
PlayerImage::PlayerImage (
shared_ptr<const Image> in,
Crop crop,
- libdcp::Size inter_size,
- libdcp::Size out_size,
+ dcp::Size inter_size,
+ dcp::Size out_size,
Scaler const * scaler
)
: _in (in)
@@ -757,10 +752,10 @@ PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
}
shared_ptr<Image>
-PlayerImage::image ()
+PlayerImage::image (AVPixelFormat format, bool aligned)
{
- shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
-
+ shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
if (_subtitle_image) {
@@ -769,3 +764,16 @@ PlayerImage::image ()
return out;
}
+
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
+}
+
+PlayerStatistics const &
+Player::statistics () const
+{
+ return _statistics;
+}
diff --git a/src/lib/player.h b/src/lib/player.h
index 11cc99e77..1ef1f58b7 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -29,6 +29,7 @@
#include "rect.h"
#include "audio_merger.h"
#include "audio_content.h"
+#include "decoded.h"
class Job;
class Film;
@@ -36,45 +37,66 @@ class Playlist;
class AudioContent;
class Piece;
class Image;
-class Resampler;
-/** @class Player
- * @brief A class which can `play' a Playlist; emitting its audio and video.
- */
-
-struct IncomingVideo
-{
-public:
- boost::weak_ptr<Piece> weak_piece;
- boost::shared_ptr<const Image> image;
- Eyes eyes;
- bool same;
- VideoContent::Frame frame;
- Time extra;
-};
-
-/** A wrapper for an Image which contains some pending operations; these may
+/** @class PlayerImage
+ * @brief A wrapper for an Image which contains some pending operations; these may
* not be necessary if the receiver of the PlayerImage throws it away.
*/
class PlayerImage
{
public:
- PlayerImage (boost::shared_ptr<const Image>, Crop, libdcp::Size, libdcp::Size, Scaler const *);
+ PlayerImage (boost::shared_ptr<const Image>, Crop, dcp::Size, dcp::Size, Scaler const *);
void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
- boost::shared_ptr<Image> image ();
+ boost::shared_ptr<Image> image (AVPixelFormat, bool);
private:
boost::shared_ptr<const Image> _in;
Crop _crop;
- libdcp::Size _inter_size;
- libdcp::Size _out_size;
+ dcp::Size _inter_size;
+ dcp::Size _out_size;
Scaler const * _scaler;
boost::shared_ptr<const Image> _subtitle_image;
Position<int> _subtitle_position;
};
+
+class PlayerStatistics
+{
+public:
+ struct Video {
+ Video ()
+ : black (0)
+ , repeat (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ int black;
+ int repeat;
+ int good;
+ int skip;
+ } video;
+
+ struct Audio {
+ Audio ()
+ : silence (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ int64_t silence;
+ int64_t good;
+ int64_t skip;
+ } audio;
+
+ void dump (boost::shared_ptr<Log>) const;
+};
+/** @class Player
+ * @brief A class which can `play' a Playlist; emitting its audio and video.
+ */
+
class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
{
public:
@@ -84,16 +106,19 @@ public:
void disable_audio ();
bool pass ();
- void seek (Time, bool);
+ void seek (DCPTime, bool);
- Time video_position () const {
+ DCPTime video_position () const {
return _video_position;
}
- void set_video_container_size (libdcp::Size);
+ void set_video_container_size (dcp::Size);
+ void set_approximate_size ();
bool repeat_last_video ();
+ PlayerStatistics const & statistics () const;
+
/** Emitted when a video frame is ready.
* First parameter is the video image.
* Second parameter is the eye(s) that should see this image.
@@ -101,10 +126,10 @@ public:
* Fourth parameter is true if the image is the same as the last one that was emitted.
* Fifth parameter is the time.
*/
- boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video;
+ boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime)> Video;
/** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, DCPTime)> Audio;
/** Emitted when something has changed such that if we went back and emitted
* the last frame again it would look different. This is not emitted after
@@ -118,19 +143,19 @@ private:
friend class PlayerWrapper;
friend class Piece;
- void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time);
- void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
- void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
void setup_pieces ();
void playlist_changed ();
void content_changed (boost::weak_ptr<Content>, int, bool);
- void do_seek (Time, bool);
+ void do_seek (DCPTime, bool);
void flush ();
void emit_black ();
- void emit_silence (OutputAudioFrame);
- boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
+ void emit_silence (AudioFrame);
void film_changed (Film::Property);
- void update_subtitle ();
+ void update_subtitle_from_image ();
+ void update_subtitle_from_text ();
+ void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>);
+ void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>);
+ void step_video_position (boost::shared_ptr<DecodedVideo>);
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
@@ -143,29 +168,30 @@ private:
std::list<boost::shared_ptr<Piece> > _pieces;
/** The time after the last video that we emitted */
- Time _video_position;
+ DCPTime _video_position;
/** The time after the last audio that we emitted */
- Time _audio_position;
+ DCPTime _audio_position;
- AudioMerger<Time, AudioContent::Frame> _audio_merger;
+ AudioMerger<DCPTime, AudioFrame> _audio_merger;
- libdcp::Size _video_container_size;
+ dcp::Size _video_container_size;
boost::shared_ptr<PlayerImage> _black_frame;
- std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
struct {
boost::weak_ptr<Piece> piece;
- boost::shared_ptr<Image> image;
- dcpomatic::Rect<double> rect;
- Time from;
- Time to;
- } _in_subtitle;
+ boost::shared_ptr<DecodedImageSubtitle> subtitle;
+ } _image_subtitle;
struct {
- boost::shared_ptr<Image> image;
+ boost::weak_ptr<Piece> piece;
+ boost::shared_ptr<DecodedTextSubtitle> subtitle;
+ } _text_subtitle;
+
+ struct {
Position<int> position;
- Time from;
- Time to;
+ boost::shared_ptr<Image> image;
+ DCPTime from;
+ DCPTime to;
} _out_subtitle;
#ifdef DCPOMATIC_DEBUG
@@ -174,7 +200,15 @@ private:
bool _last_emit_was_black;
- IncomingVideo _last_incoming_video;
+ struct {
+ boost::weak_ptr<Piece> weak_piece;
+ boost::shared_ptr<DecodedVideo> video;
+ } _last_incoming_video;
+
+ bool _just_did_inaccurate_seek;
+ bool _approximate_size;
+
+ PlayerStatistics _statistics;
boost::signals2::scoped_connection _playlist_changed_connection;
boost::signals2::scoped_connection _playlist_content_changed_connection;
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
index daa82cb94..4175de4c9 100644
--- a/src/lib/playlist.cc
+++ b/src/lib/playlist.cc
@@ -81,7 +81,7 @@ Playlist::maybe_sequence_video ()
_sequencing_video = true;
ContentList cl = _content;
- Time next = 0;
+ DCPTime next = 0;
for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
if (!dynamic_pointer_cast<VideoContent> (*i)) {
continue;
@@ -254,10 +254,10 @@ Playlist::best_dcp_frame_rate () const
return best->dcp;
}
-Time
+DCPTime
Playlist::length () const
{
- Time len = 0;
+ DCPTime len = 0;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
len = max (len, (*i)->end() + 1);
}
@@ -279,10 +279,10 @@ Playlist::reconnect ()
}
}
-Time
+DCPTime
Playlist::video_end () const
{
- Time end = 0;
+ DCPTime end = 0;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
if (dynamic_pointer_cast<const VideoContent> (*i)) {
end = max (end, (*i)->end ());
@@ -292,6 +292,23 @@ Playlist::video_end () const
return end;
}
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (!vc) {
+ break;
+ }
+
+ if (vc->position() >= t && t < vc->end()) {
+ return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+ }
+ }
+
+ return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
void
Playlist::set_sequence_video (bool s)
{
@@ -314,7 +331,7 @@ Playlist::content () const
void
Playlist::repeat (ContentList c, int n)
{
- pair<Time, Time> range (TIME_MAX, 0);
+ pair<DCPTime, DCPTime> range (TIME_MAX, 0);
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
range.first = min (range.first, (*i)->position ());
range.second = max (range.second, (*i)->position ());
@@ -322,7 +339,7 @@ Playlist::repeat (ContentList c, int n)
range.second = max (range.second, (*i)->end ());
}
- Time pos = range.second;
+ DCPTime pos = range.second;
for (int i = 0; i < n; ++i) {
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
shared_ptr<Content> copy = (*i)->clone ();
@@ -355,7 +372,7 @@ Playlist::move_earlier (shared_ptr<Content> c)
return;
}
- Time const p = (*previous)->position ();
+ DCPTime const p = (*previous)->position ();
(*previous)->set_position (p + c->length_after_trim ());
c->set_position (p);
sort (_content.begin(), _content.end(), ContentSorter ());
@@ -382,7 +399,7 @@ Playlist::move_later (shared_ptr<Content> c)
return;
}
- Time const p = (*next)->position ();
+ DCPTime const p = (*next)->position ();
(*next)->set_position (c->position ());
c->set_position (p + c->length_after_trim ());
sort (_content.begin(), _content.end(), ContentSorter ());
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
index 1915e3d04..35709f109 100644
--- a/src/lib/playlist.h
+++ b/src/lib/playlist.h
@@ -25,6 +25,7 @@
#include <boost/enable_shared_from_this.hpp>
#include "ffmpeg_content.h"
#include "audio_mapping.h"
+#include "util.h"
class Content;
class FFmpegContent;
@@ -70,10 +71,11 @@ public:
std::string video_identifier () const;
- Time length () const;
+ DCPTime length () const;
int best_dcp_frame_rate () const;
- Time video_end () const;
+ DCPTime video_end () const;
+ FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
void set_sequence_video (bool);
void maybe_sequence_video ();
diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc
new file mode 100644
index 000000000..c18cb4272
--- /dev/null
+++ b/src/lib/render_subtitles.cc
@@ -0,0 +1,150 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+ switch (v_align) {
+ case dcp::TOP:
+ return (v_position / 100) * target_height - offset;
+ case dcp::CENTER:
+ return (0.5 + v_position / 100) * target_height - offset;
+ case dcp::BOTTOM:
+ return (1.0 - v_position / 100) * target_height - offset;
+ }
+
+ return 0;
+}
+
+void
+render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target, shared_ptr<Image>& image, Position<int>& position)
+{
+ if (subtitles.empty ()) {
+ image.reset ();
+ return;
+ }
+
+ /* Estimate height that the subtitle image needs to be */
+ optional<int> top;
+ optional<int> bottom;
+ for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+ int const t = b - i->size() * target.height / (11 * 72);
+
+ top = min (top.get_value_or (t), t);
+ bottom = max (bottom.get_value_or (b), b);
+ }
+
+ top = top.get() - 32;
+ bottom = bottom.get() + 32;
+
+ image.reset (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false));
+ image->make_black ();
+
+ Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+ image->data()[0],
+ Cairo::FORMAT_ARGB32,
+ image->size().width,
+ image->size().height,
+ Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+ );
+
+ Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+ Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+ layout->set_width (image->size().width * PANGO_SCALE);
+ layout->set_alignment (Pango::ALIGN_CENTER);
+
+ context->set_line_width (1);
+
+ for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ string f = i->font ();
+ if (f.empty ()) {
+ f = "Arial";
+ }
+ Pango::FontDescription font (f);
+ font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+ if (i->italic ()) {
+ font.set_style (Pango::STYLE_ITALIC);
+ }
+ layout->set_font_description (font);
+ layout->set_text (i->text ());
+
+ /* Compute fade factor */
+ /* XXX */
+ float fade_factor = 1;
+#if 0
+ dcp::Time now (time * 1000 / (4 * TIME_HZ));
+ dcp::Time end_fade_up = i->in() + i->fade_up_time ();
+ dcp::Time start_fade_down = i->out() - i->fade_down_time ();
+ if (now < end_fade_up) {
+ fade_factor = (now - i->in()) / i->fade_up_time();
+ } else if (now > start_fade_down) {
+ fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+ }
+#endif
+
+ layout->update_from_cairo_context (context);
+
+ /* Work out position */
+
+ int const x = 0;
+ int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+ if (i->effect() == dcp::SHADOW) {
+ /* Drop-shadow effect */
+ dcp::Color const ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ context->move_to (x + 4, y + 4);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+ }
+
+ /* The actual subtitle */
+ context->move_to (x, y);
+ dcp::Color const c = i->color ();
+ context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+
+ if (i->effect() == dcp::BORDER) {
+ /* Border effect */
+ context->move_to (x, y);
+ dcp::Color ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->stroke ();
+ }
+ }
+}
+
diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h
new file mode 100644
index 000000000..895a6fd25
--- /dev/null
+++ b/src/lib/render_subtitles.h
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libdcp/subtitle_string.h>
+#include <libdcp/util.h>
+#include "position.h"
+
+class Image;
+
+void
+render_subtitles (std::list<dcp::SubtitleString>, dcp::Size, boost::shared_ptr<Image> &, Position<int> &);
diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc
index d897bf562..00121384d 100644
--- a/src/lib/resampler.cc
+++ b/src/lib/resampler.cc
@@ -64,11 +64,9 @@ Resampler::~Resampler ()
swr_free (&_swr_context);
}
-pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
-Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
{
- AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
-
/* Compute the resampled frames count and add 32 for luck */
int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
@@ -84,7 +82,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
}
resampled->set_frames (resampled_frames);
- return make_pair (resampled, resamp_time);
+ return resampled;
}
shared_ptr<const AudioBuffers>
diff --git a/src/lib/resampler.h b/src/lib/resampler.h
index 69ec83ba9..4ee11a7f0 100644
--- a/src/lib/resampler.h
+++ b/src/lib/resampler.h
@@ -33,7 +33,7 @@ public:
Resampler (int, int, int);
~Resampler ();
- std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
boost::shared_ptr<const AudioBuffers> flush ();
private:
diff --git a/src/lib/server.cc b/src/lib/server.cc
index 1f3f61e42..bf7541c33 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -57,7 +57,7 @@ using boost::bind;
using boost::scoped_array;
using boost::optional;
using boost::lexical_cast;
-using libdcp::Size;
+using dcp::Size;
Server::Server (shared_ptr<Log> log, bool verbose)
: _log (log)
@@ -85,7 +85,7 @@ Server::process (shared_ptr<Socket> socket, struct timeval& after_read, struct t
return -1;
}
- libdcp::Size size (
+ dcp::Size size (
xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
);
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
index 796229777..d3acc7d2e 100644
--- a/src/lib/sndfile_content.cc
+++ b/src/lib/sndfile_content.cc
@@ -49,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml:
, _audio_mapping (node->node_child ("AudioMapping"), version)
{
_audio_channels = node->number_child<int> ("AudioChannels");
- _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+ _audio_length = node->number_child<AudioFrame> ("AudioLength");
_audio_frame_rate = node->number_child<int> ("AudioFrameRate");
}
@@ -141,13 +141,13 @@ SndfileContent::as_xml (xmlpp::Node* node) const
_audio_mapping.as_xml (node->add_child("AudioMapping"));
}
-Time
+DCPTime
SndfileContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- OutputAudioFrame const len = audio_length() * output_audio_frame_rate() / content_audio_frame_rate ();
+ AudioFrame const len = audio_length() * output_audio_frame_rate() / content_audio_frame_rate ();
/* XXX: this depends on whether, alongside this audio, we are running video slower or faster than
it should be. The calculation above works out the output audio frames assuming that we are just
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
index 701ff16b2..94f46fea3 100644
--- a/src/lib/sndfile_content.h
+++ b/src/lib/sndfile_content.h
@@ -44,7 +44,7 @@ public:
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
/* AudioContent */
int audio_channels () const {
@@ -52,7 +52,7 @@ public:
return _audio_channels;
}
- AudioContent::Frame audio_length () const {
+ AudioFrame audio_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_length;
}
@@ -75,7 +75,7 @@ public:
private:
int _audio_channels;
- AudioContent::Frame _audio_length;
+ AudioFrame _audio_length;
int _audio_frame_rate;
AudioMapping _audio_mapping;
};
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
index e10f4f568..d6537843e 100644
--- a/src/lib/sndfile_decoder.cc
+++ b/src/lib/sndfile_decoder.cc
@@ -55,9 +55,13 @@ SndfileDecoder::~SndfileDecoder ()
delete[] _deinterleave_buffer;
}
-void
+bool
SndfileDecoder::pass ()
{
+ if (_remaining == 0) {
+ return true;
+ }
+
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
*/
@@ -90,9 +94,11 @@ SndfileDecoder::pass ()
}
data->set_frames (this_time);
- audio (data, _done);
+ audio (data, _done * TIME_HZ / audio_frame_rate ());
_done += this_time;
_remaining -= this_time;
+
+ return _remaining == 0;
}
int
@@ -101,7 +107,7 @@ SndfileDecoder::audio_channels () const
return _info.channels;
}
-AudioContent::Frame
+AudioFrame
SndfileDecoder::audio_length () const
{
return _info.frames;
@@ -113,8 +119,12 @@ SndfileDecoder::audio_frame_rate () const
return _info.samplerate;
}
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
{
- return _audio_position >= _sndfile_content->audio_length ();
+ Decoder::seek (t, accurate);
+ AudioDecoder::seek (t, accurate);
+
+ _done = t * audio_frame_rate() / TIME_HZ;
+ _remaining = _info.frames - _done;
}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
index 77fa6d177..46d9c5e5c 100644
--- a/src/lib/sndfile_decoder.h
+++ b/src/lib/sndfile_decoder.h
@@ -29,18 +29,19 @@ public:
SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
~SndfileDecoder ();
- void pass ();
- bool done () const;
+ void seek (ContentTime, bool);
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ AudioFrame 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;
+ AudioFrame _done;
+ AudioFrame _remaining;
float* _deinterleave_buffer;
};
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644
index 000000000..380a2ce2c
--- /dev/null
+++ b/src/lib/subrip.cc
@@ -0,0 +1,236 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/algorithm/string.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+ FILE* f = fopen_boost (content->path (0), "r");
+ if (!f) {
+ throw OpenFileError (content->path (0));
+ }
+
+ enum {
+ COUNTER,
+ METADATA,
+ CONTENT
+ } state = COUNTER;
+
+ char buffer[256];
+ int next_count = 1;
+
+ boost::optional<SubRipSubtitle> current;
+ list<string> lines;
+
+ while (!feof (f)) {
+ fgets (buffer, sizeof (buffer), f);
+ if (feof (f)) {
+ break;
+ }
+
+ string line (buffer);
+ trim_right_if (line, boost::is_any_of ("\n\r"));
+
+ switch (state) {
+ case COUNTER:
+ {
+ int x = 0;
+ try {
+ x = lexical_cast<int> (line);
+ } catch (...) {
+
+ }
+
+ if (x == next_count) {
+ state = METADATA;
+ ++next_count;
+ current = SubRipSubtitle ();
+ } else {
+ throw SubRipError (line, _("a subtitle count"), content->path (0));
+ }
+ }
+ break;
+ case METADATA:
+ {
+ vector<string> p;
+ boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+ if (p.size() != 3 && p.size() != 7) {
+ throw SubRipError (line, _("a time/position line"), content->path (0));
+ }
+
+ current->from = convert_time (p[0]);
+ current->to = convert_time (p[2]);
+
+ if (p.size() > 3) {
+ current->x1 = convert_coordinate (p[3]);
+ current->x2 = convert_coordinate (p[4]);
+ current->y1 = convert_coordinate (p[5]);
+ current->y2 = convert_coordinate (p[6]);
+ }
+ state = CONTENT;
+ break;
+ }
+ case CONTENT:
+ if (line.empty ()) {
+ state = COUNTER;
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ current.reset ();
+ lines.clear ();
+ } else {
+ lines.push_back (line);
+ }
+ break;
+ }
+ }
+
+ if (state == CONTENT) {
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ }
+
+ fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+ ContentTime r = 0;
+
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 3);
+ r += lexical_cast<int> (a[0]) * 60 * 60 * TIME_HZ;
+ r += lexical_cast<int> (a[1]) * 60 * TIME_HZ;
+
+ vector<string> b;
+ boost::algorithm::split (b, a[2], boost::is_any_of (","));
+ r += lexical_cast<int> (b[0]) * TIME_HZ;
+ r += lexical_cast<int> (b[1]) * TIME_HZ / 1000;
+
+ return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 2);
+ return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+ if (!p.text.empty ()) {
+ pieces.push_back (p);
+ p.text.clear ();
+ }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+ list<SubRipSubtitlePiece> pieces;
+
+ SubRipSubtitlePiece p;
+
+ enum {
+ TEXT,
+ TAG
+ } state = TEXT;
+
+ string tag;
+
+ /* XXX: missing <font> support */
+ /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+ not work, I think.
+ */
+
+ for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+ for (size_t j = 0; j < i->size(); ++j) {
+ switch (state) {
+ case TEXT:
+ if ((*i)[j] == '<' || (*i)[j] == '{') {
+ state = TAG;
+ } else {
+ p.text += (*i)[j];
+ }
+ break;
+ case TAG:
+ if ((*i)[j] == '>' || (*i)[j] == '}') {
+ if (tag == "b") {
+ maybe_content (pieces, p);
+ p.bold = true;
+ } else if (tag == "/b") {
+ maybe_content (pieces, p);
+ p.bold = false;
+ } else if (tag == "i") {
+ maybe_content (pieces, p);
+ p.italic = true;
+ } else if (tag == "/i") {
+ maybe_content (pieces, p);
+ p.italic = false;
+ } else if (tag == "u") {
+ maybe_content (pieces, p);
+ p.underline = true;
+ } else if (tag == "/u") {
+ maybe_content (pieces, p);
+ p.underline = false;
+ }
+ tag.clear ();
+ state = TEXT;
+ } else {
+ tag += (*i)[j];
+ }
+ break;
+ }
+ }
+ }
+
+ maybe_content (pieces, p);
+
+ return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+ if (_subtitles.empty ()) {
+ return 0;
+ }
+
+ return _subtitles.back().to;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644
index 000000000..e7d21675f
--- /dev/null
+++ b/src/lib/subrip.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+ SubRip (boost::shared_ptr<const SubRipContent>);
+
+ ContentTime length () const;
+
+protected:
+ std::vector<SubRipSubtitle> _subtitles;
+
+private:
+ friend class subrip_time_test;
+ friend class subrip_coordinate_test;
+ friend class subrip_content_test;
+ friend class subrip_parse_test;
+
+ static ContentTime convert_time (std::string);
+ static int convert_coordinate (std::string);
+ static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+ static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644
index 000000000..73499a5f6
--- /dev/null
+++ b/src/lib/subrip_content.cc
@@ -0,0 +1,100 @@
+/*
+ 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 "i18n.h"
+
+using std::stringstream;
+using std::string;
+using boost::shared_ptr;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+ : Content (film, path)
+ , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
+ : Content (film, node)
+ , SubtitleContent (film, node, version)
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job> job)
+{
+ Content::examine (job);
+ SubRip s (shared_from_this ());
+ boost::mutex::scoped_lock lm (_mutex);
+ _length = s.length ();
+}
+
+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)
+{
+ node->add_child("Type")->add_child_text ("SubRip");
+ Content::as_xml (node);
+ SubtitleContent::as_xml (node);
+}
+
+DCPTime
+SubRipContent::full_length () const
+{
+ /* XXX: this assumes that the timing of the SubRip file is appropriate
+ for the DCP's frame rate.
+ */
+ return _length;
+}
+
+string
+SubRipContent::identifier () const
+{
+ LocaleGuard lg;
+
+ stringstream s;
+ s << Content::identifier()
+ << "_" << subtitle_scale()
+ << "_" << subtitle_x_offset()
+ << "_" << subtitle_y_offset();
+
+ return s.str ();
+}
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
new file mode 100644
index 000000000..6138c047e
--- /dev/null
+++ b/src/lib/subrip_content.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+ SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+ boost::shared_ptr<SubRipContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *);
+ DCPTime full_length () const;
+ std::string identifier () const;
+
+private:
+ DCPTime _length;
+};
diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc
new file mode 100644
index 000000000..6639cee7c
--- /dev/null
+++ b/src/lib/subrip_decoder.cc
@@ -0,0 +1,66 @@
+/*
+ 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 <libdcp/subtitle_string.h>
+#include "subrip_decoder.h"
+
+using std::list;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const Film> film, shared_ptr<const SubRipContent> content)
+ : Decoder (film)
+ , SubtitleDecoder (film)
+ , SubRip (content)
+ , _next (0)
+{
+
+}
+
+bool
+SubRipDecoder::pass ()
+{
+ if (_next >= _subtitles.size ()) {
+ return true;
+ }
+
+ list<dcp::SubtitleString> out;
+ for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+ out.push_back (
+ dcp::SubtitleString (
+ "Arial",
+ i->italic,
+ dcp::Color (255, 255, 255),
+ 72,
+ _subtitles[_next].from,
+ _subtitles[_next].to,
+ 0.9,
+ dcp::BOTTOM,
+ i->text,
+ dcp::NONE,
+ dcp::Color (255, 255, 255),
+ 0,
+ 0
+ )
+ );
+ }
+
+ text_subtitle (out);
+ _next++;
+ return false;
+}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
new file mode 100644
index 000000000..26d5d5010
--- /dev/null
+++ b/src/lib/subrip_decoder.h
@@ -0,0 +1,39 @@
+/*
+ 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 Film>, boost::shared_ptr<const SubRipContent>);
+
+ bool pass ();
+
+private:
+ size_t _next;
+};
+
+#endif
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
new file mode 100644
index 000000000..2af5f66ae
--- /dev/null
+++ b/src/lib/subrip_subtitle.h
@@ -0,0 +1,58 @@
+/*
+ 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 <libdcp/types.h>
+#include "types.h"
+
+struct SubRipSubtitlePiece
+{
+ SubRipSubtitlePiece ()
+ : bold (false)
+ , italic (false)
+ , underline (false)
+ {}
+
+ std::string text;
+ bool bold;
+ bool italic;
+ bool underline;
+ dcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+ SubRipSubtitle ()
+ : from (0)
+ , to (0)
+ {}
+
+ ContentTime from;
+ ContentTime to;
+ boost::optional<int> x1;
+ boost::optional<int> x2;
+ boost::optional<int> y1;
+ boost::optional<int> y2;
+ std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc
index c06f3d718..ce48e6619 100644
--- a/src/lib/subtitle_decoder.cc
+++ b/src/lib/subtitle_decoder.cc
@@ -20,7 +20,9 @@
#include <boost/shared_ptr.hpp>
#include "subtitle_decoder.h"
+using std::list;
using boost::shared_ptr;
+using boost::optional;
SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
: Decoder (f)
@@ -29,11 +31,17 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
}
-/** Called by subclasses when a subtitle is ready.
+/** Called by subclasses when an image subtitle is ready.
* Image may be 0 to say that there is no current subtitle.
*/
void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::image_subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, ContentTime from, ContentTime to)
{
- Subtitle (image, rect, from, to);
+ _pending.push_back (shared_ptr<DecodedImageSubtitle> (new DecodedImageSubtitle (image, rect, from, to)));
+}
+
+void
+SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s)
+{
+ _pending.push_back (shared_ptr<DecodedTextSubtitle> (new DecodedTextSubtitle (s)));
}
diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h
index eeeadbd3f..6a962ca47 100644
--- a/src/lib/subtitle_decoder.h
+++ b/src/lib/subtitle_decoder.h
@@ -17,13 +17,18 @@
*/
+#ifndef DCPOMATIC_SUBTITLE_DECODER_H
+#define DCPOMATIC_SUBTITLE_DECODER_H
+
#include <boost/signals2.hpp>
+#include <libdcp/subtitle_string.h>
#include "decoder.h"
#include "rect.h"
#include "types.h"
+#include "decoded.h"
class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
class Image;
class SubtitleDecoder : public virtual Decoder
@@ -31,8 +36,9 @@ class SubtitleDecoder : public virtual Decoder
public:
SubtitleDecoder (boost::shared_ptr<const Film>);
- boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
-
protected:
- void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+ void image_subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, ContentTime, ContentTime);
+ void text_subtitle (std::list<dcp::SubtitleString>);
};
+
+#endif
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 5c195ee1b..46fc97fb3 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -114,6 +114,6 @@ 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();
+ VideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
return left / fps;
}
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 1c8f7e3eb..ba4d3b040 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -62,7 +62,8 @@ audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
* @param e Encoder to use.
*/
Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
- : _player (f->make_player ())
+ : _film (f)
+ , _player (f->make_player ())
, _encoder (new Encoder (f, j))
, _finishing (false)
{
@@ -78,6 +79,8 @@ Transcoder::go ()
_finishing = true;
_encoder->process_end ();
+
+ _player->statistics().dump (_film->log ());
}
float
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index d7736d4e8..25b2ef908 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -42,6 +42,7 @@ public:
}
private:
+ boost::shared_ptr<const Film> _film;
boost::shared_ptr<Player> _player;
boost::shared_ptr<Encoder> _encoder;
bool _finishing;
diff --git a/src/lib/types.h b/src/lib/types.h
index 96b993a8e..a1b40f299 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -38,11 +38,12 @@ class AudioBuffers;
*/
#define SERVER_LINK_VERSION 1
-typedef int64_t Time;
+typedef int64_t DCPTime;
#define TIME_MAX INT64_MAX
-#define TIME_HZ ((Time) 96000)
-typedef int64_t OutputAudioFrame;
-typedef int OutputVideoFrame;
+#define TIME_HZ ((DCPTime) 96000)
+typedef int64_t ContentTime;
+typedef int64_t AudioFrame;
+typedef int VideoFrame;
typedef std::vector<boost::shared_ptr<Content> > ContentList;
typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
@@ -97,7 +98,7 @@ struct Crop
/** Number of pixels to remove from the bottom */
int bottom;
- libdcp::Size apply (libdcp::Size s, int minimum = 4) const {
+ dcp::Size apply (dcp::Size s, int minimum = 4) const {
s.width -= left + right;
s.height -= top + bottom;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index b2f8c4470..fd3a318b0 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -48,6 +48,7 @@
#include <openssl/md5.h>
#include <magick/MagickCore.h>
#include <magick/version.h>
+#include <pangomm/init.h>
#include <libdcp/version.h>
#include <libdcp/util.h>
#include <libdcp/signer_chain.h>
@@ -100,7 +101,7 @@ using boost::shared_ptr;
using boost::thread;
using boost::lexical_cast;
using boost::optional;
-using libdcp::Size;
+using dcp::Size;
static boost::thread::id ui_thread;
static boost::filesystem::path backtrace_file;
@@ -250,7 +251,7 @@ dependency_version_summary ()
<< N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
<< MagickVersion << N_(", ")
<< N_("libssh ") << ssh_version (0) << N_(", ")
- << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
+ << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
return s.str ();
}
@@ -341,7 +342,8 @@ dcpomatic_setup ()
set_terminate (terminate);
- libdcp::init ();
+ Pango::init ();
+ dcp::init ();
Ratio::setup_ratios ();
DCPContentType::setup_dcp_content_types ();
@@ -781,7 +783,7 @@ ensure_ui_thread ()
* @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)
+video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second)
{
return ((int64_t) v * audio_sample_rate / frames_per_second);
}
@@ -806,7 +808,7 @@ audio_channel_name (int c)
return channels[c];
}
-FrameRateConversion::FrameRateConversion (float source, int dcp)
+FrameRateChange::FrameRateChange (float source, int dcp)
: skip (false)
, repeat (1)
, change_speed (false)
@@ -824,7 +826,8 @@ FrameRateConversion::FrameRateConversion (float source, int dcp)
repeat = round (dcp / source);
}
- change_speed = !about_equal (source * factor(), dcp);
+ speed_up = dcp / (source * factor());
+ change_speed = !about_equal (speed_up, 1.0);
if (!skip && repeat == 1 && !change_speed) {
description = _("Content and DCP have the same rate.\n");
@@ -886,7 +889,7 @@ tidy_for_filename (string f)
return t;
}
-shared_ptr<const libdcp::Signer>
+shared_ptr<const dcp::Signer>
make_signer ()
{
boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
@@ -906,47 +909,54 @@ make_signer ()
if (!boost::filesystem::exists (p)) {
boost::filesystem::remove_all (sd);
boost::filesystem::create_directories (sd);
- libdcp::make_signer_chain (sd, openssl_path ());
+ dcp::make_signer_chain (sd, openssl_path ());
break;
}
++i;
}
- libdcp::CertificateChain chain;
+ dcp::CertificateChain chain;
{
boost::filesystem::path p (sd);
p /= "ca.self-signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
{
boost::filesystem::path p (sd);
p /= "intermediate.signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
{
boost::filesystem::path p (sd);
p /= "leaf.signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
boost::filesystem::path signer_key (sd);
signer_key /= "leaf.key";
- return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
+ return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
}
-libdcp::Size
-fit_ratio_within (float ratio, libdcp::Size full_frame)
+dcp::Size
+fit_ratio_within (float ratio, dcp::Size full_frame)
{
if (ratio < full_frame.ratio ()) {
- return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
+ return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
}
- return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
+ return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
+}
+
+DCPTime
+time_round_up (DCPTime t, DCPTime nearest)
+{
+ DCPTime const a = t + nearest - 1;
+ return a - (a % nearest);
}
void *
@@ -958,4 +968,3 @@ wrapped_av_malloc (size_t s)
}
return p;
}
-
diff --git a/src/lib/util.h b/src/lib/util.h
index a229bbfc9..b89c71eee 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -32,6 +32,7 @@
#include <boost/optional.hpp>
#include <boost/filesystem.hpp>
#include <libdcp/util.h>
+#include <libdcp/signer.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
@@ -76,12 +77,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 boost::shared_ptr<const dcp::Signer> make_signer ();
+extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
-struct FrameRateConversion
+struct FrameRateChange
{
- FrameRateConversion (float, int);
+ FrameRateChange (float, int);
/** @return factor by which to multiply a source frame rate
to get the effective rate after any skip or repeat has happened.
@@ -109,11 +110,17 @@ struct FrameRateConversion
*/
bool change_speed;
+ /** Amount by which the video is being sped-up in the DCP; e.g. for a
+ * 24fps source in a 25fps DCP this would be 25/24.
+ */
+ float speed_up;
+
std::string description;
};
extern int dcp_audio_frame_rate (int);
extern int stride_round_up (int, int const *, int);
+extern DCPTime time_round_up (DCPTime, DCPTime);
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);
@@ -161,7 +168,7 @@ private:
int _timeout;
};
-extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+extern int64_t video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second);
class LocaleGuard
{
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index cc075a34c..80515ca4d 100644
--- a/src/lib/video_content.cc
+++ b/src/lib/video_content.cc
@@ -59,7 +59,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
setup_default_colour_conversion ();
}
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, VideoFrame len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
@@ -83,7 +83,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod
: Content (f, node)
, _ratio (0)
{
- _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+ _video_length = node->number_child<VideoFrame> ("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");
@@ -166,14 +166,14 @@ VideoContent::as_xml (xmlpp::Node* node) const
void
VideoContent::setup_default_colour_conversion ()
{
- _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
+ _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
}
void
VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
{
/* These examiner calls could call other content methods which take a lock on the mutex */
- libdcp::Size const vs = d->video_size ();
+ dcp::Size const vs = d->video_size ();
float const vfr = d->video_frame_rate ();
{
@@ -318,17 +318,17 @@ VideoContent::technical_summary () const
return String::compose ("video: length %1, size %2x%3, rate %4", video_length(), video_size().width, video_size().height, video_frame_rate());
}
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_3d_split () const
{
- libdcp::Size const s = video_size ();
+ dcp::Size const s = video_size ();
switch (video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
return s;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
- return libdcp::Size (s.width / 2, s.height);
+ return dcp::Size (s.width / 2, s.height);
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
- return libdcp::Size (s.width, s.height / 2);
+ return dcp::Size (s.width, s.height / 2);
}
assert (false);
@@ -346,26 +346,28 @@ VideoContent::set_colour_conversion (ColourConversion c)
}
/** @return Video size after 3D split and crop */
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_crop () const
{
return crop().apply (video_size_after_3d_split ());
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding frame index, rounded up so that the frame index
+ * is that of the next complete frame which starts after `t'.
*/
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+VideoFrame
+VideoContent::time_to_content_video_frames (DCPTime t) const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
/* Here we are converting from time (in the DCP) to a frame number in the content.
Hence we need to use the DCP's frame rate and the double/skip correction, not
- the source's rate.
+ the source's rate; source rate will be equal to DCP rate if we ignore
+ double/skip. There's no need to call Film::active_frame_rate_change() here
+ as we know that we are it (since we're video).
*/
- return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
+ return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ));
}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
index 141525e01..96572bbd9 100644
--- a/src/lib/video_content.h
+++ b/src/lib/video_content.h
@@ -43,7 +43,7 @@ public:
typedef int Frame;
VideoContent (boost::shared_ptr<const Film>);
- VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+ VideoContent (boost::shared_ptr<const Film>, DCPTime, VideoFrame);
VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -53,12 +53,12 @@ public:
virtual std::string information () const;
virtual std::string identifier () const;
- VideoContent::Frame video_length () const {
+ VideoFrame video_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_length;
}
- libdcp::Size video_size () const {
+ dcp::Size video_size () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_size;
}
@@ -120,15 +120,15 @@ public:
return _colour_conversion;
}
- libdcp::Size video_size_after_3d_split () const;
- libdcp::Size video_size_after_crop () const;
+ dcp::Size video_size_after_3d_split () const;
+ dcp::Size video_size_after_crop () const;
- VideoContent::Frame time_to_content_video_frames (Time) const;
+ VideoFrame time_to_content_video_frames (DCPTime) const;
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
- VideoContent::Frame _video_length;
+ VideoFrame _video_length;
float _video_frame_rate;
private:
@@ -139,7 +139,7 @@ private:
void setup_default_colour_conversion ();
- libdcp::Size _video_size;
+ dcp::Size _video_size;
VideoFrameType _video_frame_type;
Crop _crop;
Ratio const * _ratio;
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index e7ddec5e6..a3b45716c 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -24,38 +24,38 @@
using std::cout;
using boost::shared_ptr;
+using boost::optional;
VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
: Decoder (f)
, _video_content (c)
- , _video_position (0)
{
}
+/** Called by subclasses when they have a video frame ready */
void
-VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoFrame frame)
{
switch (_video_content->video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
- Video (image, EYES_BOTH, same, frame);
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image, EYES_BOTH, same, frame)));
break;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
{
int const half = image->size().width / 2;
- Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
- Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame)));
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame)));
break;
}
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
{
int const half = image->size().height / 2;
- Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
- Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame)));
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame)));
break;
}
+ default:
+ assert (false);
}
-
- _video_position = frame + 1;
}
-
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 142320a04..8947c2708 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -25,6 +25,7 @@
#include "decoder.h"
#include "video_content.h"
#include "util.h"
+#include "decoded.h"
class VideoContent;
class Image;
@@ -34,24 +35,14 @@ class VideoDecoder : public virtual Decoder
public:
VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
- /** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- */
- virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is the eye(s) which should see this image.
- * Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
- * Fourth parameter is the frame within our source.
- */
- boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
-
+ boost::shared_ptr<const VideoContent> video_content () const {
+ return _video_content;
+ }
+
protected:
- void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ void video (boost::shared_ptr<const Image>, bool, VideoFrame);
boost::shared_ptr<const VideoContent> _video_content;
- VideoContent::Frame _video_position;
};
#endif
diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h
index 039c494b5..6ee761384 100644
--- a/src/lib/video_examiner.h
+++ b/src/lib/video_examiner.h
@@ -26,6 +26,6 @@ class VideoExaminer
public:
virtual ~VideoExaminer () {}
virtual float video_frame_rate () const = 0;
- virtual libdcp::Size video_size () const = 0;
- virtual VideoContent::Frame video_length () const = 0;
+ virtual dcp::Size video_size () const = 0;
+ virtual VideoFrame video_length () const = 0;
};
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index 42187dc6e..a4c10195c 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -19,10 +19,14 @@
#include <fstream>
#include <cerrno>
-#include <libdcp/mono_picture_asset.h>
-#include <libdcp/stereo_picture_asset.h>
-#include <libdcp/sound_asset.h>
+#include <libdcp/mono_picture_mxf.h>
+#include <libdcp/stereo_picture_mxf.h>
+#include <libdcp/sound_mxf.h>
+#include <libdcp/sound_mxf_writer.h>
#include <libdcp/reel.h>
+#include <libdcp/reel_mono_picture_asset.h>
+#include <libdcp/reel_stereo_picture_asset.h>
+#include <libdcp/reel_sound_asset.h>
#include <libdcp/dcp.h>
#include <libdcp/cpl.h>
#include "writer.h"
@@ -48,6 +52,7 @@ using std::cout;
using std::stringstream;
using boost::shared_ptr;
using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
@@ -80,35 +85,33 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
*/
if (_film->three_d ()) {
- _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
} else {
- _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
}
- _picture_asset->set_edit_rate (_film->video_frame_rate ());
- _picture_asset->set_size (fit_ratio_within (_film->container()->ratio(), _film->full_frame ()));
- _picture_asset->set_interop (_film->interop ());
+ _picture_mxf->set_size (fit_ratio_within (_film->container()->ratio(), _film->full_frame ()));
if (_film->encrypted ()) {
- _picture_asset->set_key (_film->key ());
+ _picture_mxf->set_key (_film->key ());
}
- _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+ _picture_mxf_writer = _picture_mxf->start_write (
+ _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
+ _film->interop() ? dcp::INTEROP : dcp::SMPTE,
+ _first_nonexistant_frame > 0
+ );
- _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
- _sound_asset->set_edit_rate (_film->video_frame_rate ());
- _sound_asset->set_channels (_film->audio_channels ());
- _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
- _sound_asset->set_interop (_film->interop ());
+ _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
if (_film->encrypted ()) {
- _sound_asset->set_key (_film->key ());
+ _sound_mxf->set_key (_film->key ());
}
- /* Write the sound asset into the film directory so that we leave the creation
+ /* Write the sound MXF into the film directory so that we leave the creation
of the DCP directory until the last minute.
*/
- _sound_asset_writer = _sound_asset->start_write ();
+ _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
_thread = new boost::thread (boost::bind (&Writer::thread, this));
@@ -161,7 +164,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;
@@ -185,7 +188,7 @@ Writer::fake_write (int frame, Eyes eyes)
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- _sound_asset_writer->write (audio->data(), audio->frames());
+ _sound_mxf_writer->write (audio->data(), audio->frames());
}
/** This must be called from Writer::thread() with an appropriate lock held */
@@ -258,7 +261,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;
@@ -266,14 +269,14 @@ try
}
case QueueItem::FAKE:
_film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame));
- _picture_asset_writer->fake_write (qi.size);
+ _picture_mxf_writer->fake_write (qi.size);
_last_written[qi.eyes].reset ();
++_fake_written;
break;
case QueueItem::REPEAT:
{
_film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame));
- libdcp::FrameInfo fin = _picture_asset_writer->write (
+ dcp::FrameInfo fin = _picture_mxf_writer->write (
_last_written[qi.eyes]->data(),
_last_written[qi.eyes]->size()
);
@@ -373,13 +376,9 @@ Writer::finish ()
terminate_thread (true);
- _picture_asset_writer->finalize ();
- _sound_asset_writer->finalize ();
+ _picture_mxf_writer->finalize ();
+ _sound_mxf_writer->finalize ();
- int const frames = _last_written_frame + 1;
-
- _picture_asset->set_duration (frames);
-
/* Hard-link the video MXF into the DCP */
boost::filesystem::path video_from;
video_from /= _film->internal_video_mxf_dir();
@@ -397,11 +396,6 @@ Writer::finish ()
_film->log()->log ("Hard-link failed; fell back to copying");
}
- /* And update the asset */
-
- _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
- _picture_asset->set_file_name (_film->video_mxf_filename ());
-
/* Move the audio MXF into the DCP */
boost::filesystem::path audio_to;
@@ -415,42 +409,45 @@ Writer::finish ()
);
}
- _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
- _sound_asset->set_duration (frames);
-
- libdcp::DCP dcp (_film->dir (_film->dcp_name()));
+ dcp::DCP dcp (_film->dir (_film->dcp_name()));
- shared_ptr<libdcp::CPL> cpl (
- new libdcp::CPL (
- _film->dir (_film->dcp_name()),
+ shared_ptr<dcp::CPL> cpl (
+ new dcp::CPL (
_film->dcp_name(),
- _film->dcp_content_type()->libdcp_kind (),
- frames,
- _film->video_frame_rate ()
+ _film->dcp_content_type()->libdcp_kind ()
)
);
- dcp.add_cpl (cpl);
+ dcp.add (cpl);
- cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
- _picture_asset,
- _sound_asset,
- shared_ptr<libdcp::SubtitleAsset> ()
- )
- ));
+ 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)));
+ }
+
+ shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
+ if (stereo) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
+ }
+
+ reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+
+ cpl->add (reel);
shared_ptr<Job> job = _job.lock ();
assert (job);
job->sub (_("Computing image digest"));
- _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
job->sub (_("Computing audio digest"));
- _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
- libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
meta.set_issue_date_now ();
- dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
+ dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ());
_film->log()->log (
String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk)
@@ -493,7 +490,7 @@ Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
return false;
}
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
if (info.size == 0) {
_film->log()->log (String::compose ("Existing frame %1 has no info file", f));
diff --git a/src/lib/writer.h b/src/lib/writer.h
index 7af79a417..ca0bdeb92 100644
--- a/src/lib/writer.h
+++ b/src/lib/writer.h
@@ -29,15 +29,15 @@ class EncodedData;
class AudioBuffers;
class Job;
-namespace libdcp {
- class MonoPictureAsset;
- class MonoPictureAssetWriter;
- class StereoPictureAsset;
- class StereoPictureAssetWriter;
- class PictureAsset;
- class PictureAssetWriter;
- class SoundAsset;
- class SoundAssetWriter;
+namespace dcp {
+ class MonoPictureMXF;
+ class MonoPictureMXFWriter;
+ class StereoPictureMXF;
+ class StereoPictureMXFWriter;
+ class PictureMXF;
+ class PictureMXFWriter;
+ class SoundMXF;
+ class SoundMXFWriter;
}
struct QueueItem
@@ -130,8 +130,8 @@ private:
*/
int _pushed_to_disk;
- boost::shared_ptr<libdcp::PictureAsset> _picture_asset;
- boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer;
- boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
- boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+ boost::shared_ptr<dcp::PictureMXF> _picture_mxf;
+ boost::shared_ptr<dcp::PictureMXFWriter> _picture_mxf_writer;
+ boost::shared_ptr<dcp::SoundMXF> _sound_mxf;
+ boost::shared_ptr<dcp::SoundMXFWriter> _sound_mxf_writer;
};
diff --git a/src/lib/wscript b/src/lib/wscript
index 4921afaee..a5b069184 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -41,6 +41,7 @@ sources = """
player.cc
playlist.cc
ratio.cc
+ render_subtitles.cc
resampler.cc
scp_dcp_job.cc
scaler.cc
@@ -50,6 +51,9 @@ sources = """
sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
+ subrip.cc
+ subrip_content.cc
+ subrip_decoder.cc
subtitle_content.cc
subtitle_decoder.cc
timer.cc
@@ -76,7 +80,7 @@ def build(bld):
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
- CURL ZIP QUICKMAIL
+ CURL ZIP QUICKMAIL PANGOMM CAIROMM
"""
obj.source = sources + ' version.cc'