summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/analyse_audio_job.cc16
-rw-r--r--src/lib/analyse_audio_job.h5
-rw-r--r--src/lib/audio_buffers.cc3
-rw-r--r--src/lib/audio_content.cc10
-rw-r--r--src/lib/audio_content.h4
-rw-r--r--src/lib/audio_decoder.cc120
-rw-r--r--src/lib/audio_decoder.h34
-rw-r--r--src/lib/audio_mapping.cc14
-rw-r--r--src/lib/audio_mapping.h6
-rw-r--r--src/lib/audio_merger.h109
-rw-r--r--src/lib/cinema.cc2
-rw-r--r--src/lib/cinema.h6
-rw-r--r--src/lib/colour_conversion.cc4
-rw-r--r--src/lib/config.cc10
-rw-r--r--src/lib/config.h8
-rw-r--r--src/lib/content.cc43
-rw-r--r--src/lib/content.h33
-rw-r--r--src/lib/content_audio.h37
-rw-r--r--src/lib/content_factory.cc8
-rw-r--r--src/lib/content_subtitle.cc36
-rw-r--r--src/lib/content_subtitle.h76
-rw-r--r--src/lib/content_video.h46
-rw-r--r--src/lib/dcp_content_type.cc22
-rw-r--r--src/lib/dcp_content_type.h8
-rw-r--r--src/lib/dcp_video.cc79
-rw-r--r--src/lib/dcp_video.h65
-rw-r--r--src/lib/dcp_video_frame.cc26
-rw-r--r--src/lib/dcp_video_frame.h8
-rw-r--r--src/lib/dcpomatic_time.cc51
-rw-r--r--src/lib/dcpomatic_time.h234
-rw-r--r--src/lib/decoder.h24
-rw-r--r--src/lib/encoder.cc29
-rw-r--r--src/lib/encoder.h8
-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.cc48
-rw-r--r--src/lib/ffmpeg_content.h12
-rw-r--r--src/lib/ffmpeg_decoder.cc347
-rw-r--r--src/lib/ffmpeg_decoder.h26
-rw-r--r--src/lib/ffmpeg_examiner.cc30
-rw-r--r--src/lib/ffmpeg_examiner.h10
-rw-r--r--src/lib/film.cc95
-rw-r--r--src/lib/film.h39
-rw-r--r--src/lib/filter_graph.cc9
-rw-r--r--src/lib/filter_graph.h6
-rw-r--r--src/lib/frame_rate_change.cc91
-rw-r--r--src/lib/frame_rate_change.h58
-rw-r--r--src/lib/image.cc85
-rw-r--r--src/lib/image.h16
-rw-r--r--src/lib/image_content.cc10
-rw-r--r--src/lib/image_content.h4
-rw-r--r--src/lib/image_decoder.cc38
-rw-r--r--src/lib/image_decoder.h11
-rw-r--r--src/lib/image_examiner.cc11
-rw-r--r--src/lib/image_examiner.h8
-rw-r--r--src/lib/job.cc13
-rw-r--r--src/lib/kdm.cc30
-rw-r--r--src/lib/kdm.h12
-rw-r--r--src/lib/piece.cc83
-rw-r--r--src/lib/piece.h36
-rw-r--r--src/lib/player.cc832
-rw-r--r--src/lib/player.h140
-rw-r--r--src/lib/playlist.cc45
-rw-r--r--src/lib/playlist.h6
-rw-r--r--src/lib/position_image.h41
-rw-r--r--src/lib/ratio.cc2
-rw-r--r--src/lib/ratio.h2
-rw-r--r--src/lib/rect.h24
-rw-r--r--src/lib/render_subtitles.cc152
-rw-r--r--src/lib/render_subtitles.h (renamed from src/lib/decoder.cc)24
-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.cc40
-rw-r--r--src/lib/sndfile_content.h27
-rw-r--r--src/lib/sndfile_decoder.cc29
-rw-r--r--src/lib/sndfile_decoder.h13
-rw-r--r--src/lib/subrip.cc237
-rw-r--r--src/lib/subrip.h53
-rw-r--r--src/lib/subrip_content.cc110
-rw-r--r--src/lib/subrip_content.h42
-rw-r--r--src/lib/subrip_decoder.cc75
-rw-r--r--src/lib/subrip_decoder.h41
-rw-r--r--src/lib/subrip_subtitle.h59
-rw-r--r--src/lib/subtitle.cc116
-rw-r--r--src/lib/subtitle.h72
-rw-r--r--src/lib/subtitle_content.cc1
-rw-r--r--src/lib/subtitle_decoder.cc45
-rw-r--r--src/lib/subtitle_decoder.h21
-rw-r--r--src/lib/transcode_job.cc8
-rw-r--r--src/lib/transcoder.cc36
-rw-r--r--src/lib/transcoder.h1
-rw-r--r--src/lib/types.h20
-rw-r--r--src/lib/util.cc151
-rw-r--r--src/lib/util.h43
-rw-r--r--src/lib/video_content.cc50
-rw-r--r--src/lib/video_content.h22
-rw-r--r--src/lib/video_decoder.cc128
-rw-r--r--src/lib/video_decoder.h40
-rw-r--r--src/lib/video_examiner.h6
-rw-r--r--src/lib/writer.cc197
-rw-r--r--src/lib/writer.h47
-rw-r--r--src/lib/wscript13
104 files changed, 3247 insertions, 2109 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
index bfe0ed61f..af58e77ac 100644
--- a/src/lib/analyse_audio_job.cc
+++ b/src/lib/analyse_audio_job.cc
@@ -18,6 +18,7 @@
*/
#include "audio_analysis.h"
+#include "audio_buffers.h"
#include "analyse_audio_job.h"
#include "compose.hpp"
#include "film.h"
@@ -65,19 +66,18 @@ AnalyseAudioJob::run ()
shared_ptr<Playlist> playlist (new Playlist);
playlist->add (content);
shared_ptr<Player> player (new Player (_film, playlist));
- player->disable_video ();
- player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
-
- _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
+ int64_t const len = _film->length().frames (_film->audio_frame_rate());
+ _samples_per_point = max (int64_t (1), len / _num_points);
_current.resize (_film->audio_channels ());
_analysis.reset (new AudioAnalysis (_film->audio_channels ()));
_done = 0;
- OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
- while (!player->pass ()) {
- set_progress (double (_done) / len);
+ DCPTime const block = DCPTime::from_seconds (1.0 / 8);
+ for (DCPTime t; t < _film->length(); t += block) {
+ analyse (player->get_audio (t, block, false));
+ set_progress (t.seconds() / _film->length().seconds());
}
_analysis->write (content->audio_analysis_path ());
@@ -87,7 +87,7 @@ AnalyseAudioJob::run ()
}
void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b)
{
for (int i = 0; i < b->frames(); ++i) {
for (int j = 0; j < b->channels(); ++j) {
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
index 3e376634c..d3c35b67c 100644
--- a/src/lib/analyse_audio_job.h
+++ b/src/lib/analyse_audio_job.h
@@ -20,6 +20,7 @@
#include "job.h"
#include "audio_analysis.h"
#include "types.h"
+#include "dcpomatic_time.h"
class AudioBuffers;
class AudioContent;
@@ -34,10 +35,10 @@ public:
void run ();
private:
- void audio (boost::shared_ptr<const AudioBuffers>, Time);
+ void analyse (boost::shared_ptr<const AudioBuffers>);
boost::weak_ptr<AudioContent> _content;
- OutputAudioFrame _done;
+ int64_t _done;
int64_t _samples_per_point;
std::vector<AudioPoint> _current;
diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc
index a1c9b81ac..8d00fa8ba 100644
--- a/src/lib/audio_buffers.cc
+++ b/src/lib/audio_buffers.cc
@@ -73,6 +73,9 @@ AudioBuffers::~AudioBuffers ()
void
AudioBuffers::allocate (int channels, int frames)
{
+ assert (frames >= 0);
+ assert (channels >= 0);
+
_channels = channels;
_frames = frames;
_allocated_frames = frames;
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
index b96300e15..6da5afa0c 100644
--- a/src/lib/audio_content.cc
+++ b/src/lib/audio_content.cc
@@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
int const AudioContentProperty::AUDIO_DELAY = 204;
int const AudioContentProperty::AUDIO_MAPPING = 205;
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
: Content (f, s)
, _audio_gain (0)
, _audio_delay (Config::instance()->default_audio_delay ())
@@ -149,5 +149,11 @@ AudioContent::audio_analysis_path () const
string
AudioContent::technical_summary () const
{
- return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
+ return String::compose (
+ "audio: channels %1, length %2, raw rate %3, out rate %4",
+ audio_channels(),
+ audio_length().seconds(),
+ content_audio_frame_rate(),
+ output_audio_frame_rate()
+ );
}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
index d30db02d7..cecc8f13d 100644
--- a/src/lib/audio_content.h
+++ b/src/lib/audio_content.h
@@ -43,7 +43,7 @@ class AudioContent : public virtual Content
public:
typedef int64_t Frame;
- AudioContent (boost::shared_ptr<const Film>, Time);
+ AudioContent (boost::shared_ptr<const Film>, DCPTime);
AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -52,7 +52,7 @@ public:
std::string technical_summary () const;
virtual int audio_channels () const = 0;
- virtual AudioContent::Frame audio_length () const = 0;
+ virtual ContentTime audio_length () const = 0;
virtual int content_audio_frame_rate () const = 0;
virtual int output_audio_frame_rate () const = 0;
virtual AudioMapping audio_mapping () const = 0;
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index c0ef02f65..17a534aa4 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,6 +22,8 @@
#include "exceptions.h"
#include "log.h"
#include "resampler.h"
+#include "util.h"
+#include "film.h"
#include "i18n.h"
@@ -29,30 +31,120 @@ using std::stringstream;
using std::list;
using std::pair;
using std::cout;
+using std::min;
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
- : Decoder (film)
- , _audio_content (content)
- , _audio_position (0)
+AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content)
+ : _audio_content (content)
{
+ if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) {
+ _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ()));
+ }
+ reset_decoded_audio ();
}
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::reset_decoded_audio ()
{
- Audio (data, frame);
- _audio_position = frame + data->frames ();
+ _decoded_audio = ContentAudio (shared_ptr<AudioBuffers> (new AudioBuffers (_audio_content->audio_channels(), 0)), 0);
}
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- * The player needs to know that there is no audio otherwise it will keep trying to
- * pass() the decoder to get it to emit audio.
+shared_ptr<ContentAudio>
+AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate)
+{
+ shared_ptr<ContentAudio> dec;
+
+ AudioFrame const end = frame + length - 1;
+
+ if (frame < _decoded_audio.frame || end > (_decoded_audio.frame + length * 4)) {
+ /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+ seek (ContentTime::from_frames (frame, _audio_content->content_audio_frame_rate()), accurate);
+ }
+
+ AudioFrame decoded_offset = 0;
+
+ /* Now enough pass() calls will either:
+ * (a) give us what we want, or
+ * (b) hit the end of the decoder.
+ *
+ * If we are being accurate, we want the right frames,
+ * otherwise any frames will do.
+ */
+ if (accurate) {
+ while (!pass() && _decoded_audio.audio->frames() < length) {}
+ /* Use decoded_offset of 0, as we don't really care what frames we return */
+ } else {
+ while (!pass() && (_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end)) {}
+ decoded_offset = frame - _decoded_audio.frame;
+ }
+
+ AudioFrame const amount_left = _decoded_audio.audio->frames() - decoded_offset;
+
+ AudioFrame const to_return = min (amount_left, length);
+ shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), to_return));
+ out->copy_from (_decoded_audio.audio.get(), to_return, decoded_offset, 0);
+
+ /* Clean up decoded */
+ _decoded_audio.audio->move (decoded_offset + to_return, 0, amount_left - to_return);
+ _decoded_audio.audio->set_frames (amount_left - to_return);
+
+ return shared_ptr<ContentAudio> (new ContentAudio (out, frame));
+}
+
+/** Called by subclasses when audio data is ready.
+ *
+ * Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling.
+ * We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ * data out. Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ * The time is passed in here so that after a seek we can set up our _audio_position. The
+ * time is ignored once this has been done.
*/
-bool
-AudioDecoder::has_audio () const
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+ if (_resampler) {
+ data = _resampler->run (data);
+ }
+
+ if (!_audio_position) {
+ _audio_position = time.frames (_audio_content->output_audio_frame_rate ());
+ }
+
+ assert (_audio_position.get() >= (_decoded_audio.frame + _decoded_audio.audio->frames()));
+
+ /* Resize _decoded_audio to fit the new data */
+ int const new_size = _audio_position.get() + data->frames() - _decoded_audio.frame;
+ _decoded_audio.audio->ensure_size (new_size);
+ _decoded_audio.audio->set_frames (new_size);
+
+ /* Copy new data in */
+ _decoded_audio.audio->copy_from (data.get(), data->frames(), 0, _audio_position.get() - _decoded_audio.frame);
+ _audio_position = _audio_position.get() + data->frames ();
+}
+
+/* XXX: called? */
+void
+AudioDecoder::flush ()
+{
+ 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 ();
+ }
+ */
+}
+
+void
+AudioDecoder::seek (ContentTime, bool)
{
- return _audio_content->audio_channels () > 0;
+ _audio_position.reset ();
+ reset_decoded_audio ();
}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index ab6c4b8a9..0b0d306f6 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -27,8 +27,10 @@
#include "decoder.h"
#include "content.h"
#include "audio_content.h"
+#include "content_audio.h"
class AudioBuffers;
+class Resampler;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
@@ -36,18 +38,32 @@ class AudioBuffers;
class AudioDecoder : public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
-
- bool has_audio () const;
-
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
+ AudioDecoder (boost::shared_ptr<const AudioContent>);
+
+ boost::shared_ptr<const AudioContent> audio_content () const {
+ return _audio_content;
+ }
+ /** Try to fetch some audio from a specific place in this content.
+ * @param frame Frame to start from.
+ * @param length Frames to get.
+ * @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames.
+ * @return Time-stamped audio data which may or may not be from the location (and of the length) requested.
+ */
+ boost::shared_ptr<ContentAudio> get_audio (AudioFrame time, AudioFrame length, bool accurate);
+
protected:
- void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ void seek (ContentTime time, bool accurate);
+ void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+ void flush ();
+ void reset_decoded_audio ();
+
boost::shared_ptr<const AudioContent> _audio_content;
- AudioContent::Frame _audio_position;
+ boost::shared_ptr<Resampler> _resampler;
+ boost::optional<AudioFrame> _audio_position;
+ /** Currently-available decoded audio data */
+ ContentAudio _decoded_audio;
};
#endif
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
index ae7702998..1db827046 100644
--- a/src/lib/audio_mapping.cc
+++ b/src/lib/audio_mapping.cc
@@ -68,11 +68,11 @@ AudioMapping::make_default ()
if (_content_channels == 1) {
/* Mono -> Centre */
- set (0, libdcp::CENTRE, 1);
+ set (0, dcp::CENTRE, 1);
} else {
/* 1:1 mapping */
for (int i = 0; i < _content_channels; ++i) {
- set (i, static_cast<libdcp::Channel> (i), 1);
+ set (i, static_cast<dcp::Channel> (i), 1);
}
}
}
@@ -85,14 +85,14 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version
/* Old-style: on/off mapping */
list<cxml::NodePtr> const c = node->node_children ("Map");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
- set ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
+ set ((*i)->number_child<int> ("ContentIndex"), static_cast<dcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
}
} else {
list<cxml::NodePtr> const c = node->node_children ("Gain");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
set (
(*i)->number_attribute<int> ("Content"),
- static_cast<libdcp::Channel> ((*i)->number_attribute<int> ("DCP")),
+ static_cast<dcp::Channel> ((*i)->number_attribute<int> ("DCP")),
lexical_cast<float> ((*i)->content ())
);
}
@@ -100,13 +100,13 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version
}
void
-AudioMapping::set (int c, libdcp::Channel d, float g)
+AudioMapping::set (int c, dcp::Channel d, float g)
{
_gain[c][d] = g;
}
float
-AudioMapping::get (int c, libdcp::Channel d) const
+AudioMapping::get (int c, dcp::Channel d) const
{
return _gain[c][d];
}
@@ -121,7 +121,7 @@ AudioMapping::as_xml (xmlpp::Node* node) const
xmlpp::Element* t = node->add_child ("Gain");
t->set_attribute ("Content", lexical_cast<string> (c));
t->set_attribute ("DCP", lexical_cast<string> (d));
- t->add_child_text (lexical_cast<string> (get (c, static_cast<libdcp::Channel> (d))));
+ t->add_child_text (lexical_cast<string> (get (c, static_cast<dcp::Channel> (d))));
}
}
}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
index 26087bfff..f3096764c 100644
--- a/src/lib/audio_mapping.h
+++ b/src/lib/audio_mapping.h
@@ -21,7 +21,7 @@
#define DCPOMATIC_AUDIO_MAPPING_H
#include <vector>
-#include <libdcp/types.h>
+#include <dcp/types.h>
#include <boost/shared_ptr.hpp>
namespace xmlpp {
@@ -50,8 +50,8 @@ public:
void make_default ();
- void set (int, libdcp::Channel, float);
- float get (int, libdcp::Channel) const;
+ void set (int, dcp::Channel, float);
+ float get (int, dcp::Channel) const;
int content_channels () const {
return _content_channels;
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
deleted file mode 100644
index 226601e0e..000000000
--- a/src/lib/audio_merger.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "audio_buffers.h"
-#include "util.h"
-
-template <class T, class F>
-class AudioMerger
-{
-public:
- AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t)
- : _buffers (new AudioBuffers (channels, 0))
- , _last_pull (0)
- , _t_to_f (t_to_f)
- , _f_to_t (f_to_t)
- {}
-
- /** Pull audio up to a given time; after this call, no more data can be pushed
- * before the specified time.
- */
- TimedAudioBuffers<T>
- pull (T time)
- {
- TimedAudioBuffers<T> out;
-
- F const to_return = _t_to_f (time - _last_pull);
- out.audio.reset (new AudioBuffers (_buffers->channels(), to_return));
- /* And this is how many we will get from our buffer */
- F const to_return_from_buffers = min (to_return, _buffers->frames ());
-
- /* Copy the data that we have to the back end of the return buffer */
- out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers);
- /* Silence any gap at the start */
- out.audio->make_silent (0, to_return - to_return_from_buffers);
-
- out.time = _last_pull;
- _last_pull = time;
-
- /* And remove the data we're returning from our buffers */
- if (_buffers->frames() > to_return_from_buffers) {
- _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers);
- }
- _buffers->set_frames (_buffers->frames() - to_return_from_buffers);
-
- return out;
- }
-
- void
- push (boost::shared_ptr<const AudioBuffers> audio, T time)
- {
- assert (time >= _last_pull);
-
- F frame = _t_to_f (time);
- F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull));
- _buffers->ensure_size (after);
- _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ());
- _buffers->set_frames (after);
- }
-
- F min (F a, int b)
- {
- if (a < b) {
- return a;
- }
-
- return b;
- }
-
- F max (int a, F b)
- {
- if (a > b) {
- return a;
- }
-
- return b;
- }
-
- TimedAudioBuffers<T>
- flush ()
- {
- if (_buffers->frames() == 0) {
- return TimedAudioBuffers<T> ();
- }
-
- return TimedAudioBuffers<T> (_buffers, _last_pull);
- }
-
-private:
- boost::shared_ptr<AudioBuffers> _buffers;
- T _last_pull;
- boost::function<F (T)> _t_to_f;
- boost::function<T (F)> _f_to_t;
-};
diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc
index fca6b6afd..43a432239 100644
--- a/src/lib/cinema.cc
+++ b/src/lib/cinema.cc
@@ -70,7 +70,7 @@ Cinema::remove_screen (shared_ptr<Screen> s)
Screen::Screen (shared_ptr<const cxml::Node> node)
{
name = node->string_child ("Name");
- certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate")));
+ certificate = shared_ptr<dcp::Certificate> (new dcp::Certificate (node->string_child ("Certificate")));
}
void
diff --git a/src/lib/cinema.h b/src/lib/cinema.h
index 40dc15ae0..d8e28ecfd 100644
--- a/src/lib/cinema.h
+++ b/src/lib/cinema.h
@@ -18,7 +18,7 @@
*/
#include <boost/enable_shared_from_this.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
class Cinema;
@@ -29,7 +29,7 @@ namespace cxml {
class Screen
{
public:
- Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert)
+ Screen (std::string const & n, boost::shared_ptr<dcp::Certificate> cert)
: name (n)
, certificate (cert)
{}
@@ -40,7 +40,7 @@ public:
boost::shared_ptr<Cinema> cinema;
std::string name;
- boost::shared_ptr<libdcp::Certificate> certificate;
+ boost::shared_ptr<dcp::Certificate> certificate;
};
class Cinema : public boost::enable_shared_from_this<Cinema>
diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc
index c3fa05426..e4a2a84bf 100644
--- a/src/lib/colour_conversion.cc
+++ b/src/lib/colour_conversion.cc
@@ -19,7 +19,7 @@
#include <boost/lexical_cast.hpp>
#include <libxml++/libxml++.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include <libcxml/cxml.h>
#include "config.h"
#include "colour_conversion.h"
@@ -43,7 +43,7 @@ ColourConversion::ColourConversion ()
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
- matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+ matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j];
}
}
}
diff --git a/src/lib/config.cc b/src/lib/config.cc
index eda56416d..ca8d0bc53 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -23,7 +23,7 @@
#include <glib.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include <libcxml/cxml.h>
#include "config.h"
#include "server.h"
@@ -79,9 +79,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
@@ -165,7 +165,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 a40e3680a..ee11dcadb 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -28,7 +28,7 @@
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/metadata.h>
+#include <dcp/metadata.h>
#include "dci_metadata.h"
#include "colour_conversion.h"
#include "server.h"
@@ -137,7 +137,7 @@ public:
return _default_dcp_content_type;
}
- libdcp::XMLMetadata dcp_metadata () const {
+ dcp::XMLMetadata dcp_metadata () const {
return _dcp_metadata;
}
@@ -271,7 +271,7 @@ public:
changed ();
}
- void set_dcp_metadata (libdcp::XMLMetadata m) {
+ void set_dcp_metadata (dcp::XMLMetadata m) {
_dcp_metadata = m;
changed ();
}
@@ -375,7 +375,7 @@ private:
int _default_still_length;
Ratio const * _default_container;
DCPContentType const * _default_dcp_content_type;
- libdcp::XMLMetadata _dcp_metadata;
+ dcp::XMLMetadata _dcp_metadata;
int _default_j2k_bandwidth;
int _default_audio_delay;
std::vector<PresetColourConversion> _colour_conversions;
diff --git a/src/lib/content.cc b/src/lib/content.cc
index 829468247..1fb4681a2 100644
--- a/src/lib/content.cc
+++ b/src/lib/content.cc
@@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f)
}
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
: _film (f)
, _position (p)
, _trim_start (0)
@@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
_paths.push_back ((*i)->content ());
}
_digest = node->string_child ("Digest");
- _position = node->number_child<Time> ("Position");
- _trim_start = node->number_child<Time> ("TrimStart");
- _trim_end = node->number_child<Time> ("TrimEnd");
+ _position = DCPTime (node->number_child<double> ("Position"));
+ _trim_start = DCPTime (node->number_child<double> ("TrimStart"));
+ _trim_end = DCPTime (node->number_child<double> ("TrimEnd"));
}
Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -96,11 +96,11 @@ Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
, _change_signals_frequent (false)
{
for (size_t i = 0; i < c.size(); ++i) {
- if (i > 0 && c[i]->trim_start ()) {
+ if (i > 0 && c[i]->trim_start() > DCPTime()) {
throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
}
- if (i < (c.size() - 1) && c[i]->trim_end ()) {
+ if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) {
throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
}
@@ -119,9 +119,9 @@ Content::as_xml (xmlpp::Node* node) const
node->add_child("Path")->add_child_text (i->string ());
}
node->add_child("Digest")->add_child_text (_digest);
- node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
- node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
- node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end));
+ node->add_child("Position")->add_child_text (lexical_cast<string> (_position.get ()));
+ node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start.get ()));
+ node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end.get ()));
}
void
@@ -146,7 +146,7 @@ Content::signal_changed (int p)
}
void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -161,7 +161,7 @@ Content::set_position (Time p)
}
void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -172,7 +172,7 @@ Content::set_trim_start (Time t)
}
void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -204,24 +204,15 @@ Content::clone () const
string
Content::technical_summary () const
{
- return String::compose ("%1 %2 %3", path_summary(), digest(), position());
+ return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
}
-Time
+DCPTime
Content::length_after_trim () const
{
return full_length() - trim_start() - trim_end();
}
-/** @param t A time relative to the start of this content (not the position).
- * @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
- return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
/** @return string which includes everything about how this content affects
* its playlist.
*/
@@ -231,9 +222,9 @@ Content::identifier () const
stringstream s;
s << Content::digest()
- << "_" << position()
- << "_" << trim_start()
- << "_" << trim_end();
+ << "_" << position().get()
+ << "_" << trim_start().get()
+ << "_" << trim_end().get();
return s.str ();
}
diff --git a/src/lib/content.h b/src/lib/content.h
index 596a0a905..fc3a531fa 100644
--- a/src/lib/content.h
+++ b/src/lib/content.h
@@ -27,6 +27,7 @@
#include <boost/enable_shared_from_this.hpp>
#include <libxml++/libxml++.h>
#include "types.h"
+#include "dcpomatic_time.h"
namespace cxml {
class Node;
@@ -49,7 +50,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no
{
public:
Content (boost::shared_ptr<const Film>);
- Content (boost::shared_ptr<const Film>, Time);
+ Content (boost::shared_ptr<const Film>, DCPTime);
Content (boost::shared_ptr<const Film>, boost::filesystem::path);
Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -63,7 +64,7 @@ public:
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
- virtual Time full_length () const = 0;
+ virtual DCPTime full_length () const = 0;
virtual std::string identifier () const;
boost::shared_ptr<Content> clone () const;
@@ -95,42 +96,40 @@ public:
return _digest;
}
- void set_position (Time);
+ void set_position (DCPTime);
- /** Time that this content starts; i.e. the time that the first
+ /** DCPTime that this content starts; i.e. the time that the first
* bit of the content (trimmed or not) will happen.
*/
- Time position () const {
+ DCPTime position () const {
boost::mutex::scoped_lock lm (_mutex);
return _position;
}
- void set_trim_start (Time);
+ void set_trim_start (DCPTime);
- Time trim_start () const {
+ DCPTime trim_start () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_start;
}
- void set_trim_end (Time);
+ void set_trim_end (DCPTime);
- Time trim_end () const {
+ DCPTime trim_end () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_end;
}
- Time end () const {
- return position() + length_after_trim() - 1;
+ DCPTime end () const {
+ return position() + length_after_trim();
}
- Time length_after_trim () const;
+ DCPTime length_after_trim () const;
void set_change_signals_frequent (bool f) {
_change_signals_frequent = f;
}
- bool trimmed (Time) const;
-
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
protected:
@@ -148,9 +147,9 @@ protected:
private:
std::string _digest;
- Time _position;
- Time _trim_start;
- Time _trim_end;
+ DCPTime _position;
+ DCPTime _trim_start;
+ DCPTime _trim_end;
bool _change_signals_frequent;
};
diff --git a/src/lib/content_audio.h b/src/lib/content_audio.h
new file mode 100644
index 000000000..6ee702f2c
--- /dev/null
+++ b/src/lib/content_audio.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_buffers.h"
+
+class ContentAudio
+{
+public:
+ ContentAudio ()
+ : audio (new AudioBuffers (0, 0))
+ , frame (0)
+ {}
+
+ ContentAudio (boost::shared_ptr<AudioBuffers> a, AudioFrame f)
+ : audio (a)
+ , frame (f)
+ {}
+
+ boost::shared_ptr<AudioBuffers> audio;
+ AudioFrame frame;
+};
diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc
index 98b1dd859..092efd790 100644
--- a/src/lib/content_factory.cc
+++ b/src/lib/content_factory.cc
@@ -21,6 +21,7 @@
#include "ffmpeg_content.h"
#include "image_content.h"
#include "sndfile_content.h"
+#include "subrip_content.h"
#include "util.h"
using std::string;
@@ -40,6 +41,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, l
content.reset (new ImageContent (film, node, version));
} else if (type == "Sndfile") {
content.reset (new SndfileContent (film, node, version));
+ } else if (type == "SubRip") {
+ content.reset (new SubRipContent (film, node, version));
}
return content;
@@ -49,11 +52,16 @@ shared_ptr<Content>
content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
{
shared_ptr<Content> content;
+
+ string ext = path.extension().string ();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
if (valid_image_file (path)) {
content.reset (new ImageContent (film, path));
} else if (SndfileContent::valid_file (path)) {
content.reset (new SndfileContent (film, path));
+ } else if (ext == ".srt") {
+ content.reset (new SubRipContent (film, path));
} else {
content.reset (new FFmpegContent (film, path));
}
diff --git a/src/lib/content_subtitle.cc b/src/lib/content_subtitle.cc
new file mode 100644
index 000000000..11873afee
--- /dev/null
+++ b/src/lib/content_subtitle.cc
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "content_subtitle.h"
+
+ContentTime
+ContentTextSubtitle::from () const
+{
+ /* XXX: assuming we have some subs and they are all at the same time */
+ assert (!subs.empty ());
+ return ContentTime::from_seconds (double (subs.front().in().to_ticks()) / 250);
+}
+
+ContentTime
+ContentTextSubtitle::to () const
+{
+ /* XXX: assuming we have some subs and they are all at the same time */
+ assert (!subs.empty ());
+ return ContentTime::from_seconds (double (subs.front().out().to_ticks()) / 250);
+}
diff --git a/src/lib/content_subtitle.h b/src/lib/content_subtitle.h
new file mode 100644
index 000000000..8c266f483
--- /dev/null
+++ b/src/lib/content_subtitle.h
@@ -0,0 +1,76 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_SUBTITLE_H
+#define DCPOMATIC_CONTENT_SUBTITLE_H
+
+#include <list>
+#include <dcp/subtitle_string.h>
+#include "dcpomatic_time.h"
+#include "rect.h"
+
+class Image;
+
+class ContentSubtitle
+{
+public:
+ virtual ContentTime from () const = 0;
+ virtual ContentTime to () const = 0;
+};
+
+class ContentImageSubtitle : public ContentSubtitle
+{
+public:
+ ContentImageSubtitle (ContentTime f, ContentTime t, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r)
+ : image (im)
+ , rectangle (r)
+ , _from (f)
+ , _to (t)
+ {}
+
+ ContentTime from () const {
+ return _from;
+ }
+
+ ContentTime to () const {
+ return _to;
+ }
+
+ boost::shared_ptr<Image> image;
+ dcpomatic::Rect<double> rectangle;
+
+private:
+ ContentTime _from;
+ ContentTime _to;
+};
+
+class ContentTextSubtitle : public ContentSubtitle
+{
+public:
+ ContentTextSubtitle (std::list<dcp::SubtitleString> s)
+ : subs (s)
+ {}
+
+ ContentTime from () const;
+ ContentTime to () const;
+
+ std::list<dcp::SubtitleString> subs;
+};
+
+#endif
diff --git a/src/lib/content_video.h b/src/lib/content_video.h
new file mode 100644
index 000000000..20b5b8dec
--- /dev/null
+++ b/src/lib/content_video.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_VIDEO_H
+#define DCPOMATIC_CONTENT_VIDEO_H
+
+class Image;
+
+/** @class ContentVideo
+ * @brief A frame of video straight out of some content.
+ */
+class ContentVideo
+{
+public:
+ ContentVideo ()
+ : eyes (EYES_BOTH)
+ {}
+
+ ContentVideo (boost::shared_ptr<const Image> i, Eyes e, VideoFrame f)
+ : image (i)
+ , eyes (e)
+ , frame (f)
+ {}
+
+ boost::shared_ptr<const Image> image;
+ Eyes eyes;
+ VideoFrame frame;
+};
+
+#endif
diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc
index 82bd5fa01..b3a45e40e 100644
--- a/src/lib/dcp_content_type.cc
+++ b/src/lib/dcp_content_type.cc
@@ -30,7 +30,7 @@ using namespace std;
vector<DCPContentType const *> DCPContentType::_dcp_content_types;
-DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
+DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d)
: _pretty_name (p)
, _libdcp_kind (k)
, _dci_name (d)
@@ -41,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
void
DCPContentType::setup_dcp_content_types ()
{
- _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
- _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
- _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
- _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
- _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
- _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
- _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
- _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
- _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
- _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
+ _dcp_content_types.push_back (new DCPContentType (_("Feature"), dcp::FEATURE, N_("FTR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Short"), dcp::SHORT, N_("SHR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Trailer"), dcp::TRAILER, N_("TLR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Test"), dcp::TEST, N_("TST")));
+ _dcp_content_types.push_back (new DCPContentType (_("Transitional"), dcp::TRANSITIONAL, N_("XSN")));
+ _dcp_content_types.push_back (new DCPContentType (_("Rating"), dcp::RATING, N_("RTG")));
+ _dcp_content_types.push_back (new DCPContentType (_("Teaser"), dcp::TEASER, N_("TSR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Policy"), dcp::POLICY, N_("POL")));
+ _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), dcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
+ _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), dcp::ADVERTISEMENT, N_("ADV")));
}
DCPContentType const *
diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h
index 965c16347..05f30af55 100644
--- a/src/lib/dcp_content_type.h
+++ b/src/lib/dcp_content_type.h
@@ -26,7 +26,7 @@
#include <string>
#include <vector>
-#include <libdcp/dcp.h>
+#include <dcp/dcp.h>
/** @class DCPContentType
* @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
@@ -34,14 +34,14 @@
class DCPContentType : public boost::noncopyable
{
public:
- DCPContentType (std::string, libdcp::ContentKind, std::string);
+ DCPContentType (std::string, dcp::ContentKind, std::string);
/** @return user-visible `pretty' name */
std::string pretty_name () const {
return _pretty_name;
}
- libdcp::ContentKind libdcp_kind () const {
+ dcp::ContentKind libdcp_kind () const {
return _libdcp_kind;
}
@@ -58,7 +58,7 @@ public:
private:
std::string _pretty_name;
- libdcp::ContentKind _libdcp_kind;
+ dcp::ContentKind _libdcp_kind;
std::string _dci_name;
/** All available DCP content types */
diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc
new file mode 100644
index 000000000..8ea5cec88
--- /dev/null
+++ b/src/lib/dcp_video.cc
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "dcp_video.h"
+#include "image.h"
+
+using boost::shared_ptr;
+
+/** From ContentVideo:
+ * @param in Image.
+ * @param eyes Eye(s) that the Image is for.
+ *
+ * From Content:
+ * @param crop Crop to apply.
+ * @param inter_size
+ * @param out_size
+ * @param scaler Scaler to use.
+ * @param conversion Colour conversion to use.
+ *
+ * @param time DCP time.
+ */
+DCPVideo::DCPVideo (
+ shared_ptr<const Image> in,
+ Eyes eyes,
+ Crop crop,
+ dcp::Size inter_size,
+ dcp::Size out_size,
+ Scaler const * scaler,
+ ColourConversion conversion,
+ DCPTime time
+ )
+ : _in (in)
+ , _eyes (eyes)
+ , _crop (crop)
+ , _inter_size (inter_size)
+ , _out_size (out_size)
+ , _scaler (scaler)
+ , _conversion (conversion)
+ , _time (time)
+{
+
+}
+
+void
+DCPVideo::set_subtitle (PositionImage s)
+{
+ _subtitle = s;
+}
+
+shared_ptr<Image>
+DCPVideo::image (AVPixelFormat format, bool aligned) const
+{
+ 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) {
+ out->alpha_blend (_subtitle.image, _subtitle.position);
+ }
+
+ return out;
+}
+
diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h
new file mode 100644
index 000000000..75823f31d
--- /dev/null
+++ b/src/lib/dcp_video.h
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern "C" {
+#include <libavutil/avutil.h>
+}
+#include <boost/shared_ptr.hpp>
+#include "types.h"
+#include "colour_conversion.h"
+#include "position.h"
+#include "position_image.h"
+
+class Image;
+class Scaler;
+
+/** @class DCPVideo
+ *
+ * A ContentVideo image with:
+ * - content parameters (crop, scaling, colour conversion)
+ * - merged content (subtitles)
+ * and with its time converted from a ContentTime to a DCPTime.
+ */
+class DCPVideo
+{
+public:
+ DCPVideo (boost::shared_ptr<const Image>, Eyes eyes, Crop, dcp::Size, dcp::Size, Scaler const *, ColourConversion conversion, DCPTime time);
+
+ void set_subtitle (PositionImage);
+ boost::shared_ptr<Image> image (AVPixelFormat, bool) const;
+
+ Eyes eyes () const {
+ return _eyes;
+ }
+
+ ColourConversion conversion () const {
+ return _conversion;
+ }
+
+private:
+ boost::shared_ptr<const Image> _in;
+ Eyes _eyes;
+ Crop _crop;
+ dcp::Size _inter_size;
+ dcp::Size _out_size;
+ Scaler const * _scaler;
+ ColourConversion _conversion;
+ DCPTime _time;
+ PositionImage _subtitle;
+};
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index 54531a0f9..2b7a2e18f 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -43,12 +43,10 @@
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
-#include <libdcp/rec709_linearised_gamma_lut.h>
-#include <libdcp/srgb_linearised_gamma_lut.h>
-#include <libdcp/gamma_lut.h>
-#include <libdcp/xyz_frame.h>
-#include <libdcp/rgb_xyz.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/gamma_lut.h>
+#include <dcp/xyz_frame.h>
+#include <dcp/rgb_xyz.h>
+#include <dcp/colour_matrix.h>
#include <libcxml/cxml.h>
#include "film.h"
#include "dcp_video_frame.h"
@@ -68,7 +66,7 @@ using std::stringstream;
using std::cout;
using boost::shared_ptr;
using boost::lexical_cast;
-using libdcp::Size;
+using dcp::Size;
#define DCI_COEFFICENT (48.0 / 52.37)
@@ -120,12 +118,8 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const Image> image, shared_ptr<const cx
shared_ptr<EncodedData>
DCPVideoFrame::encode_locally ()
{
- shared_ptr<libdcp::LUT> in_lut;
- if (_conversion.input_gamma_linearised) {
- in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma);
- } else {
- in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma);
- }
+ shared_ptr<dcp::GammaLUT> in_lut;
+ in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised);
/* XXX: libdcp should probably use boost */
@@ -136,10 +130,10 @@ DCPVideoFrame::encode_locally ()
}
}
- shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+ shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
_image,
in_lut,
- libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma),
+ dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false),
matrix
);
@@ -392,7 +386,7 @@ EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const
}
void
-EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const
{
boost::filesystem::path const info = film->info_path (frame, eyes);
FILE* h = fopen_boost (info, "w");
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 40f758c74..0a8b5f287 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
Taken from code Copyright (C) 2010-2011 Terrence Meiczinger
This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,7 @@
*/
-#include <openjpeg.h>
-#include <libdcp/picture_asset.h>
-#include <libdcp/picture_asset_writer.h>
+#include <dcp/picture_mxf_writer.h>
#include "util.h"
/** @file src/dcp_video_frame.h
@@ -49,7 +47,7 @@ public:
void send (boost::shared_ptr<Socket> socket);
void write (boost::shared_ptr<const Film>, int, Eyes) const;
- void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
+ void write_info (boost::shared_ptr<const Film>, int, Eyes, dcp::FrameInfo) const;
/** @return data */
uint8_t* data () const {
diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc
new file mode 100644
index 000000000..98888646d
--- /dev/null
+++ b/src/lib/dcpomatic_time.cc
@@ -0,0 +1,51 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "dcpomatic_time.h"
+
+using std::ostream;
+
+ContentTime::ContentTime (DCPTime d, FrameRateChange f)
+ : Time (rint (d.get() * f.speed_up))
+{
+
+}
+
+DCPTime min (DCPTime a, DCPTime b)
+{
+ if (a < b) {
+ return a;
+ }
+
+ return b;
+}
+
+ostream &
+operator<< (ostream& s, ContentTime t)
+{
+ s << "[CONT " << t.get() << " " << t.seconds() << "s]";
+ return s;
+}
+
+ostream &
+operator<< (ostream& s, DCPTime t)
+{
+ s << "[DCP " << t.get() << " " << t.seconds() << "s]";
+ return s;
+}
diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h
new file mode 100644
index 000000000..76fbe6902
--- /dev/null
+++ b/src/lib/dcpomatic_time.h
@@ -0,0 +1,234 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_TIME_H
+#define DCPOMATIC_TIME_H
+
+#include <cmath>
+#include <ostream>
+#include <stdint.h>
+#include "frame_rate_change.h"
+
+class dcpomatic_round_up_test;
+
+class Time;
+
+/** A time in seconds, expressed as a number scaled up by Time::HZ. */
+class Time
+{
+public:
+ Time ()
+ : _t (0)
+ {}
+
+ explicit Time (int64_t t)
+ : _t (t)
+ {}
+
+ virtual ~Time () {}
+
+ int64_t get () const {
+ return _t;
+ }
+
+ double seconds () const {
+ return double (_t) / HZ;
+ }
+
+ template <typename T>
+ int64_t frames (T r) const {
+ return rint (_t * r / HZ);
+ }
+
+protected:
+ friend class dcptime_round_up_test;
+
+ int64_t _t;
+ static const int HZ = 96000;
+};
+
+class DCPTime;
+
+class ContentTime : public Time
+{
+public:
+ ContentTime () : Time () {}
+ explicit ContentTime (int64_t t) : Time (t) {}
+ ContentTime (int64_t n, int64_t d) : Time (n * HZ / d) {}
+ ContentTime (DCPTime d, FrameRateChange f);
+
+ bool operator< (ContentTime const & o) const {
+ return _t < o._t;
+ }
+
+ bool operator<= (ContentTime const & o) const {
+ return _t <= o._t;
+ }
+
+ bool operator== (ContentTime const & o) const {
+ return _t == o._t;
+ }
+
+ bool operator!= (ContentTime const & o) const {
+ return _t != o._t;
+ }
+
+ bool operator>= (ContentTime const & o) const {
+ return _t >= o._t;
+ }
+
+ bool operator> (ContentTime const & o) const {
+ return _t > o._t;
+ }
+
+ ContentTime operator+ (ContentTime const & o) const {
+ return ContentTime (_t + o._t);
+ }
+
+ ContentTime & operator+= (ContentTime const & o) {
+ _t += o._t;
+ return *this;
+ }
+
+ ContentTime operator- () const {
+ return ContentTime (-_t);
+ }
+
+ ContentTime operator- (ContentTime const & o) const {
+ return ContentTime (_t - o._t);
+ }
+
+ ContentTime & operator-= (ContentTime const & o) {
+ _t -= o._t;
+ return *this;
+ }
+
+ /** Round up to the nearest sampling interval
+ * at some sampling rate.
+ * @param r Sampling rate.
+ */
+ ContentTime round_up (float r) {
+ int64_t const n = rint (HZ / r);
+ int64_t const a = _t + n - 1;
+ return ContentTime (a - (a % n));
+ }
+
+ static ContentTime from_seconds (double s) {
+ return ContentTime (s * HZ);
+ }
+
+ template <class T>
+ static ContentTime from_frames (int64_t f, T r) {
+ assert (r > 0);
+ return ContentTime (f * HZ / r);
+ }
+
+ static ContentTime max () {
+ return ContentTime (INT64_MAX);
+ }
+};
+
+std::ostream& operator<< (std::ostream& s, ContentTime t);
+
+class DCPTime : public Time
+{
+public:
+ DCPTime () : Time () {}
+ explicit DCPTime (int64_t t) : Time (t) {}
+ DCPTime (ContentTime t, FrameRateChange c) : Time (rint (t.get() / c.speed_up)) {}
+
+ bool operator< (DCPTime const & o) const {
+ return _t < o._t;
+ }
+
+ bool operator<= (DCPTime const & o) const {
+ return _t <= o._t;
+ }
+
+ bool operator== (DCPTime const & o) const {
+ return _t == o._t;
+ }
+
+ bool operator!= (DCPTime const & o) const {
+ return _t != o._t;
+ }
+
+ bool operator>= (DCPTime const & o) const {
+ return _t >= o._t;
+ }
+
+ bool operator> (DCPTime const & o) const {
+ return _t > o._t;
+ }
+
+ DCPTime operator+ (DCPTime const & o) const {
+ return DCPTime (_t + o._t);
+ }
+
+ DCPTime & operator+= (DCPTime const & o) {
+ _t += o._t;
+ return *this;
+ }
+
+ DCPTime operator- (DCPTime const & o) const {
+ return DCPTime (_t - o._t);
+ }
+
+ DCPTime & operator-= (DCPTime const & o) {
+ _t -= o._t;
+ return *this;
+ }
+
+ /** Round up to the nearest sampling interval
+ * at some sampling rate.
+ * @param r Sampling rate.
+ */
+ DCPTime round_up (float r) {
+ int64_t const n = rint (HZ / r);
+ int64_t const a = _t + n - 1;
+ return DCPTime (a - (a % n));
+ }
+
+ DCPTime abs () const {
+ return DCPTime (std::abs (_t));
+ }
+
+ static DCPTime from_seconds (double s) {
+ return DCPTime (s * HZ);
+ }
+
+ template <class T>
+ static DCPTime from_frames (int64_t f, T r) {
+ assert (r > 0);
+ return DCPTime (f * HZ / r);
+ }
+
+ static DCPTime delta () {
+ return DCPTime (1);
+ }
+
+ static DCPTime max () {
+ return DCPTime (INT64_MAX);
+ }
+};
+
+DCPTime min (DCPTime a, DCPTime b);
+std::ostream& operator<< (std::ostream& s, DCPTime t);
+
+#endif
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index d67592ed8..38556c818 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -27,7 +27,10 @@
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
+#include "types.h"
+#include "dcpomatic_time.h"
+class Decoded;
class Film;
/** @class Decoder.
@@ -36,21 +39,18 @@ class Film;
class Decoder : public boost::noncopyable
{
public:
- Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- /** Perform one decode pass of the content, which may or may not
- * cause the object to emit some data.
+protected:
+ /** Seek so that the next pass() will yield the next thing
+ * (video/sound frame, subtitle etc.) at or after the requested
+ * time. Pass accurate = true to try harder to get close to
+ * the request. Note that seeking to time t may mean that
+ * the next pass() yields, for example, audio at time t and then
+ * video before it.
*/
- virtual void pass () = 0;
- virtual bool done () const = 0;
-
-protected:
-
- virtual void flush () {};
-
- /** The Film that we are decoding in */
- boost::weak_ptr<const Film> _film;
+ virtual void seek (ContentTime time, bool accurate) = 0;
+ virtual bool pass () = 0;
};
#endif
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 8e8da6229..b83cbc10a 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@
#include "writer.h"
#include "server_finder.h"
#include "player.h"
+#include "dcp_video.h"
#include "i18n.h"
@@ -60,9 +61,7 @@ Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j)
, _video_frames_out (0)
, _terminate (false)
{
- _have_a_real_frame[EYES_BOTH] = false;
- _have_a_real_frame[EYES_LEFT] = false;
- _have_a_real_frame[EYES_RIGHT] = false;
+
}
Encoder::~Encoder ()
@@ -179,7 +178,7 @@ Encoder::frame_done ()
}
void
-Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, bool same)
+Encoder::process_video (shared_ptr<DCPVideo> frame)
{
_waker.nudge ();
@@ -206,28 +205,28 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
rethrow ();
if (_writer->can_fake_write (_video_frames_out)) {
- _writer->fake_write (_video_frames_out, eyes);
- _have_a_real_frame[eyes] = false;
- frame_done ();
- } else if (same && _have_a_real_frame[eyes]) {
- /* Use the last frame that we encoded. */
- _writer->repeat (_video_frames_out, eyes);
+ _writer->fake_write (_video_frames_out, frame->eyes ());
frame_done ();
} else {
/* Queue this new frame for encoding */
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(),
- _film->j2k_bandwidth(), _film->resolution(), _film->log()
+ frame->image(PIX_FMT_RGB24, false),
+ _video_frames_out,
+ frame->eyes(),
+ frame->conversion(),
+ _film->video_frame_rate(),
+ _film->j2k_bandwidth(),
+ _film->resolution(),
+ _film->log()
)
));
_condition.notify_all ();
- _have_a_real_frame[eyes] = true;
}
- if (eyes != EYES_LEFT) {
+ if (frame->eyes() != EYES_LEFT) {
++_video_frames_out;
}
}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index e0ee2d414..6c465f816 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -48,7 +48,7 @@ class EncodedData;
class Writer;
class Job;
class ServerFinder;
-class PlayerImage;
+class DCPVideo;
/** @class Encoder
* @brief Encoder to J2K and WAV for DCP.
@@ -67,10 +67,9 @@ public:
void process_begin ();
/** Call with a frame of video.
- * @param i Video frame image.
- * @param same true if i is the same as the last time we were called.
+ * @param f Video frame.
*/
- void process_video (boost::shared_ptr<PlayerImage> i, Eyes eyes, ColourConversion, bool same);
+ void process_video (boost::shared_ptr<DCPVideo> f);
/** Call with some audio data */
void process_audio (boost::shared_ptr<const AudioBuffers>);
@@ -106,7 +105,6 @@ private:
/** Number of video frames written for the DCP so far */
int _video_frames_out;
- bool _have_a_real_frame[EYES_COUNT];
bool _terminate;
std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
std::list<boost::thread *> _threads;
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 60eea6ec7..a98aa9828 100644
--- a/src/lib/ffmpeg.cc
+++ b/src/lib/ffmpeg.cc
@@ -192,6 +192,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 b39113fd1..447f3631c 100644
--- a/src/lib/ffmpeg_content.cc
+++ b/src/lib/ffmpeg_content.cc
@@ -152,7 +152,7 @@ FFmpegContent::as_xml (xmlpp::Node* node) const
}
if (_first_video) {
- node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+ node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get().get()));
}
}
@@ -163,14 +163,14 @@ FFmpegContent::examine (shared_ptr<Job> job)
Content::examine (job);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
+ take_from_video_examiner (examiner);
- VideoContent::Frame video_length = 0;
- video_length = examiner->video_length ();
- film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
+ ContentTime video_length = examiner->video_length ();
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ())));
{
boost::mutex::scoped_lock lm (_mutex);
@@ -190,8 +190,6 @@ FFmpegContent::examine (shared_ptr<Job> job)
_first_video = examiner->first_video ();
}
- take_from_video_examiner (examiner);
-
signal_changed (ContentProperty::LENGTH);
signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
@@ -233,13 +231,13 @@ FFmpegContent::technical_summary () const
string
FFmpegContent::information () const
{
- if (video_length() == 0 || video_frame_rate() == 0) {
+ if (video_length() == ContentTime (0) || video_frame_rate() == 0) {
return "";
}
stringstream s;
- s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
s << VideoContent::information ();
return s.str ();
@@ -267,19 +265,14 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
signal_changed (FFmpegContentProperty::AUDIO_STREAM);
}
-AudioContent::Frame
+ContentTime
FFmpegContent::audio_length () const
{
- int const cafr = content_audio_frame_rate ();
- float const vfr = video_frame_rate ();
- VideoContent::Frame const vl = video_length_after_3d_combine ();
-
- boost::mutex::scoped_lock lm (_mutex);
- if (!_audio_stream) {
- return 0;
+ if (!audio_stream ()) {
+ return ContentTime ();
}
-
- return video_frames_to_audio_frames (vl, cafr, vfr);
+
+ return video_length ();
}
int
@@ -315,16 +308,15 @@ FFmpegContent::output_audio_frame_rate () const
/* Resample to a DCI-approved sample rate */
double t = dcp_audio_frame_rate (content_audio_frame_rate ());
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
/* Compensate if the DCP is being run at a different frame rate
to the source; that is, if the video is run such that it will
look different in the DCP compared to the source (slower or faster).
- skip/repeat doesn't come into effect here.
*/
if (frc.change_speed) {
- t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+ t /= frc.speed_up;
}
return rint (t);
@@ -372,7 +364,7 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const
root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
if (first_audio) {
- root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get ()));
+ root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get().get()));
}
mapping.as_xml (root->add_child("Mapping"));
}
@@ -422,14 +414,12 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
FFmpegStream::as_xml (root);
}
-Time
+DCPTime
FFmpegContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
- return video_length_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate ();
+ return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
}
AudioMapping
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
index 6ab95d2fe..d86c90af9 100644
--- a/src/lib/ffmpeg_content.h
+++ b/src/lib/ffmpeg_content.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -87,7 +87,7 @@ public:
int frame_rate;
int channels;
AudioMapping mapping;
- boost::optional<double> first_audio;
+ boost::optional<ContentTime> first_audio;
private:
friend class ffmpeg_pts_offset_test;
@@ -139,13 +139,13 @@ public:
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
/* AudioContent */
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ ContentTime audio_length () const;
int content_audio_frame_rate () const;
int output_audio_frame_rate () const;
AudioMapping audio_mapping () const;
@@ -182,7 +182,7 @@ public:
void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
- boost::optional<double> first_video () const {
+ boost::optional<ContentTime> first_video () const {
boost::mutex::scoped_lock lm (_mutex);
return _first_video;
}
@@ -194,7 +194,7 @@ private:
boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
boost::shared_ptr<FFmpegAudioStream> _audio_stream;
- boost::optional<double> _first_video;
+ boost::optional<ContentTime> _first_video;
/** Video filters that should be used when generating DCPs */
std::vector<Filter const *> _filters;
};
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 1920f9275..0a4624569 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,7 +33,6 @@ extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
-#include "film.h"
#include "filter.h"
#include "exceptions.h"
#include "image.h"
@@ -56,20 +55,15 @@ using std::pair;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-using libdcp::Size;
+using dcp::Size;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
- : Decoder (f)
- , VideoDecoder (f, c)
- , AudioDecoder (f, c)
- , SubtitleDecoder (f)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log)
+ : VideoDecoder (c)
+ , AudioDecoder (c)
, FFmpeg (c)
+ , _log (log)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
- , _decode_video (video)
- , _decode_audio (audio)
- , _pts_offset (0)
- , _just_sought (false)
{
setup_subtitle ();
@@ -82,13 +76,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
Then we remove big initial gaps in PTS and we allow our
insertion of black frames to work.
- We will do:
- audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
- video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+ We will do pts_to_use = pts_from_ffmpeg + pts_offset;
*/
- bool const have_video = video && c->first_video();
- bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+ bool const have_video = c->first_video();
+ bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
/* First, make one of them start at 0 */
@@ -102,15 +94,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
/* Now adjust both so that the video pts starts on a frame */
if (have_video && have_audio) {
- double first_video = c->first_video().get() + _pts_offset;
- double const old_first_video = first_video;
-
- /* Round the first video up to a frame boundary */
- if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
- first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
- }
-
- _pts_offset += first_video - old_first_video;
+ ContentTime first_video = c->first_video().get() + _pts_offset;
+ ContentTime const old_first_video = first_video;
+ _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
}
}
@@ -133,20 +119,15 @@ FFmpegDecoder::flush ()
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
- if (_decode_video) {
- while (decode_video_packet ()) {}
- }
+ while (decode_video_packet ()) {}
- if (_ffmpeg_content->audio_stream() && _decode_audio) {
+ if (_ffmpeg_content->audio_stream()) {
decode_audio_packet ();
+ AudioDecoder::flush ();
}
-
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length_after_3d_combine ();
- _audio_position = _ffmpeg_content->audio_length ();
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
@@ -156,29 +137,25 @@ FFmpegDecoder::pass ()
/* Maybe we should fail here, but for now we'll just finish off instead */
char buf[256];
av_strerror (r, buf, sizeof(buf));
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
+ _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
}
flush ();
- return;
+ return true;
}
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
int const si = _packet.stream_index;
- if (si == _video_stream && _decode_video) {
+ if (si == _video_stream) {
decode_video_packet ();
- } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
decode_audio_packet ();
- } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
+ } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
decode_subtitle_packet ();
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
@@ -310,77 +287,131 @@ FFmpegDecoder::bytes_per_audio_sample () const
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+ optional<ContentTime> last_video;
+ optional<ContentTime> last_audio;
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
- */
- int initial = frame;
+ while (!finished (last_video, last_audio, frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ /* We should flush our decoders here, possibly yielding a few more frames,
+ but the consequence of having to do that is too hideous to contemplate.
+ Instead we give up and say that you can't seek too close to the end
+ of a file.
+ */
+ return frames_read;
+ }
+
+ ++frames_read;
+
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+ if (_packet.stream_index == _video_stream) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int got_picture = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
+ if (r >= 0 && got_picture) {
+ last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+ }
+
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
+ AVPacket copy_packet = _packet;
+ while (copy_packet.size > 0) {
- if (accurate) {
- initial -= 5;
+ int got_frame;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
+ if (r >= 0 && got_frame) {
+ last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- if (initial < 0) {
- initial = 0;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+ return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+ ContentTime const u = t - _pts_offset;
+ int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ if (_ffmpeg_content->audio_stream ()) {
+ s = min (
+ s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+ );
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+ /* Ridiculous empirical hack */
+ s--;
+ if (s < 0) {
+ s = 0;
+ }
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ av_seek_frame (_format_context, _video_stream, s, 0);
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+}
- /* This !accurate is piling hack upon hack; setting _just_sought to true
- even with accurate == true defeats our attempt to align the start
- of the video and audio. Here we disable that defeat when accurate == true
- i.e. when we are making a DCP rather than just previewing one.
- Ewww. This should be gone in 2.0.
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ VideoDecoder::seek (time, accurate);
+ AudioDecoder::seek (time, accurate);
+
+ /* If we are doing an accurate seek, our initial shot will be 2s (2 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 ? ContentTime::from_seconds (2) : ContentTime (0);
+ ContentTime initial_seek = time - pre_roll;
+ if (initial_seek < ContentTime (0)) {
+ initial_seek = ContentTime (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));
}
}
@@ -397,39 +428,23 @@ 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);
- film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+ _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
return;
}
if (frame_finished) {
-
- if (_audio_position == 0) {
- /* Where we are in the source, in seconds */
- double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
- if (pts > 0) {
- /* Emit some silence */
- shared_ptr<AudioBuffers> silence (
- new AudioBuffers (
- _ffmpeg_content->audio_channels(),
- pts * _ffmpeg_content->content_audio_frame_rate()
- )
- );
-
- silence->make_silent ();
- audio (silence, _audio_position);
- }
- }
+ ContentTime const ct = ContentTime::from_seconds (
+ av_frame_get_best_effort_timestamp (_frame) *
+ av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+ + _pts_offset;
int const data_size = av_samples_get_buffer_size (
0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
-
- audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+
+ audio (deinterleave_audio (_frame->data, data_size), ct);
}
copy_packet.data += decode_result;
@@ -450,18 +465,14 @@ FFmpegDecoder::decode_video_packet ()
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+ while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
++i;
}
if (i == _filter_graphs.end ()) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
_filter_graphs.push_back (graph);
-
- film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+ _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
} else {
graph = *i;
}
@@ -473,49 +484,10 @@ FFmpegDecoder::decode_video_packet ()
shared_ptr<Image> image = i->first;
if (i->second != AV_NOPTS_VALUE) {
-
- double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
- if (_just_sought) {
- /* We just did a seek, so disable any attempts to correct for where we
- are / should be.
- */
- _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
- _just_sought = false;
- }
-
- double const next = _video_position / _ffmpeg_content->video_frame_rate();
- double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
- double delta = pts - next;
-
- while (delta > one_frame) {
- /* This PTS is more than one frame forward in time of where we think we should be; emit
- a black frame.
- */
-
- /* XXX: I think this should be a copy of the last frame... */
- boost::shared_ptr<Image> black (
- new Image (
- static_cast<AVPixelFormat> (_frame->format),
- libdcp::Size (video_codec_context()->width, video_codec_context()->height),
- true
- )
- );
-
- black->make_black ();
- video (image, false, _video_position);
- delta -= one_frame;
- }
-
- if (delta > -one_frame) {
- /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (image, false, _video_position);
- }
-
+ double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
+ video (image, rint (pts * _ffmpeg_content->video_frame_rate ()));
} else {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log ("Dropping frame without PTS");
+ _log->log ("Dropping frame without PTS");
}
}
@@ -548,14 +520,6 @@ FFmpegDecoder::setup_subtitle ()
}
}
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
-
void
FFmpegDecoder::decode_subtitle_packet ()
{
@@ -569,31 +533,33 @@ 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 (ContentTime (), ContentTime (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
return;
} else if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
}
- /* Subtitle PTS in seconds (within the source, not taking into account any of the
+ /* Subtitle PTS (within the source, not taking into account any of the
source that we may have chopped off for the DCP)
*/
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
-
+ ContentTime packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
+
/* hence start time for this sub */
- Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
- Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+ ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3);
+ ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3);
AVSubtitleRect const * rect = sub.rects[0];
if (rect->type != SUBTITLE_BITMAP) {
- throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ /* XXX */
+ // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ return;
}
/* Note RGBA is expressed little-endian, so the first byte in the word is R, second
G, third B, fourth A.
*/
- shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
/* Start of the first line in the subtitle */
uint8_t* sub_p = rect->pict.data[0];
@@ -615,20 +581,19 @@ 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 (
+ from,
+ to,
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
static_cast<double> (rect->y) / vs.height,
static_cast<double> (rect->w) / vs.width,
static_cast<double> (rect->h) / vs.height
- ),
- from,
- to
+ )
);
-
avsubtitle_free (&sub);
}
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index d4b4fa1c0..2cda8f89d 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -37,7 +37,7 @@ extern "C" {
#include "subtitle_decoder.h"
#include "ffmpeg.h"
-class Film;
+class Log;
class FilterGraph;
class ffmpeg_pts_offset_test;
@@ -47,18 +47,14 @@ class ffmpeg_pts_offset_test;
class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg
{
public:
- FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
+ FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>);
~FFmpegDecoder ();
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
-
private:
friend class ::ffmpeg_pts_offset_test;
- static double compute_pts_offset (double, double, float);
-
+ void seek (ContentTime time, bool);
+ bool pass ();
void flush ();
void setup_subtitle ();
@@ -73,15 +69,17 @@ private:
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
+ bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+ bool seek_final_finished (int, int) const;
+ int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
+ void seek_and_flush (ContentTime);
+
+ boost::shared_ptr<Log> _log;
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
boost::mutex _filter_graphs_mutex;
- bool _decode_video;
- bool _decode_audio;
-
- double _pts_offset;
- bool _just_sought;
+ ContentTime _pts_offset;
};
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
index ec090ed61..72db9bce1 100644
--- a/src/lib/ffmpeg_examiner.cc
+++ b/src/lib/ffmpeg_examiner.cc
@@ -102,14 +102,14 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
}
}
-optional<double>
+optional<ContentTime>
FFmpegExaminer::frame_time (AVStream* s) const
{
- optional<double> t;
+ optional<ContentTime> t;
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- t = bet * av_q2d (s->time_base);
+ t = ContentTime::from_seconds (bet * av_q2d (s->time_base));
}
return t;
@@ -118,27 +118,25 @@ FFmpegExaminer::frame_time (AVStream* s) const
float
FFmpegExaminer::video_frame_rate () const
{
- AVStream* s = _format_context->streams[_video_stream];
-
- if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
- return av_q2d (s->avg_frame_rate);
- }
-
- return av_q2d (s->r_frame_rate);
+ /* This use of r_frame_rate is debateable; there's a few different
+ * frame rates in the format context, but this one seems to be the most
+ * reliable.
+ */
+ return av_q2d (av_stream_get_r_frame_rate (_format_context->streams[_video_stream]));
}
-libdcp::Size
+dcp::Size
FFmpegExaminer::video_size () const
{
- return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+ return dcp::Size (video_codec_context()->width, video_codec_context()->height);
}
-/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+/** @return Length according to our content's header */
+ContentTime
FFmpegExaminer::video_length () const
{
- VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
- return max (1, length);
+ ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE);
+ return ContentTime (max (int64_t (1), length.get ()));
}
string
diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h
index 369dac29c..381c5cea9 100644
--- a/src/lib/ffmpeg_examiner.h
+++ b/src/lib/ffmpeg_examiner.h
@@ -30,8 +30,8 @@ public:
FFmpegExaminer (boost::shared_ptr<const FFmpegContent>);
float video_frame_rate () const;
- libdcp::Size video_size () const;
- VideoContent::Frame video_length () const;
+ dcp::Size video_size () const;
+ ContentTime video_length () const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
return _subtitle_streams;
@@ -41,7 +41,7 @@ public:
return _audio_streams;
}
- boost::optional<double> first_video () const {
+ boost::optional<ContentTime> first_video () const {
return _first_video;
}
@@ -49,9 +49,9 @@ private:
std::string stream_name (AVStream* s) const;
std::string audio_stream_name (AVStream* s) const;
std::string subtitle_stream_name (AVStream* s) const;
- boost::optional<double> frame_time (AVStream* s) const;
+ boost::optional<ContentTime> frame_time (AVStream* s) const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
- boost::optional<double> _first_video;
+ boost::optional<ContentTime> _first_video;
};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 8bececf4f..267138ce6 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -28,14 +28,13 @@
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
-#include <boost/date_time.hpp>
#include <libxml++/libxml++.h>
#include <libcxml/cxml.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/cpl.h>
-#include <libdcp/signer.h>
-#include <libdcp/util.h>
-#include <libdcp/kdm.h>
+#include <dcp/signer_chain.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
+#include <dcp/util.h>
+#include <dcp/local_time.h>
#include "film.h"
#include "job.h"
#include "util.h"
@@ -78,8 +77,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.
@@ -436,7 +435,7 @@ Film::read_metadata ()
_sequence_video = f.bool_child ("SequenceVideo");
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
- _key = libdcp::Key (f.string_child ("Key"));
+ _key = dcp::Key (f.string_child ("Key"));
list<string> notes;
/* This method is the only one that can return notes (so far) */
@@ -766,7 +765,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
{
@@ -780,7 +779,7 @@ Film::dcps () const
) {
try {
- libdcp::DCP dcp (*i);
+ dcp::DCP dcp (*i);
dcp.read ();
out.push_back (i->path().leaf ());
} catch (...) {
@@ -875,7 +874,7 @@ Film::move_content_later (shared_ptr<Content> c)
_playlist->move_later (c);
}
-Time
+DCPTime
Film::length () const
{
return _playlist->length ();
@@ -887,12 +886,18 @@ Film::has_subtitles () const
return _playlist->has_subtitles ();
}
-OutputVideoFrame
+int
Film::best_video_frame_rate () const
{
return _playlist->best_dcp_frame_rate ();
}
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
@@ -911,31 +916,7 @@ Film::playlist_changed ()
signal_changed (CONTENT);
}
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
-{
- return divide_with_round (t * audio_frame_rate (), TIME_HZ);
-}
-
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
-{
- return divide_with_round (t * video_frame_rate (), TIME_HZ);
-}
-
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
-{
- return divide_with_round (f * TIME_HZ, audio_frame_rate ());
-}
-
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
-{
- return divide_with_round (f * TIME_HZ, video_frame_rate ());
-}
-
-OutputAudioFrame
+int
Film::audio_frame_rate () const
{
/* XXX */
@@ -951,38 +932,38 @@ Film::set_sequence_video (bool s)
}
/** @return Size of the largest possible image in whatever resolution we are using */
-libdcp::Size
+dcp::Size
Film::full_frame () const
{
switch (_resolution) {
case RESOLUTION_2K:
- return libdcp::Size (2048, 1080);
+ return dcp::Size (2048, 1080);
case RESOLUTION_4K:
- return libdcp::Size (4096, 2160);
+ return dcp::Size (4096, 2160);
}
assert (false);
- return libdcp::Size ();
+ return dcp::Size ();
}
/** @return Size of the frame */
-libdcp::Size
+dcp::Size
Film::frame_size () const
{
return fit_ratio_within (container()->ratio(), full_frame ());
}
-libdcp::KDM
+dcp::EncryptedKDM
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
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const
{
shared_ptr<const Signer> signer = make_signer ();
- libdcp::DCP dcp (dir (dcp_dir.string ()));
+ dcp::DCP dcp (dir (dcp_dir.string ()));
try {
dcp.read ();
@@ -990,24 +971,22 @@ Film::make_kdm (
throw KDMError (_("Could not read DCP to make KDM for"));
}
- time_t now = time (0);
- struct tm* tm = localtime (&now);
- string const issue_date = libdcp::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::DecryptedKDM (
+ dcp.cpls().front(), from, until, "DCP-o-matic", dcp.cpls().front()->content_title_text(), dcp::LocalTime().as_string()
+ ).encrypt (signer, target);
}
-list<libdcp::KDM>
+list<dcp::EncryptedKDM>
Film::make_kdms (
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const
{
- list<libdcp::KDM> kdms;
+ list<dcp::EncryptedKDM> kdms;
for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until));
@@ -1022,7 +1001,7 @@ Film::make_kdms (
uint64_t
Film::required_disk_space () const
{
- return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ;
+ return uint64_t (j2k_bandwidth() / 8) * length().seconds();
}
/** This method checks the disk that the Film is on and tries to decide whether or not
diff --git a/src/lib/film.h b/src/lib/film.h
index 162b67b35..ee8756b3d 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -31,8 +31,9 @@
#include <boost/signals2.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/key.h>
-#include <libdcp/kdm.h>
+#include <dcp/key.h>
+#include <dcp/decrypted_kdm.h>
+#include <dcp/encrypted_kdm.h>
#include "util.h"
#include "types.h"
#include "dci_metadata.h"
@@ -95,20 +96,15 @@ public:
return _dirty;
}
- libdcp::Size full_frame () const;
- libdcp::Size frame_size () const;
+ dcp::Size full_frame () const;
+ dcp::Size frame_size () const;
std::list<boost::filesystem::path> dcps () const;
boost::shared_ptr<Player> make_player () const;
boost::shared_ptr<Playlist> playlist () const;
- OutputAudioFrame audio_frame_rate () const;
-
- OutputAudioFrame time_to_audio_frames (Time) const;
- OutputVideoFrame time_to_video_frames (Time) const;
- Time video_frames_to_time (OutputVideoFrame) const;
- Time audio_frames_to_time (OutputAudioFrame) const;
+ int audio_frame_rate () const;
uint64_t required_disk_space () const;
bool should_be_enough_disk_space (double &, double &) const;
@@ -116,26 +112,27 @@ public:
/* Proxies for some Playlist methods */
ContentList content () const;
- Time length () const;
+ DCPTime length () const;
bool has_subtitles () const;
- OutputVideoFrame best_video_frame_rate () const;
+ int best_video_frame_rate () const;
+ FrameRateChange active_frame_rate_change (DCPTime) const;
- libdcp::KDM
+ dcp::EncryptedKDM
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
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const;
- std::list<libdcp::KDM> make_kdms (
+ std::list<dcp::EncryptedKDM> make_kdms (
std::list<boost::shared_ptr<Screen> >,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const;
- libdcp::Key key () const {
+ dcp::Key key () const {
return _key;
}
@@ -328,7 +325,7 @@ private:
bool _three_d;
bool _sequence_video;
bool _interop;
- libdcp::Key _key;
+ dcp::Key _key;
int _state_version;
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index a36a41f43..5add16d19 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -45,14 +45,14 @@ using std::make_pair;
using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
/** Construct a FilterGraph for the settings in a piece of content.
* @param content Content.
* @param s Size of the images to process.
* @param p Pixel format of the images to process.
*/
-FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
@@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size
throw DecodeError (N_("could not configure filter graph."));
}
- /* XXX: leaking `inputs' / `outputs' ? */
+ avfilter_inout_free (&inputs);
+ avfilter_inout_free (&outputs);
}
FilterGraph::~FilterGraph ()
@@ -159,7 +160,7 @@ FilterGraph::process (AVFrame* frame)
* @return true if this chain can process images with `s' and `p', otherwise false.
*/
bool
-FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (dcp::Size s, AVPixelFormat p) const
{
return (_size == s && _pixel_format == p);
}
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index 9b403c2bc..45ad5d998 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -36,16 +36,16 @@ class FFmpegContent;
class FilterGraph : public boost::noncopyable
{
public:
- FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p);
~FilterGraph ();
- bool can_process (libdcp::Size s, AVPixelFormat p) const;
+ bool can_process (dcp::Size s, AVPixelFormat p) const;
std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame);
private:
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
- libdcp::Size _size; ///< size of the images that this chain can process
+ dcp::Size _size; ///< size of the images that this chain can process
AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
AVFrame* _frame;
};
diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc
new file mode 100644
index 000000000..3e9c4b505
--- /dev/null
+++ b/src/lib/frame_rate_change.cc
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cmath>
+#include "frame_rate_change.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+static bool
+about_equal (float a, float b)
+{
+ /* A film of F seconds at f FPS will be Ff frames;
+ Consider some delta FPS d, so if we run the same
+ film at (f + d) FPS it will last F(f + d) seconds.
+
+ Hence the difference in length over the length of the film will
+ be F(f + d) - Ff frames
+ = Ff + Fd - Ff frames
+ = Fd frames
+ = Fd/f seconds
+
+ So if we accept a difference of 1 frame, ie 1/f seconds, we can
+ say that
+
+ 1/f = Fd/f
+ ie 1 = Fd
+ ie d = 1/F
+
+ So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+ FPS error is 1/F ~= 0.0001 ~= 10-e4
+ */
+
+ return (fabs (a - b) < 1e-4);
+}
+
+
+FrameRateChange::FrameRateChange (float source, int dcp)
+ : skip (false)
+ , repeat (1)
+ , change_speed (false)
+{
+ if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
+ /* The difference between source and DCP frame rate will be lower
+ (i.e. better) if we skip.
+ */
+ skip = true;
+ } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
+ /* The difference between source and DCP frame rate would be better
+ if we repeated each frame once; it may be better still if we
+ repeated more than once. Work out the required repeat.
+ */
+ repeat = round (dcp / source);
+ }
+
+ speed_up = dcp / (source * factor());
+ change_speed = !about_equal (speed_up, 1.0);
+
+ if (!skip && repeat == 1 && !change_speed) {
+ description = _("Content and DCP have the same rate.\n");
+ } else {
+ if (skip) {
+ description = _("DCP will use every other frame of the content.\n");
+ } else if (repeat == 2) {
+ description = _("Each content frame will be doubled in the DCP.\n");
+ } else if (repeat > 2) {
+ description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
+ }
+
+ if (change_speed) {
+ float const pc = dcp * 100 / (source * factor());
+ description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
+ }
+ }
+}
diff --git a/src/lib/frame_rate_change.h b/src/lib/frame_rate_change.h
new file mode 100644
index 000000000..6165f6840
--- /dev/null
+++ b/src/lib/frame_rate_change.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <string>
+
+struct FrameRateChange
+{
+ FrameRateChange (float, int);
+
+ /** @return factor by which to multiply a source frame rate
+ to get the effective rate after any skip or repeat has happened.
+ */
+ float factor () const {
+ if (skip) {
+ return 0.5;
+ }
+
+ return repeat;
+ }
+
+ /** true to skip every other frame */
+ bool skip;
+ /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
+ int repeat;
+ /** true if this DCP will run its video faster or slower than the source
+ * without taking into account `repeat' nor `skip'.
+ * (e.g. change_speed will be true if
+ * source is 29.97fps, DCP is 30fps
+ * source is 14.50fps, DCP is 30fps
+ * but not if
+ * source is 15.00fps, DCP is 30fps
+ * source is 12.50fps, DCP is 25fps)
+ */
+ bool change_speed;
+
+ /** Amount by which the video is being sped-up in the DCP; e.g. for a
+ * 24fps source in a 25fps DCP this would be 25/24.
+ */
+ float speed_up;
+
+ std::string description;
+};
diff --git a/src/lib/image.cc b/src/lib/image.cc
index d083cf3f6..432cfbd54 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -30,6 +30,8 @@ extern "C" {
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
+#include "timer.h"
+#include "rect.h"
#include "i18n.h"
@@ -37,8 +39,9 @@ using std::string;
using std::min;
using std::cout;
using std::cerr;
+using std::list;
using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
int
Image::line_factor (int n) const
@@ -82,7 +85,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
@@ -98,13 +101,13 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
out->make_black ();
/* Size of the image after any crop */
- libdcp::Size const cropped_size = crop.apply (size ());
+ dcp::Size const cropped_size = crop.apply (size ());
/* Scale context for a scale from cropped_size to inter_size */
struct SwsContext* scale_context = sws_getContext (
- cropped_size.width, cropped_size.height, pixel_format(),
- inter_size.width, inter_size.height, out_format,
- scaler->ffmpeg_id (), 0, 0, 0
+ cropped_size.width, cropped_size.height, pixel_format(),
+ inter_size.width, inter_size.height, out_format,
+ scaler->ffmpeg_id (), 0, 0, 0
);
if (!scale_context) {
@@ -138,7 +141,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
@@ -169,7 +172,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_fo
shared_ptr<Image>
Image::crop (Crop crop, bool aligned) const
{
- libdcp::Size cropped_size = crop.apply (size ());
+ dcp::Size cropped_size = crop.apply (size ());
shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
for (int c = 0; c < components(); ++c) {
@@ -342,10 +345,30 @@ Image::make_black ()
}
void
+Image::make_transparent ()
+{
+ if (_pixel_format != PIX_FMT_RGBA) {
+ throw PixelFormatError ("make_transparent()", _pixel_format);
+ }
+
+ memset (data()[0], 0, lines(0) * stride()[0]);
+}
+
+void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* Only implemented for RGBA onto RGB24 so far */
- assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+ 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;
@@ -364,15 +387,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;
}
}
}
@@ -456,8 +479,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)
{
@@ -494,7 +517,7 @@ Image::allocate ()
}
Image::Image (Image const & other)
- : libdcp::Image (other)
+ : dcp::Image (other)
, _pixel_format (other._pixel_format)
, _aligned (other._aligned)
{
@@ -512,7 +535,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)
{
@@ -531,7 +554,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)
{
@@ -564,7 +587,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);
@@ -607,7 +630,7 @@ Image::stride () const
return _stride;
}
-libdcp::Size
+dcp::Size
Image::size () const
{
return _size;
@@ -619,3 +642,23 @@ Image::aligned () const
return _aligned;
}
+PositionImage
+merge (list<PositionImage> images)
+{
+ if (images.empty ()) {
+ return PositionImage ();
+ }
+
+ dcpomatic::Rect<int> all (images.front().position, images.front().image->size().width, images.front().image->size().height);
+ for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
+ all.extend (dcpomatic::Rect<int> (i->position, i->image->size().width, i->image->size().height));
+ }
+
+ shared_ptr<Image> merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true));
+ merged->make_transparent ();
+ for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
+ merged->alpha_blend (i->image, i->position);
+ }
+
+ return PositionImage (merged, all.position ());
+}
diff --git a/src/lib/image.h b/src/lib/image.h
index 2d9f32231..23b88dd76 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -31,16 +31,17 @@ extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
}
-#include <libdcp/image.h>
+#include <dcp/image.h>
#include "util.h"
#include "position.h"
+#include "position_image.h"
class Scaler;
-class Image : public libdcp::Image
+class Image : public dcp::Image
{
public:
- Image (AVPixelFormat, libdcp::Size, bool);
+ Image (AVPixelFormat, dcp::Size, bool);
Image (AVFrame *);
Image (Image const &);
Image (boost::shared_ptr<const Image>, bool);
@@ -50,19 +51,20 @@ public:
uint8_t ** data () const;
int * line_size () const;
int * stride () const;
- libdcp::Size size () const;
+ dcp::Size size () const;
bool aligned () const;
int components () const;
int line_factor (int) const;
int lines (int) const;
- boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
- boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
void make_black ();
+ void make_transparent ();
void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
void copy (boost::shared_ptr<const Image> image, Position<int> pos);
@@ -89,4 +91,6 @@ private:
bool _aligned;
};
+extern PositionImage merge (std::list<PositionImage> images);
+
#endif
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
index 3b87fcf00..56c83d3f7 100644
--- a/src/lib/image_content.cc
+++ b/src/lib/image_content.cc
@@ -114,7 +114,7 @@ ImageContent::examine (shared_ptr<Job> job)
}
void
-ImageContent::set_video_length (VideoContent::Frame len)
+ImageContent::set_video_length (ContentTime len)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -124,14 +124,12 @@ ImageContent::set_video_length (VideoContent::Frame len)
signal_changed (ContentProperty::LENGTH);
}
-Time
+DCPTime
ImageContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
- return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate();
+ return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate(), film->video_frame_rate()));
}
string
@@ -139,7 +137,7 @@ ImageContent::identifier () const
{
stringstream s;
s << VideoContent::identifier ();
- s << "_" << video_length();
+ s << "_" << video_length().get();
return s.str ();
}
diff --git a/src/lib/image_content.h b/src/lib/image_content.h
index e56abce4a..6db24767d 100644
--- a/src/lib/image_content.h
+++ b/src/lib/image_content.h
@@ -41,11 +41,11 @@ public:
std::string summary () const;
std::string technical_summary () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
- void set_video_length (VideoContent::Frame);
+ void set_video_length (ContentTime);
bool still () const;
void set_video_frame_rate (float);
};
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
index a7999c02a..5de0c8582 100644
--- a/src/lib/image_decoder.cc
+++ b/src/lib/image_decoder.cc
@@ -30,37 +30,39 @@
using std::cout;
using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
-ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c)
- : Decoder (f)
- , VideoDecoder (f, c)
+ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c)
+ : VideoDecoder (c)
, _image_content (c)
{
}
-void
+bool
ImageDecoder::pass ()
{
- if (_video_position >= _image_content->video_length ()) {
- return;
+ if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) {
+ return true;
}
if (_image && _image_content->still ()) {
- video (_image, true, _video_position);
- return;
+ video (_image, _video_position);
+ ++_video_position;
+ return false;
}
Magick::Image* magick_image = 0;
+
boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position);
+
try {
magick_image = new Magick::Image (path.string ());
} catch (...) {
throw OpenFileError (path);
}
- libdcp::Size size (magick_image->columns(), magick_image->rows());
+ dcp::Size size (magick_image->columns(), magick_image->rows());
_image.reset (new Image (PIX_FMT_RGB24, size, true));
@@ -80,17 +82,15 @@ ImageDecoder::pass ()
delete magick_image;
- video (_image, false, _video_position);
-}
+ video (_image, _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 ();
+ VideoDecoder::seek (time, accurate);
+ _video_position = time.frames (_image_content->video_frame_rate ());
}
diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h
index c7500243e..8d88df3de 100644
--- a/src/lib/image_decoder.h
+++ b/src/lib/image_decoder.h
@@ -28,20 +28,19 @@ class ImageContent;
class ImageDecoder : public VideoDecoder
{
public:
- ImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>);
+ ImageDecoder (boost::shared_ptr<const ImageContent> c);
boost::shared_ptr<const ImageContent> content () {
return _image_content;
}
- /* Decoder */
-
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
+ void seek (ContentTime, bool);
private:
+ bool pass ();
+
boost::shared_ptr<const ImageContent> _image_content;
boost::shared_ptr<Image> _image;
+ VideoFrame _video_position;
};
diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc
index 12fe2b8a6..3897bbf37 100644
--- a/src/lib/image_examiner.cc
+++ b/src/lib/image_examiner.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -39,21 +39,20 @@ using boost::bad_lexical_cast;
ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const ImageContent> content, shared_ptr<Job>)
: _film (film)
, _image_content (content)
- , _video_length (0)
{
using namespace MagickCore;
Magick::Image* image = new Magick::Image (content->path(0).string());
- _video_size = libdcp::Size (image->columns(), image->rows());
+ _video_size = dcp::Size (image->columns(), image->rows());
delete image;
if (content->still ()) {
- _video_length = Config::instance()->default_still_length() * video_frame_rate();
+ _video_length = ContentTime::from_seconds (Config::instance()->default_still_length());
} else {
- _video_length = _image_content->number_of_paths ();
+ _video_length = ContentTime::from_frames (_image_content->number_of_paths (), video_frame_rate ());
}
}
-libdcp::Size
+dcp::Size
ImageExaminer::video_size () const
{
return _video_size.get ();
diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h
index 8887f0d3d..6ae0422cb 100644
--- a/src/lib/image_examiner.h
+++ b/src/lib/image_examiner.h
@@ -31,14 +31,14 @@ public:
ImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>, boost::shared_ptr<Job>);
float video_frame_rate () const;
- libdcp::Size video_size () const;
- VideoContent::Frame video_length () const {
+ dcp::Size video_size () const;
+ ContentTime video_length () const {
return _video_length;
}
private:
boost::weak_ptr<const Film> _film;
boost::shared_ptr<const ImageContent> _image_content;
- boost::optional<libdcp::Size> _video_size;
- VideoContent::Frame _video_length;
+ boost::optional<dcp::Size> _video_size;
+ ContentTime _video_length;
};
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 96aedac65..c6a6b90a8 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -23,12 +23,14 @@
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
#include "job.h"
#include "util.h"
#include "cross.h"
#include "ui_signaller.h"
#include "exceptions.h"
+#include "film.h"
+#include "log.h"
#include "i18n.h"
@@ -66,8 +68,8 @@ Job::run_wrapper ()
run ();
- } catch (libdcp::FileError& e) {
-
+ } catch (dcp::FileError& e) {
+
string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
try {
@@ -204,7 +206,7 @@ Job::set_state (State s)
}
}
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
int
Job::elapsed_time () const
{
@@ -279,6 +281,7 @@ Job::error_summary () const
void
Job::set_error (string s, string d)
{
+ _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d));
boost::mutex::scoped_lock lm (_state_mutex);
_error_summary = s;
_error_details = d;
diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc
index cf551285b..793a3fa0e 100644
--- a/src/lib/kdm.cc
+++ b/src/lib/kdm.cc
@@ -21,7 +21,7 @@
#include <boost/shared_ptr.hpp>
#include <quickmail.h>
#include <zip.h>
-#include <libdcp/kdm.h>
+#include <dcp/encrypted_kdm.h>
#include "kdm.h"
#include "cinema.h"
#include "exceptions.h"
@@ -36,13 +36,13 @@ using boost::shared_ptr;
struct ScreenKDM
{
- ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
+ ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
: screen (s)
, kdm (k)
{}
shared_ptr<Screen> screen;
- libdcp::KDM kdm;
+ dcp::EncryptedKDM kdm;
};
static string
@@ -103,16 +103,16 @@ make_screen_kdms (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
- list<libdcp::KDM> kdms = film->make_kdms (screens, dcp, from, to);
+ list<dcp::EncryptedKDM> 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::EncryptedKDM>::iterator j = kdms.begin ();
while (i != screens.end() && j != kdms.end ()) {
screen_kdms.push_back (ScreenKDM (*i, *j));
++i;
@@ -127,8 +127,8 @@ make_cinema_kdms (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
@@ -169,8 +169,8 @@ write_kdm_files (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
)
{
@@ -189,8 +189,8 @@ write_kdm_zip_files (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
)
{
@@ -208,8 +208,8 @@ email_kdms (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
diff --git a/src/lib/kdm.h b/src/lib/kdm.h
index c4fd43d49..5df161b2a 100644
--- a/src/lib/kdm.h
+++ b/src/lib/kdm.h
@@ -27,8 +27,8 @@ extern void write_kdm_files (
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
);
@@ -36,8 +36,8 @@ extern void write_kdm_zip_files (
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
);
@@ -45,7 +45,7 @@ extern void email_kdms (
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
);
diff --git a/src/lib/piece.cc b/src/lib/piece.cc
deleted file mode 100644
index 3c39ecfb8..000000000
--- a/src/lib/piece.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "piece.h"
-#include "player.h"
-
-using boost::shared_ptr;
-
-Piece::Piece (shared_ptr<Content> c)
- : content (c)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
-{
-
-}
-
-Piece::Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
- : content (c)
- , decoder (d)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
-{
-
-}
-
-/** Set this piece to repeat a video frame a given number of times */
-void
-Piece::set_repeat (IncomingVideo video, int num)
-{
- repeat_video = video;
- repeat_to_do = num;
- repeat_done = 0;
-}
-
-void
-Piece::reset_repeat ()
-{
- repeat_video.image.reset ();
- repeat_to_do = 0;
- repeat_done = 0;
-}
-
-bool
-Piece::repeating () const
-{
- return repeat_done != repeat_to_do;
-}
-
-void
-Piece::repeat (Player* player)
-{
- player->process_video (
- repeat_video.weak_piece,
- repeat_video.image,
- repeat_video.eyes,
- repeat_done > 0,
- repeat_video.frame,
- (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
- );
-
- ++repeat_done;
-}
-
diff --git a/src/lib/piece.h b/src/lib/piece.h
index 76df909ff..976409381 100644
--- a/src/lib/piece.h
+++ b/src/lib/piece.h
@@ -25,41 +25,19 @@
class Content;
class Decoder;
-class Piece;
-class Image;
-class Player;
-
-struct IncomingVideo
-{
-public:
- boost::weak_ptr<Piece> weak_piece;
- boost::shared_ptr<const Image> image;
- Eyes eyes;
- bool same;
- VideoContent::Frame frame;
- Time extra;
-};
class Piece
{
public:
- Piece (boost::shared_ptr<Content> c);
- Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d);
- void set_repeat (IncomingVideo video, int num);
- void reset_repeat ();
- bool repeating () const;
- void repeat (Player* player);
-
+ Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d, FrameRateChange f)
+ : content (c)
+ , decoder (d)
+ , frc (f)
+ {}
+
boost::shared_ptr<Content> content;
boost::shared_ptr<Decoder> decoder;
- /** Time of the last video we emitted relative to the start of the DCP */
- Time video_position;
- /** Time of the last audio we emitted relative to the start of the DCP */
- Time audio_position;
-
- IncomingVideo repeat_video;
- int repeat_to_do;
- int repeat_done;
+ FrameRateChange frc;
};
#endif
diff --git a/src/lib/player.cc b/src/lib/player.cc
index 1bb2e7cf4..2450ace2e 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -18,44 +18,50 @@
*/
#include <stdint.h>
+#include <algorithm>
#include "player.h"
#include "film.h"
#include "ffmpeg_decoder.h"
+#include "audio_buffers.h"
#include "ffmpeg_content.h"
#include "image_decoder.h"
#include "image_content.h"
#include "sndfile_decoder.h"
#include "sndfile_content.h"
#include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
#include "playlist.h"
#include "job.h"
#include "image.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
+#include "dcp_video.h"
+#include "config.h"
+#include "content_video.h"
using std::list;
using std::cout;
using std::min;
using std::max;
+using std::min;
using std::vector;
using std::pair;
using std::map;
+using std::make_pair;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
: _film (f)
, _playlist (p)
- , _video (true)
- , _audio (true)
, _have_valid_pieces (false)
- , _video_position (0)
- , _audio_position (0)
- , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
- , _last_emit_was_black (false)
+ , _approximate_size (false)
+ , _burn_subtitles (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));
@@ -64,397 +70,86 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
}
void
-Player::disable_video ()
-{
- _video = false;
-}
-
-void
-Player::disable_audio ()
-{
- _audio = false;
-}
-
-bool
-Player::pass ()
-{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
-
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
-
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
-
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
-
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
- }
- }
-
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
- }
- }
- }
-
- if (!earliest) {
- flush ();
- return true;
- }
-
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
- } else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
- } else {
- earliest->decoder->pass ();
- }
- }
- break;
-
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
- } else {
- earliest->decoder->pass ();
-
- if (earliest->decoder->done()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
- assert (ac);
- shared_ptr<Resampler> re = resampler (ac, false);
- if (re) {
- shared_ptr<const AudioBuffers> b = re->flush ();
- if (b->frames ()) {
- process_audio (earliest, b, ac->audio_length ());
- }
- }
- }
- }
- 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;
- }
-
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
- if (ad && ad->has_audio ()) {
- audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
- }
- }
-
- if (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 ());
- }
- }
-
- 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)
-{
- /* 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;
-
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
-
- shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
- assert (content);
-
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
- if (frc.skip && (frame % 2) == 1) {
- return;
- }
-
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
- return;
- }
-
- Time const time = content->position() + relative_time + extra - content->trim_start ();
- libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
-
- shared_ptr<PlayerImage> pi (
- new PlayerImage (
- image,
- content->crop(),
- image_size,
- _video_container_size,
- _film->scaler()
- )
- );
-
- if (_film->with_subtitles ()) {
- for (list<Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- if (i->covers (time)) {
- /* This may be true for more than one of _subtitles, but the last (latest-starting)
- one is the one we want to use, so that's ok.
- */
- Position<int> const container_offset (
- (_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
- );
-
- pi->set_subtitle (i->out_image(), i->out_position() + container_offset);
- }
- }
- }
-
- /* Clear out old subtitles */
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ) {
- list<Subtitle>::iterator j = i;
- ++j;
-
- if (i->ends_before (time)) {
- _subtitles.erase (i);
- }
-
- i = j;
- }
-
-#ifdef DCPOMATIC_DEBUG
- _last_video = piece->content;
-#endif
-
- Video (pi, eyes, content->colour_conversion(), same, 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::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
-{
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
-
- shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
- assert (content);
-
- /* Gain */
- if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
- gain->apply_gain (content->audio_gain ());
- audio = gain;
- }
-
- /* Resample */
- if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
- }
-
- Time const relative_time = _film->audio_frames_to_time (frame);
-
- if (content->trimmed (relative_time)) {
- return;
- }
-
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-
- /* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
- dcp_mapped->make_silent ();
-
- AudioMapping map = content->audio_mapping ();
- for (int i = 0; i < map.content_channels(); ++i) {
- for (int j = 0; j < _film->audio_channels(); ++j) {
- if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
- dcp_mapped->accumulate_channel (
- audio.get(),
- i,
- static_cast<libdcp::Channel> (j),
- map.get (i, static_cast<libdcp::Channel> (j))
- );
- }
- }
- }
-
- audio = dcp_mapped;
-
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
- return;
- }
-
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
-
- audio = trimmed;
- time = 0;
- }
-
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
-}
-
-void
-Player::flush ()
-{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
- if (_audio && tb.audio) {
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
- }
-
- while (_video && _video_position < _audio_position) {
- emit_black ();
- }
-
- while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
- }
-
-}
-
-/** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- * @return true on error
- */
-void
-Player::seek (Time t, bool accurate)
-{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
-
- if (_pieces.empty ()) {
- return;
- }
-
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
- if (!vc) {
- continue;
- }
-
- /* s is the offset of t from the start position of this content */
- Time s = t - vc->position ();
- s = max (static_cast<Time> (0), s);
- s = min (vc->length_after_trim(), s);
-
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
-
- /* 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 ();
- }
-
- _video_position = _audio_position = t;
-
- /* XXX: don't seek audio because we don't need to... */
-}
-
-void
Player::setup_pieces ()
{
list<shared_ptr<Piece> > old_pieces = _pieces;
-
_pieces.clear ();
ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
if (!(*i)->paths_valid ()) {
continue;
}
+
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
+
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ DCPTime best_overlap_t;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+ } else {
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
+ /* FFmpeg */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
- fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
- piece->decoder = fd;
+ decoder.reset (new FFmpegDecoder (fc, _film->log()));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
-
+
+ /* ImageContent */
shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
if (ic) {
- bool reusing = false;
-
/* See if we can re-use an old ImageDecoder */
for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
if (imd && imd->content() == ic) {
- piece = *j;
- reusing = true;
+ decoder = imd;
}
}
- if (!reusing) {
- shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- piece->decoder = id;
+ if (!decoder) {
+ decoder.reset (new ImageDecoder (ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
+ /* SndfileContent */
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
- shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+ decoder.reset (new SndfileDecoder (sc));
+ frc = best_overlap_frc;
+ }
- piece->decoder = sd;
+ /* SubRipContent */
+ shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+ if (rc) {
+ decoder.reset (new SubRipDecoder (rc));
+ frc = best_overlap_frc;
}
- _pieces.push_back (piece);
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
_have_valid_pieces = true;
@@ -469,9 +164,12 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
}
if (
- property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
- property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
- property == VideoContentProperty::VIDEO_FRAME_TYPE
+ property == ContentProperty::POSITION ||
+ property == ContentProperty::LENGTH ||
+ property == ContentProperty::TRIM_START ||
+ property == ContentProperty::TRIM_END ||
+ property == ContentProperty::PATH ||
+ property == VideoContentProperty::VIDEO_FRAME_TYPE
) {
_have_valid_pieces = false;
@@ -480,26 +178,13 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
} else if (
property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
- property == SubtitleContentProperty::SUBTITLE_SCALE
- ) {
-
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- i->update (_film, _video_container_size);
- }
-
- Changed (frequent);
-
- } else if (
- property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE ||
+ property == SubtitleContentProperty::SUBTITLE_SCALE ||
+ property == VideoContentProperty::VIDEO_CROP ||
+ property == VideoContentProperty::VIDEO_SCALE ||
property == VideoContentProperty::VIDEO_FRAME_RATE
) {
Changed (frequent);
-
- } else if (property == ContentProperty::PATH) {
-
- _have_valid_pieces = false;
- Changed (frequent);
}
}
@@ -511,154 +196,325 @@ Player::playlist_changed ()
}
void
-Player::set_video_container_size (libdcp::Size s)
+Player::set_video_container_size (dcp::Size s)
{
_video_container_size = s;
- shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
- im->make_black ();
-
- _black_frame.reset (
- new PlayerImage (
- im,
- Crop(),
- _video_container_size,
- _video_container_size,
- Scaler::from_id ("bicubic")
- )
- );
+ _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
+ _black_image->make_black ();
}
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
+void
+Player::film_changed (Film::Property p)
{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
+ /* Here we should notice Film properties that affect our output, and
+ alert listeners that our output now would be different to how it was
+ last time we were run.
+ */
- if (!create) {
- return shared_ptr<Resampler> ();
+ if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
+ Changed (false);
}
-
- _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 ()
+list<PositionImage>
+Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs)
{
-#ifdef DCPOMATIC_DEBUG
- _last_video.reset ();
-#endif
+ list<PositionImage> all;
+
+ for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ if (!(*i)->image) {
+ continue;
+ }
- Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
- _video_position += _film->video_frames_to_time (1);
- _last_emit_was_black = true;
+ dcpomatic::Rect<double> in_rect = (*i)->rectangle;
+ dcp::Size scaled_size;
+
+ in_rect.x += content->subtitle_x_offset ();
+ in_rect.y += content->subtitle_y_offset ();
+
+ /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
+ scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
+ scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
+
+ /* Then we need a corrective translation, consisting of two parts:
+ *
+ * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
+ * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
+ *
+ * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
+ * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
+ * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+ *
+ * Combining these two translations gives these expressions.
+ */
+
+ all.push_back (
+ PositionImage (
+ (*i)->image->scale (
+ scaled_size,
+ Scaler::from_id ("bicubic"),
+ (*i)->image->pixel_format (),
+ true
+ ),
+ Position<int> (
+ rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
+ rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
+ )
+ )
+ );
+ }
+
+ return all;
}
-void
-Player::emit_silence (OutputAudioFrame most)
+list<PositionImage>
+Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub)
{
- if (most == 0) {
- return;
+ list<PositionImage> all;
+ for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
+ if (!(*i)->subs.empty ()) {
+ all.push_back (render_subtitles ((*i)->subs, _video_container_size));
+ }
}
-
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
- silence->make_silent ();
- Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
+
+ return all;
}
void
-Player::film_changed (Film::Property p)
+Player::set_approximate_size ()
{
- /* Here we should notice Film properties that affect our output, and
- alert listeners that our output now would be different to how it was
- last time we were run.
- */
+ _approximate_size = true;
+}
- if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
- Changed (false);
- }
+shared_ptr<DCPVideo>
+Player::black_dcp_video (DCPTime time) const
+{
+ return shared_ptr<DCPVideo> (
+ new DCPVideo (
+ _black_image,
+ EYES_BOTH,
+ Crop (),
+ _video_container_size,
+ _video_container_size,
+ Scaler::from_id ("bicubic"),
+ Config::instance()->colour_conversions().front().conversion,
+ time
+ )
+ );
}
-void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+shared_ptr<DCPVideo>
+Player::get_video (DCPTime time, bool accurate)
{
- if (!image) {
- /* A null image means that we should stop any current subtitles at `from' */
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- i->set_stop (from);
+ if (!_have_valid_pieces) {
+ setup_pieces ();
+ }
+
+ list<shared_ptr<Piece> > ov = overlaps<VideoContent> (time);
+ if (ov.empty ()) {
+ /* No video content at this time */
+ return black_dcp_video (time);
+ }
+
+ /* Create a DCPVideo from the content's video at this time */
+
+ shared_ptr<Piece> piece = ov.back ();
+ shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
+ assert (decoder);
+ shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+ assert (content);
+
+ optional<ContentVideo> dec = decoder->get_video (dcp_to_content_video (piece, time), accurate);
+ if (!dec) {
+ return black_dcp_video (time);
+ }
+
+ dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
+ }
+
+ shared_ptr<DCPVideo> dcp_video (
+ new DCPVideo (
+ dec->image,
+ dec->eyes,
+ content->crop (),
+ image_size,
+ _video_container_size,
+ _film->scaler(),
+ content->colour_conversion (),
+ time
+ )
+ );
+
+ /* Add subtitles */
+
+ ov = overlaps<SubtitleContent> (time);
+ list<PositionImage> sub_images;
+
+ for (list<shared_ptr<Piece> >::const_iterator i = ov.begin(); i != ov.end(); ++i) {
+ shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
+ shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
+ ContentTime const from = dcp_to_content_subtitle (*i, time);
+ ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
+
+ list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (from, to);
+ if (!image_subtitles.empty ()) {
+ list<PositionImage> im = process_content_image_subtitles (
+ subtitle_content,
+ image_subtitles
+ );
+
+ copy (im.begin(), im.end(), back_inserter (sub_images));
+ }
+
+ if (_burn_subtitles) {
+ list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (from, to);
+ if (!text_subtitles.empty ()) {
+ list<PositionImage> im = process_content_text_subtitles (text_subtitles);
+ copy (im.begin(), im.end(), back_inserter (sub_images));
+ }
}
- } else {
- _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to));
}
+
+ if (!sub_images.empty ()) {
+ dcp_video->set_subtitle (merge (sub_images));
+ }
+
+ return dcp_video;
}
-/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
- * @return false if this could not be done.
- */
-bool
-Player::repeat_last_video ()
+shared_ptr<AudioBuffers>
+Player::get_audio (DCPTime time, DCPTime length, bool accurate)
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
- return false;
+ if (!_have_valid_pieces) {
+ setup_pieces ();
}
- process_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
- );
+ AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
+
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
+ audio->make_silent ();
+
+ list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time);
+ if (ov.empty ()) {
+ return audio;
+ }
+
+ for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
+
+ shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
+ assert (content);
+ shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ assert (decoder);
+
+ if (content->content_audio_frame_rate() == 0) {
+ /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
+ * audio stream).
+ */
+ continue;
+ }
+
+ AudioFrame const content_time = dcp_to_content_audio (*i, time);
- return true;
+ /* Audio from this piece's decoder (which might be more than what we asked for) */
+ shared_ptr<ContentAudio> all = decoder->get_audio (content_time, length_frames, accurate);
+
+ /* Gain */
+ if (content->audio_gain() != 0) {
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
+ gain->apply_gain (content->audio_gain ());
+ all->audio = gain;
+ }
+
+ /* Remap channels */
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
+ dcp_mapped->make_silent ();
+ AudioMapping map = content->audio_mapping ();
+ for (int i = 0; i < map.content_channels(); ++i) {
+ for (int j = 0; j < _film->audio_channels(); ++j) {
+ if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
+ dcp_mapped->accumulate_channel (
+ all->audio.get(),
+ i,
+ j,
+ map.get (i, static_cast<dcp::Channel> (j))
+ );
+ }
+ }
+ }
+
+ all->audio = dcp_mapped;
+
+ /* Delay */
+ /* XXX
+ 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->audio->frames ()) {
+ return;
+ }
+
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->audio->channels(), audio->audio->frames() - frames));
+ trimmed->copy_from (audio->audio.get(), audio->audio->frames() - frames, frames, 0);
+
+ audio->audio = trimmed;
+ audio->dcp_time = 0;
+ }
+ */
+
+ audio->accumulate_frames (all->audio.get(), all->frame - content_time, 0, min (AudioFrame (all->audio->frames()), length_frames));
+ }
+
+ return audio;
}
-PlayerImage::PlayerImage (
- shared_ptr<const Image> in,
- Crop crop,
- libdcp::Size inter_size,
- libdcp::Size out_size,
- Scaler const * scaler
- )
- : _in (in)
- , _crop (crop)
- , _inter_size (inter_size)
- , _out_size (out_size)
- , _scaler (scaler)
+VideoFrame
+Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
{
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
+ /* Convert this to the content frame */
+ return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
}
-void
-PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
+AudioFrame
+Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
{
- _subtitle_image = image;
- _subtitle_position = pos;
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
+
+ /* Convert this to the content frame */
+ return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
}
-shared_ptr<Image>
-PlayerImage::image ()
+ContentTime
+Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
{
- shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
+ return ContentTime (s, piece->frc);
+}
- if (_subtitle_image) {
- out->alpha_blend (_subtitle_image, _subtitle_position);
- }
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()));
+}
- return out;
+PlayerStatistics const &
+Player::statistics () const
+{
+ return _statistics;
}
diff --git a/src/lib/player.h b/src/lib/player.h
index 4d911a83b..62ba89e6c 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -27,10 +27,11 @@
#include "content.h"
#include "film.h"
#include "rect.h"
-#include "audio_merger.h"
#include "audio_content.h"
+#include "dcpomatic_time.h"
+#include "content_subtitle.h"
+#include "position_image.h"
#include "piece.h"
-#include "subtitle.h"
class Job;
class Film;
@@ -38,7 +39,40 @@ class Playlist;
class AudioContent;
class Piece;
class Image;
-class Resampler;
+class DCPVideo;
+class Decoder;
+
+class PlayerStatistics
+{
+public:
+ struct Video {
+ Video ()
+ : black (0)
+ , repeat (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ int black;
+ int repeat;
+ int good;
+ int skip;
+ } video;
+
+ struct Audio {
+ Audio ()
+ : silence (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ DCPTime silence;
+ int64_t good;
+ int64_t skip;
+ } audio;
+
+ void dump (boost::shared_ptr<Log>) const;
+};
/** A wrapper for an Image which contains some pending operations; these may
* not be necessary if the receiver of the PlayerImage throws it away.
@@ -46,56 +80,41 @@ class Resampler;
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 ();
-
+
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 Player
- * @brief A class which can `play' a Playlist; emitting its audio and video.
+ * @brief A class which can `play' a Playlist.
*/
class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
{
public:
Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
- void disable_video ();
- void disable_audio ();
-
- bool pass ();
- void seek (Time, bool);
+ boost::shared_ptr<DCPVideo> get_video (DCPTime time, bool accurate);
+ boost::shared_ptr<AudioBuffers> get_audio (DCPTime time, DCPTime length, bool accurate);
- Time video_position () const {
- return _video_position;
+ void set_video_container_size (dcp::Size);
+ void set_approximate_size ();
+ void set_burn_subtitles (bool burn) {
+ _burn_subtitles = burn;
}
- void set_video_container_size (libdcp::Size);
-
- bool repeat_last_video ();
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is the eye(s) that should see this image.
- * Third parameter is the colour conversion that should be used for this image.
- * 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;
+ PlayerStatistics const & statistics () const;
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
-
/** Emitted when something has changed such that if we went back and emitted
* the last frame again it would look different. This is not emitted after
* a seek.
@@ -108,50 +127,49 @@ 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 flush ();
- void emit_black ();
- void emit_silence (OutputAudioFrame);
- boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
void film_changed (Film::Property);
- void update_subtitle ();
-
+ std::list<PositionImage> process_content_image_subtitles (
+ boost::shared_ptr<SubtitleContent>, std::list<boost::shared_ptr<ContentImageSubtitle> >
+ );
+ std::list<PositionImage> process_content_text_subtitles (std::list<boost::shared_ptr<ContentTextSubtitle> >);
+ void update_subtitle_from_text ();
+ VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ AudioFrame dcp_to_content_audio (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ ContentTime dcp_to_content_subtitle (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ boost::shared_ptr<DCPVideo> black_dcp_video (DCPTime) const;
+
+ template<class C>
+ std::list<boost::shared_ptr<Piece> >
+ overlaps (DCPTime t)
+ {
+ std::list<boost::shared_ptr<Piece> > overlaps;
+ for (typename std::list<boost::shared_ptr<Piece> >::const_iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+ if (boost::dynamic_pointer_cast<C> ((*i)->content) && (*i)->content->position() <= t && t < (*i)->content->end()) {
+ overlaps.push_back (*i);
+ }
+ }
+
+ return overlaps;
+ }
+
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
-
- bool _video;
- bool _audio;
/** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
bool _have_valid_pieces;
std::list<boost::shared_ptr<Piece> > _pieces;
- /** The time after the last video that we emitted */
- Time _video_position;
- /** The time after the last audio that we emitted */
- Time _audio_position;
-
- AudioMerger<Time, AudioContent::Frame> _audio_merger;
-
- libdcp::Size _video_container_size;
- boost::shared_ptr<PlayerImage> _black_frame;
- std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
-
- std::list<Subtitle> _subtitles;
-
-#ifdef DCPOMATIC_DEBUG
- boost::shared_ptr<Content> _last_video;
-#endif
+ dcp::Size _video_container_size;
+ boost::shared_ptr<Image> _black_image;
- bool _last_emit_was_black;
+ bool _approximate_size;
+ bool _burn_subtitles;
- IncomingVideo _last_incoming_video;
+ PlayerStatistics _statistics;
boost::signals2::scoped_connection _playlist_changed_connection;
boost::signals2::scoped_connection _playlist_content_changed_connection;
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
index a2bec83bb..1e8a3319c 100644
--- a/src/lib/playlist.cc
+++ b/src/lib/playlist.cc
@@ -81,20 +81,20 @@ Playlist::maybe_sequence_video ()
_sequencing_video = true;
ContentList cl = _content;
- Time next_left = 0;
- Time next_right = 0;
+ DCPTime next_left;
+ DCPTime next_right;
for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
if (!vc) {
continue;
}
-
+
if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
vc->set_position (next_right);
- next_right = vc->end() + 1;
+ next_right = vc->end() + DCPTime::delta ();
} else {
vc->set_position (next_left);
- next_left = vc->end() + 1;
+ next_left = vc->end() + DCPTime::delta ();
}
}
@@ -261,12 +261,12 @@ Playlist::best_dcp_frame_rate () const
return best->dcp;
}
-Time
+DCPTime
Playlist::length () const
{
- Time len = 0;
+ DCPTime len;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
- len = max (len, (*i)->end() + 1);
+ len = max (len, (*i)->end() + DCPTime::delta ());
}
return len;
@@ -286,10 +286,10 @@ Playlist::reconnect ()
}
}
-Time
+DCPTime
Playlist::video_end () const
{
- Time end = 0;
+ DCPTime end;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
if (dynamic_pointer_cast<const VideoContent> (*i)) {
end = max (end, (*i)->end ());
@@ -299,6 +299,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)
{
@@ -321,7 +338,7 @@ Playlist::content () const
void
Playlist::repeat (ContentList c, int n)
{
- pair<Time, Time> range (TIME_MAX, 0);
+ pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
range.first = min (range.first, (*i)->position ());
range.second = max (range.second, (*i)->position ());
@@ -329,7 +346,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 ();
@@ -362,7 +379,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 ());
@@ -389,7 +406,7 @@ Playlist::move_later (shared_ptr<Content> c)
return;
}
- Time const p = (*next)->position ();
+ DCPTime const p = (*next)->position ();
(*next)->set_position (c->position ());
c->set_position (p + c->length_after_trim ());
sort (_content.begin(), _content.end(), ContentSorter ());
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
index 394023f5c..444eb9ae5 100644
--- a/src/lib/playlist.h
+++ b/src/lib/playlist.h
@@ -25,6 +25,7 @@
#include <boost/enable_shared_from_this.hpp>
#include "ffmpeg_content.h"
#include "audio_mapping.h"
+#include "util.h"
class Content;
class FFmpegContent;
@@ -70,10 +71,11 @@ public:
std::string video_identifier () const;
- Time length () const;
+ DCPTime length () const;
int best_dcp_frame_rate () const;
- Time video_end () const;
+ DCPTime video_end () const;
+ FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
void set_sequence_video (bool);
void maybe_sequence_video ();
diff --git a/src/lib/position_image.h b/src/lib/position_image.h
new file mode 100644
index 000000000..dbd3c600e
--- /dev/null
+++ b/src/lib/position_image.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_POSITION_IMAGE_H
+#define DCPOMATIC_POSITION_IMAGE_H
+
+#include "position.h"
+
+class Image;
+
+class PositionImage
+{
+public:
+ PositionImage () {}
+
+ PositionImage (boost::shared_ptr<Image> i, Position<int> p)
+ : image (i)
+ , position (p)
+ {}
+
+ boost::shared_ptr<Image> image;
+ Position<int> position;
+};
+
+#endif
diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc
index a47b2101e..275f4ef15 100644
--- a/src/lib/ratio.cc
+++ b/src/lib/ratio.cc
@@ -17,7 +17,7 @@
*/
-#include <libdcp/types.h>
+#include <dcp/types.h>
#include "ratio.h"
#include "util.h"
diff --git a/src/lib/ratio.h b/src/lib/ratio.h
index f3354f1b6..cd7d0d6e4 100644
--- a/src/lib/ratio.h
+++ b/src/lib/ratio.h
@@ -22,7 +22,7 @@
#include <vector>
#include <boost/utility.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
class Ratio : public boost::noncopyable
{
diff --git a/src/lib/rect.h b/src/lib/rect.h
index 6f4709c08..1feb8ad4f 100644
--- a/src/lib/rect.h
+++ b/src/lib/rect.h
@@ -42,6 +42,13 @@ public:
, height (0)
{}
+ Rect (Position<T> p, T w_, T h_)
+ : x (p.x)
+ , y (p.y)
+ , width (w_)
+ , height (h_)
+ {}
+
Rect (T x_, T y_, T w_, T h_)
: x (x_)
, y (y_)
@@ -54,11 +61,13 @@ public:
T width;
T height;
- Position<T> position () const {
+ Position<T> position () const
+ {
return Position<T> (x, y);
}
- Rect<T> intersection (Rect<T> const & other) const {
+ Rect<T> intersection (Rect<T> const & other) const
+ {
T const tx = max (x, other.x);
T const ty = max (y, other.y);
@@ -69,7 +78,16 @@ public:
);
}
- bool contains (Position<T> p) const {
+ void extend (Rect<T> const & other)
+ {
+ x = std::min (x, other.x);
+ y = std::min (y, other.y);
+ width = std::max (x + width, other.x + other.width) - x;
+ height = std::max (y + height, other.y + other.height) - y;
+ }
+
+ bool contains (Position<T> p) const
+ {
return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
}
};
diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc
new file mode 100644
index 000000000..5364b8dfe
--- /dev/null
+++ b/src/lib/render_subtitles.cc
@@ -0,0 +1,152 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using std::pair;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+ switch (v_align) {
+ case dcp::TOP:
+ return (v_position / 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;
+}
+
+PositionImage
+render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target)
+{
+ if (subtitles.empty ()) {
+ return PositionImage ();
+ }
+
+ /* Estimate height that the subtitle image needs to be */
+ optional<int> top;
+ optional<int> bottom;
+ for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+ int const t = b - i->size() * target.height / (11 * 72);
+
+ top = min (top.get_value_or (t), t);
+ bottom = max (bottom.get_value_or (b), b);
+ }
+
+ top = top.get() - 32;
+ bottom = bottom.get() + 32;
+
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false));
+ image->make_black ();
+
+ Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+ image->data()[0],
+ Cairo::FORMAT_ARGB32,
+ image->size().width,
+ image->size().height,
+ Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+ );
+
+ Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+ Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+ layout->set_width (image->size().width * PANGO_SCALE);
+ layout->set_alignment (Pango::ALIGN_CENTER);
+
+ context->set_line_width (1);
+
+ for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ string f = i->font ();
+ if (f.empty ()) {
+ f = "Arial";
+ }
+ Pango::FontDescription font (f);
+ font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+ if (i->italic ()) {
+ font.set_style (Pango::STYLE_ITALIC);
+ }
+ layout->set_font_description (font);
+ layout->set_text (i->text ());
+
+ /* Compute fade factor */
+ /* XXX */
+ float fade_factor = 1;
+#if 0
+ dcp::Time now (time * 1000 / (4 * TIME_HZ));
+ dcp::Time end_fade_up = i->in() + i->fade_up_time ();
+ dcp::Time start_fade_down = i->out() - i->fade_down_time ();
+ if (now < end_fade_up) {
+ fade_factor = (now - i->in()) / i->fade_up_time();
+ } else if (now > start_fade_down) {
+ fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+ }
+#endif
+
+ layout->update_from_cairo_context (context);
+
+ /* Work out position */
+
+ int const x = 0;
+ int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+ if (i->effect() == dcp::SHADOW) {
+ /* Drop-shadow effect */
+ dcp::Color const ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ context->move_to (x + 4, y + 4);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+ }
+
+ /* The actual subtitle */
+ context->move_to (x, y);
+ dcp::Color const c = i->color ();
+ context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+
+ if (i->effect() == dcp::BORDER) {
+ /* Border effect */
+ context->move_to (x, y);
+ dcp::Color ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->stroke ();
+ }
+ }
+
+ return PositionImage (image, Position<int> (0, top.get ()));
+}
+
diff --git a/src/lib/decoder.cc b/src/lib/render_subtitles.h
index 3f4cda6eb..d83dc119a 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/render_subtitles.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,22 +17,8 @@
*/
-/** @file src/decoder.cc
- * @brief Parent class for decoders of content.
- */
+#include <dcp/subtitle_string.h>
+#include <dcp/util.h>
+#include "position_image.h"
-#include "film.h"
-#include "decoder.h"
-
-#include "i18n.h"
-
-using boost::shared_ptr;
-
-/** @param f Film.
- * @param o Decode options.
- */
-Decoder::Decoder (shared_ptr<const Film> f)
- : _film (f)
-{
-
-}
+PositionImage render_subtitles (std::list<dcp::SubtitleString>, dcp::Size);
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 a8388ab77..2d7fa1c1c 100644
--- a/src/lib/sndfile_content.cc
+++ b/src/lib/sndfile_content.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,8 +33,6 @@ using std::cout;
using boost::shared_ptr;
using boost::lexical_cast;
-int const SndfileContentProperty::VIDEO_FRAME_RATE = 600;
-
SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
: Content (f, p)
, AudioContent (f, p)
@@ -51,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml:
, _audio_mapping (node->node_child ("AudioMapping"), version)
{
_audio_channels = node->number_child<int> ("AudioChannels");
- _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+ _audio_length = ContentTime (node->number_child<int64_t> ("AudioLength"));
_audio_frame_rate = node->number_child<int> ("AudioFrameRate");
}
@@ -83,7 +81,7 @@ SndfileContent::information () const
_("%1 channels, %2kHz, %3 samples"),
audio_channels(),
content_audio_frame_rate() / 1000.0,
- audio_length()
+ audio_length().frames (content_audio_frame_rate ())
);
return s.str ();
@@ -104,10 +102,7 @@ SndfileContent::examine (shared_ptr<Job> job)
job->set_progress_unknown ();
Content::examine (job);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- SndfileDecoder dec (film, shared_from_this());
+ SndfileDecoder dec (shared_from_this());
{
boost::mutex::scoped_lock lm (_mutex);
@@ -138,24 +133,17 @@ SndfileContent::as_xml (xmlpp::Node* node) const
AudioContent::as_xml (node);
node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (audio_channels ()));
- node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length ()));
+ node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length().get ()));
node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (content_audio_frame_rate ()));
_audio_mapping.as_xml (node->add_child("AudioMapping"));
}
-Time
+DCPTime
SndfileContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- float const rate = _video_frame_rate.get_value_or (film->video_frame_rate ());
- OutputAudioFrame const len = divide_with_round (
- audio_length() * output_audio_frame_rate() * rate,
- content_audio_frame_rate() * film->video_frame_rate()
- );
-
- return film->audio_frames_to_time (len);
+ return DCPTime (audio_length(), film->active_frame_rate_change (position ()));
}
int
@@ -178,17 +166,3 @@ SndfileContent::set_audio_mapping (AudioMapping m)
signal_changed (AudioContentProperty::AUDIO_MAPPING);
}
-float
-SndfileContent::video_frame_rate () const
-{
- {
- boost::mutex::scoped_lock lm (_mutex);
- if (_video_frame_rate) {
- return _video_frame_rate.get ();
- }
- }
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- return film->video_frame_rate ();
-}
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
index a79fb99b6..04d157131 100644
--- a/src/lib/sndfile_content.h
+++ b/src/lib/sndfile_content.h
@@ -29,12 +29,6 @@ namespace cxml {
class Node;
}
-class SndfileContentProperty
-{
-public:
- static int const VIDEO_FRAME_RATE;
-};
-
class SndfileContent : public AudioContent
{
public:
@@ -50,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 {
@@ -58,7 +52,7 @@ public:
return _audio_channels;
}
- AudioContent::Frame audio_length () const {
+ ContentTime audio_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_length;
}
@@ -75,30 +69,15 @@ public:
return _audio_mapping;
}
- void set_video_frame_rate (float r) {
- {
- boost::mutex::scoped_lock lm (_mutex);
- _video_frame_rate = r;
- }
-
- signal_changed (SndfileContentProperty::VIDEO_FRAME_RATE);
- }
-
- float video_frame_rate () const;
-
void set_audio_mapping (AudioMapping);
static bool valid_file (boost::filesystem::path);
private:
int _audio_channels;
- AudioContent::Frame _audio_length;
+ ContentTime _audio_length;
int _audio_frame_rate;
AudioMapping _audio_mapping;
- /** Video frame rate that this audio has been prepared for,
- if specified.
- */
- boost::optional<float> _video_frame_rate;
};
#endif
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
index f66a7c7dc..c37a84474 100644
--- a/src/lib/sndfile_decoder.cc
+++ b/src/lib/sndfile_decoder.cc
@@ -25,7 +25,6 @@
#include <sndfile.h>
#include "sndfile_content.h"
#include "sndfile_decoder.h"
-#include "film.h"
#include "exceptions.h"
#include "audio_buffers.h"
@@ -37,9 +36,8 @@ using std::min;
using std::cout;
using boost::shared_ptr;
-SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
- : Decoder (f)
- , AudioDecoder (f, c)
+SndfileDecoder::SndfileDecoder (shared_ptr<const SndfileContent> c)
+ : AudioDecoder (c)
, _sndfile_content (c)
, _deinterleave_buffer (0)
{
@@ -66,9 +64,13 @@ SndfileDecoder::~SndfileDecoder ()
delete[] _deinterleave_buffer;
}
-void
+bool
SndfileDecoder::pass ()
{
+ if (_remaining == 0) {
+ return true;
+ }
+
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
*/
@@ -101,9 +103,11 @@ SndfileDecoder::pass ()
}
data->set_frames (this_time);
- audio (data, _done);
+ audio (data, ContentTime::from_frames (_done, audio_frame_rate ()));
_done += this_time;
_remaining -= this_time;
+
+ return _remaining == 0;
}
int
@@ -112,10 +116,10 @@ SndfileDecoder::audio_channels () const
return _info.channels;
}
-AudioContent::Frame
+ContentTime
SndfileDecoder::audio_length () const
{
- return _info.frames;
+ return ContentTime::from_frames (_info.frames, audio_frame_rate ());
}
int
@@ -124,8 +128,11 @@ SndfileDecoder::audio_frame_rate () const
return _info.samplerate;
}
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
{
- return _audio_position >= _sndfile_content->audio_length ();
+ AudioDecoder::seek (t, accurate);
+
+ _done = t.frames (audio_frame_rate ());
+ _remaining = _info.frames - _done;
}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
index 77fa6d177..52590ef24 100644
--- a/src/lib/sndfile_decoder.h
+++ b/src/lib/sndfile_decoder.h
@@ -26,21 +26,22 @@ class SndfileContent;
class SndfileDecoder : public AudioDecoder
{
public:
- SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+ SndfileDecoder (boost::shared_ptr<const SndfileContent> c);
~SndfileDecoder ();
- void pass ();
- bool done () const;
+ void seek (ContentTime, bool);
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ ContentTime audio_length () const;
int audio_frame_rate () const;
private:
+ bool pass ();
+
boost::shared_ptr<const SndfileContent> _sndfile_content;
SNDFILE* _sndfile;
SF_INFO _info;
- AudioContent::Frame _done;
- AudioContent::Frame _remaining;
+ int64_t _done;
+ int64_t _remaining;
float* _deinterleave_buffer;
};
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644
index 000000000..aa4a0b548
--- /dev/null
+++ b/src/lib/subrip.cc
@@ -0,0 +1,237 @@
+/*
+ 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 <boost/lexical_cast.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+ FILE* f = fopen_boost (content->path (0), "r");
+ if (!f) {
+ throw OpenFileError (content->path (0));
+ }
+
+ enum {
+ COUNTER,
+ METADATA,
+ CONTENT
+ } state = COUNTER;
+
+ char buffer[256];
+ int next_count = 1;
+
+ boost::optional<SubRipSubtitle> current;
+ list<string> lines;
+
+ while (!feof (f)) {
+ fgets (buffer, sizeof (buffer), f);
+ if (feof (f)) {
+ break;
+ }
+
+ string line (buffer);
+ trim_right_if (line, boost::is_any_of ("\n\r"));
+
+ switch (state) {
+ case COUNTER:
+ {
+ int x = 0;
+ try {
+ x = lexical_cast<int> (line);
+ } catch (...) {
+
+ }
+
+ if (x == next_count) {
+ state = METADATA;
+ ++next_count;
+ current = SubRipSubtitle ();
+ } else {
+ throw SubRipError (line, _("a subtitle count"), content->path (0));
+ }
+ }
+ break;
+ case METADATA:
+ {
+ vector<string> p;
+ boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+ if (p.size() != 3 && p.size() != 7) {
+ throw SubRipError (line, _("a time/position line"), content->path (0));
+ }
+
+ current->from = convert_time (p[0]);
+ current->to = convert_time (p[2]);
+
+ if (p.size() > 3) {
+ current->x1 = convert_coordinate (p[3]);
+ current->x2 = convert_coordinate (p[4]);
+ current->y1 = convert_coordinate (p[5]);
+ current->y2 = convert_coordinate (p[6]);
+ }
+ state = CONTENT;
+ break;
+ }
+ case CONTENT:
+ if (line.empty ()) {
+ state = COUNTER;
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ current.reset ();
+ lines.clear ();
+ } else {
+ lines.push_back (line);
+ }
+ break;
+ }
+ }
+
+ if (state == CONTENT) {
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ }
+
+ fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+ ContentTime r;
+
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 3);
+ r += ContentTime::from_seconds (lexical_cast<int> (a[0]) * 60 * 60);
+ r += ContentTime::from_seconds (lexical_cast<int> (a[1]) * 60);
+
+ vector<string> b;
+ boost::algorithm::split (b, a[2], boost::is_any_of (","));
+ r += ContentTime::from_seconds (lexical_cast<int> (b[0]));
+ r += ContentTime::from_seconds (lexical_cast<float> (b[1]) / 1000);
+
+ return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 2);
+ return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+ if (!p.text.empty ()) {
+ pieces.push_back (p);
+ p.text.clear ();
+ }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+ list<SubRipSubtitlePiece> pieces;
+
+ SubRipSubtitlePiece p;
+
+ enum {
+ TEXT,
+ TAG
+ } state = TEXT;
+
+ string tag;
+
+ /* XXX: missing <font> support */
+ /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+ not work, I think.
+ */
+
+ for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+ for (size_t j = 0; j < i->size(); ++j) {
+ switch (state) {
+ case TEXT:
+ if ((*i)[j] == '<' || (*i)[j] == '{') {
+ state = TAG;
+ } else {
+ p.text += (*i)[j];
+ }
+ break;
+ case TAG:
+ if ((*i)[j] == '>' || (*i)[j] == '}') {
+ if (tag == "b") {
+ maybe_content (pieces, p);
+ p.bold = true;
+ } else if (tag == "/b") {
+ maybe_content (pieces, p);
+ p.bold = false;
+ } else if (tag == "i") {
+ maybe_content (pieces, p);
+ p.italic = true;
+ } else if (tag == "/i") {
+ maybe_content (pieces, p);
+ p.italic = false;
+ } else if (tag == "u") {
+ maybe_content (pieces, p);
+ p.underline = true;
+ } else if (tag == "/u") {
+ maybe_content (pieces, p);
+ p.underline = false;
+ }
+ tag.clear ();
+ state = TEXT;
+ } else {
+ tag += (*i)[j];
+ }
+ break;
+ }
+ }
+ }
+
+ maybe_content (pieces, p);
+
+ return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+ if (_subtitles.empty ()) {
+ return ContentTime ();
+ }
+
+ return _subtitles.back().to;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644
index 000000000..e7d21675f
--- /dev/null
+++ b/src/lib/subrip.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+ SubRip (boost::shared_ptr<const SubRipContent>);
+
+ ContentTime length () const;
+
+protected:
+ std::vector<SubRipSubtitle> _subtitles;
+
+private:
+ friend class subrip_time_test;
+ friend class subrip_coordinate_test;
+ friend class subrip_content_test;
+ friend class subrip_parse_test;
+
+ static ContentTime convert_time (std::string);
+ static int convert_coordinate (std::string);
+ static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+ static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644
index 000000000..9524cf96b
--- /dev/null
+++ b/src/lib/subrip_content.cc
@@ -0,0 +1,110 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subrip_content.h"
+#include "util.h"
+#include "subrip.h"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+ : Content (film, path)
+ , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
+ : Content (film, node)
+ , SubtitleContent (film, node, version)
+ , _length (node->number_child<int64_t> ("Length"))
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job> job)
+{
+ Content::examine (job);
+ SubRip s (shared_from_this ());
+ shared_ptr<const Film> film = _film.lock ();
+ DCPTime len (s.length (), film->active_frame_rate_change (position ()));
+
+ boost::mutex::scoped_lock lm (_mutex);
+ _length = len;
+}
+
+string
+SubRipContent::summary () const
+{
+ return path_summary() + " " + _("[subtitles]");
+}
+
+string
+SubRipContent::technical_summary () const
+{
+ return Content::technical_summary() + " - " + _("SubRip subtitles");
+}
+
+string
+SubRipContent::information () const
+{
+
+}
+
+void
+SubRipContent::as_xml (xmlpp::Node* node) const
+{
+ LocaleGuard lg;
+
+ node->add_child("Type")->add_child_text ("SubRip");
+ Content::as_xml (node);
+ SubtitleContent::as_xml (node);
+ node->add_child("Length")->add_child_text (lexical_cast<string> (_length.get ()));
+}
+
+DCPTime
+SubRipContent::full_length () const
+{
+ /* XXX: this assumes that the timing of the SubRip file is appropriate
+ for the DCP's frame rate.
+ */
+ return _length;
+}
+
+string
+SubRipContent::identifier () const
+{
+ LocaleGuard lg;
+
+ stringstream s;
+ s << Content::identifier()
+ << "_" << subtitle_scale()
+ << "_" << subtitle_x_offset()
+ << "_" << subtitle_y_offset();
+
+ return s.str ();
+}
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
new file mode 100644
index 000000000..3a8380cec
--- /dev/null
+++ b/src/lib/subrip_content.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+ SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+ boost::shared_ptr<SubRipContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *) const;
+ DCPTime full_length () const;
+ std::string identifier () const;
+
+private:
+ DCPTime _length;
+};
diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc
new file mode 100644
index 000000000..013c6fab7
--- /dev/null
+++ b/src/lib/subrip_decoder.cc
@@ -0,0 +1,75 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <dcp/subtitle_string.h>
+#include "subrip_decoder.h"
+
+using std::list;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content)
+ : SubRip (content)
+ , _next (0)
+{
+
+}
+
+void
+SubRipDecoder::seek (ContentTime time, bool)
+{
+ _next = 0;
+ list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin();
+ while (i != _subtitles[_next].pieces.end() && _subtitles[_next].from < time) {
+ ++i;
+ }
+
+}
+
+bool
+SubRipDecoder::pass ()
+{
+ if (_next >= _subtitles.size ()) {
+ return true;
+ }
+
+ list<dcp::SubtitleString> out;
+ for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+ out.push_back (
+ dcp::SubtitleString (
+ "Arial",
+ i->italic,
+ dcp::Color (255, 255, 255),
+ 72,
+ dcp::Time (rint (_subtitles[_next].from.seconds() * 250)),
+ dcp::Time (rint (_subtitles[_next].to.seconds() * 250)),
+ 0.9,
+ dcp::BOTTOM,
+ i->text,
+ dcp::NONE,
+ dcp::Color (255, 255, 255),
+ 0,
+ 0
+ )
+ );
+ }
+
+ text_subtitle (out);
+ _next++;
+ return false;
+}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
new file mode 100644
index 000000000..ca885a2ef
--- /dev/null
+++ b/src/lib/subrip_decoder.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_DECODER_H
+#define DCPOMATIC_SUBRIP_DECODER_H
+
+#include "subtitle_decoder.h"
+#include "subrip.h"
+
+class SubRipContent;
+
+class SubRipDecoder : public SubtitleDecoder, public SubRip
+{
+public:
+ SubRipDecoder (boost::shared_ptr<const SubRipContent>);
+
+protected:
+ void seek (ContentTime time, bool accurate);
+ bool pass ();
+
+private:
+ size_t _next;
+};
+
+#endif
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
new file mode 100644
index 000000000..dd46b6c64
--- /dev/null
+++ b/src/lib/subrip_subtitle.h
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <dcp/types.h>
+#include "types.h"
+#include "dcpomatic_time.h"
+
+struct SubRipSubtitlePiece
+{
+ SubRipSubtitlePiece ()
+ : bold (false)
+ , italic (false)
+ , underline (false)
+ {}
+
+ std::string text;
+ bool bold;
+ bool italic;
+ bool underline;
+ dcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+ SubRipSubtitle ()
+ : from (0)
+ , to (0)
+ {}
+
+ ContentTime from;
+ ContentTime to;
+ boost::optional<int> x1;
+ boost::optional<int> x2;
+ boost::optional<int> y1;
+ boost::optional<int> y2;
+ std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc
deleted file mode 100644
index 0d18861c4..000000000
--- a/src/lib/subtitle.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "subtitle.h"
-#include "subtitle_content.h"
-#include "piece.h"
-#include "image.h"
-#include "scaler.h"
-#include "film.h"
-
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-using boost::weak_ptr;
-
-Subtitle::Subtitle (shared_ptr<const Film> film, libdcp::Size video_container_size, weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
- : _piece (weak_piece)
- , _in_image (image)
- , _in_rect (rect)
- , _in_from (from)
- , _in_to (to)
-{
- update (film, video_container_size);
-}
-
-void
-Subtitle::update (shared_ptr<const Film> film, libdcp::Size video_container_size)
-{
- shared_ptr<Piece> piece = _piece.lock ();
- if (!piece) {
- return;
- }
-
- if (!_in_image) {
- _out_image.reset ();
- return;
- }
-
- shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
- assert (sc);
-
- dcpomatic::Rect<double> in_rect = _in_rect;
- libdcp::Size scaled_size;
-
- in_rect.x += sc->subtitle_x_offset ();
- in_rect.y += sc->subtitle_y_offset ();
-
- /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
- scaled_size.width = in_rect.width * video_container_size.width * sc->subtitle_scale ();
- scaled_size.height = in_rect.height * video_container_size.height * sc->subtitle_scale ();
-
- /* Then we need a corrective translation, consisting of two parts:
- *
- * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
- * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
- *
- * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
- * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
- * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
- *
- * Combining these two translations gives these expressions.
- */
-
- _out_position.x = rint (video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
- _out_position.y = rint (video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
-
- _out_image = _in_image->scale (
- scaled_size,
- Scaler::from_id ("bicubic"),
- _in_image->pixel_format (),
- true
- );
-
- /* XXX: hack */
- Time from = _in_from;
- Time to = _in_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_from = from + piece->content->position ();
- _out_to = to + piece->content->position ();
-
- check_out_to ();
-}
-
-bool
-Subtitle::covers (Time t) const
-{
- return _out_from <= t && t <= _out_to;
-}
-
-void
-Subtitle::check_out_to ()
-{
- if (_stop && _out_to > _stop.get ()) {
- _out_to = _stop.get ();
- }
-}
diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h
deleted file mode 100644
index c74f5c1b9..000000000
--- a/src/lib/subtitle.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-#include <boost/optional.hpp>
-#include <libdcp/util.h>
-#include "rect.h"
-#include "types.h"
-
-class Film;
-class Piece;
-class Image;
-
-class Subtitle
-{
-public:
-
- Subtitle (boost::shared_ptr<const Film>, libdcp::Size, boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
-
- void update (boost::shared_ptr<const Film>, libdcp::Size);
- void set_stop (Time t) {
- _stop = t;
- check_out_to ();
- }
-
- bool covers (Time t) const;
- bool ends_before (Time t) const {
- return _out_to < t;
- }
-
- boost::shared_ptr<Image> out_image () const {
- return _out_image;
- }
-
- Position<int> out_position () const {
- return _out_position;
- }
-
-private:
- void check_out_to ();
-
- boost::weak_ptr<Piece> _piece;
- boost::shared_ptr<Image> _in_image;
- dcpomatic::Rect<double> _in_rect;
- Time _in_from;
- Time _in_to;
-
- boost::shared_ptr<Image> _out_image;
- Position<int> _out_position;
- Time _out_from;
- Time _out_to;
-
- /** Time at which this subtitle should stop (overriding _out_to) */
- boost::optional<Time> _stop;
-};
diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc
index 8f88574e5..4c6e60192 100644
--- a/src/lib/subtitle_content.cc
+++ b/src/lib/subtitle_content.cc
@@ -26,6 +26,7 @@
using std::string;
using std::vector;
+using std::cout;
using boost::shared_ptr;
using boost::lexical_cast;
using boost::dynamic_pointer_cast;
diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc
index c06f3d718..3daa6e431 100644
--- a/src/lib/subtitle_decoder.cc
+++ b/src/lib/subtitle_decoder.cc
@@ -20,20 +20,53 @@
#include <boost/shared_ptr.hpp>
#include "subtitle_decoder.h"
+using std::list;
using boost::shared_ptr;
+using boost::optional;
-SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
- : Decoder (f)
+SubtitleDecoder::SubtitleDecoder ()
{
}
-
-/** Called by subclasses when a subtitle is ready.
+/** Called by subclasses when an image subtitle is ready.
* Image may be 0 to say that there is no current subtitle.
*/
void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::image_subtitle (ContentTime from, ContentTime to, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
+{
+ _decoded_image_subtitles.push_back (shared_ptr<ContentImageSubtitle> (new ContentImageSubtitle (from, to, image, rect)));
+}
+
+void
+SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s)
+{
+ _decoded_text_subtitles.push_back (shared_ptr<ContentTextSubtitle> (new ContentTextSubtitle (s)));
+}
+
+template <class T>
+list<shared_ptr<T> >
+get (list<shared_ptr<T> > const & subs, ContentTime from, ContentTime to)
+{
+ /* XXX: inefficient */
+ list<shared_ptr<T> > out;
+ for (typename list<shared_ptr<T> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ if ((*i)->from() <= to && (*i)->to() >= from) {
+ out.push_back (*i);
+ }
+ }
+
+ return out;
+}
+
+list<shared_ptr<ContentTextSubtitle> >
+SubtitleDecoder::get_text_subtitles (ContentTime from, ContentTime to)
+{
+ return get<ContentTextSubtitle> (_decoded_text_subtitles, from, to);
+}
+
+list<shared_ptr<ContentImageSubtitle> >
+SubtitleDecoder::get_image_subtitles (ContentTime from, ContentTime to)
{
- Subtitle (image, rect, from, to);
+ return get<ContentImageSubtitle> (_decoded_image_subtitles, from, to);
}
diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h
index eeeadbd3f..efa90fd92 100644
--- a/src/lib/subtitle_decoder.h
+++ b/src/lib/subtitle_decoder.h
@@ -17,22 +17,33 @@
*/
-#include <boost/signals2.hpp>
+#ifndef DCPOMATIC_SUBTITLE_DECODER_H
+#define DCPOMATIC_SUBTITLE_DECODER_H
+
+#include <dcp/subtitle_string.h>
#include "decoder.h"
#include "rect.h"
#include "types.h"
+#include "content_subtitle.h"
class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
class Image;
class SubtitleDecoder : public virtual Decoder
{
public:
- SubtitleDecoder (boost::shared_ptr<const Film>);
+ SubtitleDecoder ();
- boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
+ std::list<boost::shared_ptr<ContentImageSubtitle> > get_image_subtitles (ContentTime from, ContentTime to);
+ std::list<boost::shared_ptr<ContentTextSubtitle> > get_text_subtitles (ContentTime from, ContentTime to);
protected:
- void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+ void image_subtitle (ContentTime from, ContentTime to, boost::shared_ptr<Image>, dcpomatic::Rect<double>);
+ void text_subtitle (std::list<dcp::SubtitleString>);
+
+ std::list<boost::shared_ptr<ContentImageSubtitle> > _decoded_image_subtitles;
+ std::list<boost::shared_ptr<ContentTextSubtitle> > _decoded_text_subtitles;
};
+
+#endif
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 7b304cb35..97e8bd416 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -34,6 +34,7 @@ using std::string;
using std::stringstream;
using std::fixed;
using std::setprecision;
+using std::cout;
using boost::shared_ptr;
/** @param s Film to use.
@@ -72,9 +73,6 @@ TranscodeJob::run ()
_transcoder.reset ();
} catch (...) {
- set_progress (1);
- set_state (FINISHED_ERROR);
- _film->log()->log (N_("Transcode job failed or cancelled"));
_transcoder.reset ();
throw;
}
@@ -103,6 +101,7 @@ TranscodeJob::status () const
return s.str ();
}
+/** @return Approximate remaining time in seconds */
int
TranscodeJob::remaining_time () const
{
@@ -120,6 +119,5 @@ TranscodeJob::remaining_time () const
}
/* Compute approximate proposed length here, as it's only here that we need it */
- OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
- return left / fps;
+ return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps;
}
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 1c8f7e3eb..810606391 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -35,49 +35,39 @@
#include "job.h"
using std::string;
+using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
-static void
-video_proxy (weak_ptr<Encoder> encoder, shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, bool same)
-{
- shared_ptr<Encoder> e = encoder.lock ();
- if (e) {
- e->process_video (image, eyes, conversion, same);
- }
-}
-
-static void
-audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
-{
- shared_ptr<Encoder> e = encoder.lock ();
- if (e) {
- e->process_audio (audio);
- }
-}
-
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
* @param e Encoder to use.
*/
Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
- : _player (f->make_player ())
+ : _film (f)
+ , _player (f->make_player ())
, _encoder (new Encoder (f, j))
, _finishing (false)
{
- _player->Video.connect (bind (video_proxy, _encoder, _1, _2, _3, _4));
- _player->Audio.connect (bind (audio_proxy, _encoder, _1));
+
}
void
Transcoder::go ()
{
_encoder->process_begin ();
- while (!_player->pass ()) {}
+
+ DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ());
+ for (DCPTime t; t < _film->length(); t += frame) {
+ _encoder->process_video (_player->get_video (t, true));
+ _encoder->process_audio (_player->get_audio (t, frame, true));
+ }
_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 c255bd0d8..35c7a91f9 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -23,7 +23,9 @@
#include <vector>
#include <stdint.h>
#include <boost/shared_ptr.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include "dcpomatic_time.h"
+#include "position.h"
class Content;
class VideoContent;
@@ -38,31 +40,29 @@ class AudioBuffers;
*/
#define SERVER_LINK_VERSION 1
-typedef int64_t Time;
-#define TIME_MAX INT64_MAX
-#define TIME_HZ ((Time) 96000)
-typedef int64_t OutputAudioFrame;
-typedef int OutputVideoFrame;
typedef std::vector<boost::shared_ptr<Content> > ContentList;
typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
typedef std::vector<boost::shared_ptr<SubtitleContent> > SubtitleContentList;
typedef std::vector<boost::shared_ptr<FFmpegContent> > FFmpegContentList;
-template<class T>
+typedef int64_t VideoFrame;
+typedef int64_t AudioFrame;
+
+/* XXX -> DCPAudio */
struct TimedAudioBuffers
{
TimedAudioBuffers ()
: time (0)
{}
- TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
+ TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, DCPTime t)
: audio (a)
, time (t)
{}
boost::shared_ptr<AudioBuffers> audio;
- T time;
+ DCPTime time;
};
enum VideoFrameType
@@ -102,7 +102,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 f15d2283c..c23deb59e 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
Copyright (C) 2000-2007 Paul Davis
This program is free software; you can redistribute it and/or modify
@@ -46,12 +46,13 @@
#include <glib.h>
#include <openjpeg.h>
#include <openssl/md5.h>
+#include <pangomm/init.h>
#include <magick/MagickCore.h>
#include <magick/version.h>
-#include <libdcp/version.h>
-#include <libdcp/util.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/signer.h>
+#include <dcp/version.h>
+#include <dcp/util.h>
+#include <dcp/signer_chain.h>
+#include <dcp/signer.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
@@ -70,6 +71,7 @@ extern "C" {
#include "job.h"
#include "cross.h"
#include "video_content.h"
+#include "rect.h"
#ifdef DCPOMATIC_WINDOWS
#include "stack.hpp"
#endif
@@ -101,7 +103,7 @@ using boost::shared_ptr;
using boost::thread;
using boost::lexical_cast;
using boost::optional;
-using libdcp::Size;
+using dcp::Size;
static boost::thread::id ui_thread;
static boost::filesystem::path backtrace_file;
@@ -237,24 +239,6 @@ ffmpeg_version_to_string (int v)
return s.str ();
}
-/** Return a user-readable string summarising the versions of our dependencies */
-string
-dependency_version_summary ()
-{
- stringstream s;
- s << N_("libopenjpeg ") << opj_version () << N_(", ")
- << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
- << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
- << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
- << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
- << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
- << MagickVersion << N_(", ")
- << N_("libssh ") << ssh_version (0) << N_(", ")
- << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
-
- return s.str ();
-}
-
double
seconds (struct timeval t)
{
@@ -341,7 +325,8 @@ dcpomatic_setup ()
set_terminate (terminate);
- libdcp::init ();
+ Pango::init ();
+ dcp::init ();
Ratio::setup_ratios ();
VideoContentScale::setup_scales ();
@@ -490,33 +475,6 @@ md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job)
return s.str ();
}
-static bool
-about_equal (float a, float b)
-{
- /* A film of F seconds at f FPS will be Ff frames;
- Consider some delta FPS d, so if we run the same
- film at (f + d) FPS it will last F(f + d) seconds.
-
- Hence the difference in length over the length of the film will
- be F(f + d) - Ff frames
- = Ff + Fd - Ff frames
- = Fd frames
- = Fd/f seconds
-
- So if we accept a difference of 1 frame, ie 1/f seconds, we can
- say that
-
- 1/f = Fd/f
- ie 1 = Fd
- ie d = 1/F
-
- So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
- FPS error is 1/F ~= 0.0001 ~= 10-e4
- */
-
- return (fabs (a - b) < 1e-4);
-}
-
/** @param An arbitrary audio frame rate.
* @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
*/
@@ -776,17 +734,6 @@ ensure_ui_thread ()
assert (boost::this_thread::get_id() == ui_thread);
}
-/** @param v Content video frame.
- * @param audio_sample_rate Source audio sample rate.
- * @param frames_per_second Number of video frames per second.
- * @return Equivalent number of audio frames for `v'.
- */
-int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
-{
- return ((int64_t) v * audio_sample_rate / frames_per_second);
-}
-
string
audio_channel_name (int c)
{
@@ -814,44 +761,6 @@ audio_channel_name (int c)
return channels[c];
}
-FrameRateConversion::FrameRateConversion (float source, int dcp)
- : skip (false)
- , repeat (1)
- , change_speed (false)
-{
- if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
- /* The difference between source and DCP frame rate will be lower
- (i.e. better) if we skip.
- */
- skip = true;
- } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
- /* The difference between source and DCP frame rate would be better
- if we repeated each frame once; it may be better still if we
- repeated more than once. Work out the required repeat.
- */
- repeat = round (dcp / source);
- }
-
- change_speed = !about_equal (source * factor(), dcp);
-
- if (!skip && repeat == 1 && !change_speed) {
- description = _("Content and DCP have the same rate.\n");
- } else {
- if (skip) {
- description = _("DCP will use every other frame of the content.\n");
- } else if (repeat == 2) {
- description = _("Each content frame will be doubled in the DCP.\n");
- } else if (repeat > 2) {
- description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
- }
-
- if (change_speed) {
- float const pc = dcp * 100 / (source * factor());
- description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
- }
- }
-}
-
LocaleGuard::LocaleGuard ()
: _old (0)
{
@@ -894,7 +803,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 ();
@@ -914,37 +823,37 @@ make_signer ()
if (!boost::filesystem::exists (p)) {
boost::filesystem::remove_all (sd);
boost::filesystem::create_directories (sd);
- libdcp::make_signer_chain (sd, openssl_path ());
+ dcp::make_signer_chain (sd, openssl_path ());
break;
}
++i;
}
- libdcp::CertificateChain chain;
+ dcp::CertificateChain chain;
{
boost::filesystem::path p (sd);
p /= "ca.self-signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
{
boost::filesystem::path p (sd);
p /= "intermediate.signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
{
boost::filesystem::path p (sd);
p /= "leaf.signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
boost::filesystem::path signer_key (sd);
signer_key /= "leaf.key";
- return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
+ return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
}
map<string, string>
@@ -993,14 +902,14 @@ split_get_request (string url)
return r;
}
-libdcp::Size
-fit_ratio_within (float ratio, libdcp::Size full_frame)
+dcp::Size
+fit_ratio_within (float ratio, dcp::Size full_frame)
{
if (ratio < full_frame.ratio ()) {
- return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
+ return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
}
- return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
+ return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
}
void *
@@ -1031,6 +940,24 @@ divide_with_round (int64_t a, int64_t b)
}
}
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
+ stringstream s;
+ s << N_("libopenjpeg ") << opj_version () << N_(", ")
+ << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+ << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+ << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+ << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+ << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+ << MagickVersion << N_(", ")
+ << N_("libssh ") << ssh_version (0) << N_(", ")
+ << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
+
+ return s.str ();
+}
+
ScopedTemporary::ScopedTemporary ()
: _open (0)
{
diff --git a/src/lib/util.h b/src/lib/util.h
index 0bbab8305..579b1c231 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -31,7 +31,8 @@
#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include <dcp/signer.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
@@ -76,44 +77,10 @@ extern bool valid_image_file (boost::filesystem::path);
extern boost::filesystem::path mo_path ();
#endif
extern std::string tidy_for_filename (std::string);
-extern boost::shared_ptr<const libdcp::Signer> make_signer ();
-extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
+extern boost::shared_ptr<const dcp::Signer> make_signer ();
+extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
extern std::string entities_to_text (std::string e);
extern std::map<std::string, std::string> split_get_request (std::string url);
-
-struct FrameRateConversion
-{
- FrameRateConversion (float, int);
-
- /** @return factor by which to multiply a source frame rate
- to get the effective rate after any skip or repeat has happened.
- */
- float factor () const {
- if (skip) {
- return 0.5;
- }
-
- return repeat;
- }
-
- /** true to skip every other frame */
- bool skip;
- /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
- int repeat;
- /** true if this DCP will run its video faster or slower than the source
- * without taking into account `repeat' nor `skip'.
- * (e.g. change_speed will be true if
- * source is 29.97fps, DCP is 30fps
- * source is 14.50fps, DCP is 30fps
- * but not if
- * source is 15.00fps, DCP is 30fps
- * source is 12.50fps, DCP is 25fps)
- */
- bool change_speed;
-
- std::string description;
-};
-
extern int dcp_audio_frame_rate (int);
extern int stride_round_up (int, int const *, int);
extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
@@ -164,8 +131,6 @@ private:
int _timeout;
};
-extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
-
class LocaleGuard
{
public:
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index b704b6447..9edbc104a 100644
--- a/src/lib/video_content.cc
+++ b/src/lib/video_content.cc
@@ -19,7 +19,7 @@
#include <iomanip>
#include <libcxml/cxml.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include "video_content.h"
#include "video_examiner.h"
#include "compose.hpp"
@@ -61,7 +61,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
setup_default_colour_conversion ();
}
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
@@ -84,7 +84,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
: Content (f, node)
{
- _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+ _video_length = ContentTime (node->number_child<int64_t> ("VideoLength"));
_video_size.width = node->number_child<int> ("VideoWidth");
_video_size.height = node->number_child<int> ("VideoHeight");
_video_frame_rate = node->number_child<float> ("VideoFrameRate");
@@ -155,7 +155,7 @@ void
VideoContent::as_xml (xmlpp::Node* node) const
{
boost::mutex::scoped_lock lm (_mutex);
- node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
+ node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length.get ()));
node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
@@ -171,14 +171,14 @@ VideoContent::as_xml (xmlpp::Node* node) const
void
VideoContent::setup_default_colour_conversion ()
{
- _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
+ _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
}
void
VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
{
/* These examiner calls could call other content methods which take a lock on the mutex */
- libdcp::Size const vs = d->video_size ();
+ dcp::Size const vs = d->video_size ();
float const vfr = d->video_frame_rate ();
{
@@ -319,14 +319,17 @@ VideoContent::technical_summary () const
{
return String::compose (
"video: length %1, size %2x%3, rate %4",
- video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate()
+ video_length_after_3d_combine().seconds(),
+ video_size().width,
+ video_size().height,
+ video_frame_rate()
);
}
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_3d_split () const
{
- libdcp::Size const s = video_size ();
+ dcp::Size const s = video_size ();
switch (video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
case VIDEO_FRAME_TYPE_3D_ALTERNATE:
@@ -334,9 +337,9 @@ VideoContent::video_size_after_3d_split () const
case VIDEO_FRAME_TYPE_3D_RIGHT:
return s;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
- return libdcp::Size (s.width / 2, s.height);
+ return dcp::Size (s.width / 2, s.height);
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
- return libdcp::Size (s.width, s.height / 2);
+ return dcp::Size (s.width, s.height / 2);
}
assert (false);
@@ -354,28 +357,21 @@ VideoContent::set_colour_conversion (ColourConversion c)
}
/** @return Video size after 3D split and crop */
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_crop () const
{
return crop().apply (video_size_after_3d_split ());
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding time with respect to the content.
*/
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+ContentTime
+VideoContent::dcp_time_to_content_time (DCPTime t) const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
- /* Here we are converting from time (in the DCP) to a frame number in the content.
- Hence we need to use the DCP's frame rate and the double/skip correction, not
- the source's rate.
- */
- return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
}
VideoContentScale::VideoContentScale (Ratio const * r)
@@ -452,14 +448,14 @@ VideoContentScale::name () const
/** @param display_container Size of the container that we are displaying this content in.
* @param film_container The size of the film's image.
*/
-libdcp::Size
-VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
+dcp::Size
+VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
{
if (_ratio) {
return fit_ratio_within (_ratio->ratio (), display_container);
}
- libdcp::Size const ac = c->video_size_after_crop ();
+ dcp::Size const ac = c->video_size_after_crop ();
/* Force scale if the film_container is smaller than the content's image */
if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
@@ -469,7 +465,7 @@ VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_
/* Scale the image so that it will be in the right place in film_container, even if display_container is a
different size.
*/
- return libdcp::Size (
+ return dcp::Size (
c->video_size().width * float(display_container.width) / film_container.width,
c->video_size().height * float(display_container.height) / film_container.height
);
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
index fbba9c09d..6b91997c9 100644
--- a/src/lib/video_content.h
+++ b/src/lib/video_content.h
@@ -45,7 +45,7 @@ public:
VideoContentScale (bool);
VideoContentScale (boost::shared_ptr<cxml::Node>);
- libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const;
+ dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const;
std::string id () const;
std::string name () const;
void as_xml (xmlpp::Node *) const;
@@ -81,7 +81,7 @@ public:
typedef int Frame;
VideoContent (boost::shared_ptr<const Film>);
- VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+ VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime);
VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -91,21 +91,21 @@ public:
virtual std::string information () const;
virtual std::string identifier () const;
- VideoContent::Frame video_length () const {
+ ContentTime video_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_length;
}
- VideoContent::Frame video_length_after_3d_combine () const {
+ ContentTime video_length_after_3d_combine () const {
boost::mutex::scoped_lock lm (_mutex);
if (_video_frame_type == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
- return _video_length / 2;
+ return ContentTime (_video_length.get() / 2);
}
return _video_length;
}
- libdcp::Size video_size () const {
+ dcp::Size video_size () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_size;
}
@@ -166,15 +166,15 @@ public:
return _colour_conversion;
}
- libdcp::Size video_size_after_3d_split () const;
- libdcp::Size video_size_after_crop () const;
+ dcp::Size video_size_after_3d_split () const;
+ dcp::Size video_size_after_crop () const;
- VideoContent::Frame time_to_content_video_frames (Time) const;
+ ContentTime dcp_time_to_content_time (DCPTime) const;
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
- VideoContent::Frame _video_length;
+ ContentTime _video_length;
float _video_frame_rate;
private:
@@ -185,7 +185,7 @@ private:
void setup_default_colour_conversion ();
- libdcp::Size _video_size;
+ dcp::Size _video_size;
VideoFrameType _video_frame_type;
Crop _crop;
VideoContentScale _scale;
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index 2a33a8c3a..bd609d168 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -19,52 +19,152 @@
#include "video_decoder.h"
#include "image.h"
+#include "content_video.h"
#include "i18n.h"
using std::cout;
+using std::list;
using boost::shared_ptr;
+using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
- : Decoder (f)
+VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
+#ifdef DCPOMATIC_DEBUG
+ : test_gaps (0)
, _video_content (c)
- , _video_position (0)
+#else
+ : _video_content (c)
+#endif
{
}
+optional<ContentVideo>
+VideoDecoder::decoded_video (VideoFrame frame)
+{
+ for (list<ContentVideo>::const_iterator i = _decoded_video.begin(); i != _decoded_video.end(); ++i) {
+ if (i->frame == frame) {
+ return *i;
+ }
+ }
+
+ return optional<ContentVideo> ();
+}
+
+optional<ContentVideo>
+VideoDecoder::get_video (VideoFrame frame, bool accurate)
+{
+ if (_decoded_video.empty() || (frame < _decoded_video.front().frame || frame > (_decoded_video.back().frame + 1))) {
+ /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+ seek (ContentTime::from_frames (frame, _video_content->video_frame_rate()), accurate);
+ }
+
+ optional<ContentVideo> dec;
+
+ /* Now enough pass() calls should either:
+ * (a) give us what we want, or
+ * (b) hit the end of the decoder.
+ */
+ if (accurate) {
+ /* We are being accurate, so we want the right frame.
+ * This could all be one statement but it's split up for clarity.
+ */
+ while (true) {
+ if (decoded_video (frame)) {
+ /* We got what we want */
+ break;
+ }
+
+ if (pass ()) {
+ /* The decoder has nothing more for us */
+ break;
+ }
+
+ if (!_decoded_video.empty() && _decoded_video.front().frame > frame) {
+ /* We're never going to get the frame we want. Perhaps the caller is asking
+ * for a video frame before the content's video starts (if its audio
+ * begins before its video, for example).
+ */
+ break;
+ }
+ }
+
+ dec = decoded_video (frame);
+ } else {
+ /* Any frame will do: use the first one that comes out of pass() */
+ while (_decoded_video.empty() && !pass ()) {}
+ if (!_decoded_video.empty ()) {
+ dec = _decoded_video.front ();
+ }
+ }
+
+ /* Clean up decoded_video */
+ while (!_decoded_video.empty() && _decoded_video.front().frame < (frame - 1)) {
+ _decoded_video.pop_front ();
+ }
+
+ return dec;
+}
+
+
+/** 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, VideoFrame frame)
{
+ /* We should not receive the same thing twice */
+ assert (_decoded_video.empty() || frame != _decoded_video.back().frame);
+
+ /* Fill in gaps */
+ /* XXX: 3D */
+
+ while (!_decoded_video.empty () && (_decoded_video.back().frame + 1) < frame) {
+#ifdef DCPOMATIC_DEBUG
+ test_gaps++;
+#endif
+ _decoded_video.push_back (
+ ContentVideo (
+ _decoded_video.back().image,
+ _decoded_video.back().eyes,
+ _decoded_video.back().frame + 1
+ )
+ );
+ }
+
switch (_video_content->video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
- Video (image, EYES_BOTH, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_BOTH, frame));
break;
case VIDEO_FRAME_TYPE_3D_ALTERNATE:
- Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, same, frame / 2);
+ _decoded_video.push_back (ContentVideo (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, 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);
+ _decoded_video.push_back (ContentVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, frame));
+ _decoded_video.push_back (ContentVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, 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);
+ _decoded_video.push_back (ContentVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, frame));
+ _decoded_video.push_back (ContentVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, frame));
break;
}
case VIDEO_FRAME_TYPE_3D_LEFT:
- Video (image, EYES_LEFT, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_LEFT, frame));
break;
case VIDEO_FRAME_TYPE_3D_RIGHT:
- Video (image, EYES_RIGHT, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, frame));
break;
+ default:
+ assert (false);
}
-
- _video_position = frame + 1;
+}
+
+void
+VideoDecoder::seek (ContentTime, bool)
+{
+ _decoded_video.clear ();
}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 255a038a9..8715b9714 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@
#include "decoder.h"
#include "video_content.h"
#include "util.h"
+#include "content_video.h"
class VideoContent;
class Image;
@@ -32,29 +33,26 @@ class Image;
class VideoDecoder : public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
-
- /** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- */
- virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is the eye(s) which should see this image.
- * Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
- * Fourth parameter is the frame within our source.
- */
- boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
-
+ VideoDecoder (boost::shared_ptr<const VideoContent> c);
+
+ boost::optional<ContentVideo> get_video (VideoFrame frame, bool accurate);
+
+ boost::shared_ptr<const VideoContent> video_content () const {
+ return _video_content;
+ }
+
+#ifdef DCPOMATIC_DEBUG
+ int test_gaps;
+#endif
+
protected:
- void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ void seek (ContentTime time, bool accurate);
+ void video (boost::shared_ptr<const Image>, VideoFrame frame);
+ boost::optional<ContentVideo> decoded_video (VideoFrame frame);
+
boost::shared_ptr<const VideoContent> _video_content;
- /** This is in frames without taking 3D into account (e.g. if we are doing 3D alternate,
- * this would equal 2 on the left-eye second frame (not 1)).
- */
- VideoContent::Frame _video_position;
+ std::list<ContentVideo> _decoded_video;
};
#endif
diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h
index 039c494b5..d4897213b 100644
--- a/src/lib/video_examiner.h
+++ b/src/lib/video_examiner.h
@@ -17,7 +17,7 @@
*/
-#include <libdcp/types.h>
+#include <dcp/types.h>
#include "types.h"
#include "video_content.h"
@@ -26,6 +26,6 @@ class VideoExaminer
public:
virtual ~VideoExaminer () {}
virtual float video_frame_rate () const = 0;
- virtual libdcp::Size video_size () const = 0;
- virtual VideoContent::Frame video_length () const = 0;
+ virtual dcp::Size video_size () const = 0;
+ virtual ContentTime video_length () const = 0;
};
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index 639685149..306f6d7f4 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -19,12 +19,16 @@
#include <fstream>
#include <cerrno>
-#include <libdcp/mono_picture_asset.h>
-#include <libdcp/stereo_picture_asset.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/reel.h>
-#include <libdcp/dcp.h>
-#include <libdcp/cpl.h>
+#include <dcp/mono_picture_mxf.h>
+#include <dcp/stereo_picture_mxf.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_mxf_writer.h>
+#include <dcp/reel.h>
+#include <dcp/reel_mono_picture_asset.h>
+#include <dcp/reel_stereo_picture_asset.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/dcp.h>
+#include <dcp/cpl.h>
#include "writer.h"
#include "compose.hpp"
#include "film.h"
@@ -37,6 +41,7 @@
#include "config.h"
#include "job.h"
#include "cross.h"
+#include "audio_buffers.h"
#include "i18n.h"
@@ -51,6 +56,7 @@ using std::cout;
using std::stringstream;
using boost::shared_ptr;
using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
@@ -65,7 +71,6 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
, _last_written_eyes (EYES_RIGHT)
, _full_written (0)
, _fake_written (0)
- , _repeat_written (0)
, _pushed_to_disk (0)
{
/* Remove any old DCP */
@@ -83,36 +88,34 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
*/
if (_film->three_d ()) {
- _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
} else {
- _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
}
- _picture_asset->set_edit_rate (_film->video_frame_rate ());
- _picture_asset->set_size (_film->frame_size ());
- _picture_asset->set_interop (_film->interop ());
+ _picture_mxf->set_size (_film->frame_size ());
if (_film->encrypted ()) {
- _picture_asset->set_key (_film->key ());
+ _picture_mxf->set_key (_film->key ());
}
- _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+ _picture_mxf_writer = _picture_mxf->start_write (
+ _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
+ _film->interop() ? dcp::INTEROP : dcp::SMPTE,
+ _first_nonexistant_frame > 0
+ );
if (_film->audio_channels ()) {
- _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
- _sound_asset->set_edit_rate (_film->video_frame_rate ());
- _sound_asset->set_channels (_film->audio_channels ());
- _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
- _sound_asset->set_interop (_film->interop ());
+ _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
if (_film->encrypted ()) {
- _sound_asset->set_key (_film->key ());
+ _sound_mxf->set_key (_film->key ());
}
-
- /* Write the sound asset into the film directory so that we leave the creation
+
+ /* Write the sound MXF into the film directory so that we leave the creation
of the DCP directory until the last minute.
*/
- _sound_asset_writer = _sound_asset->start_write ();
+ _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
}
_thread = new boost::thread (boost::bind (&Writer::thread, this));
@@ -166,7 +169,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;
@@ -190,8 +193,8 @@ Writer::fake_write (int frame, Eyes eyes)
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- if (_sound_asset) {
- _sound_asset_writer->write (audio->data(), audio->frames());
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->write (audio->data(), audio->frames());
}
}
@@ -265,7 +268,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;
@@ -273,39 +276,27 @@ 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 (
- _last_written[qi.eyes]->data(),
- _last_written[qi.eyes]->size()
- );
-
- _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
- ++_repeat_written;
- break;
- }
}
lock.lock ();
_last_written_frame = qi.frame;
_last_written_eyes = qi.eyes;
- if (_film->length()) {
- shared_ptr<Job> job = _job.lock ();
- assert (job);
- int total = _film->time_to_video_frames (_film->length ());
- if (_film->three_d ()) {
- /* _full_written and so on are incremented for each eye, so we need to double the total
- frames to get the correct progress.
- */
- total *= 2;
- }
- job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
+ shared_ptr<Job> job = _job.lock ();
+ assert (job);
+ int64_t total = _film->length().frames (_film->video_frame_rate ());
+ if (_film->three_d ()) {
+ /* _full_written and so on are incremented for each eye, so we need to double the total
+ frames to get the correct progress.
+ */
+ total *= 2;
+ }
+ if (total) {
+ job->set_progress (float (_full_written + _fake_written) / total);
}
}
@@ -380,15 +371,11 @@ Writer::finish ()
terminate_thread (true);
- _picture_asset_writer->finalize ();
- if (_sound_asset_writer) {
- _sound_asset_writer->finalize ();
+ _picture_mxf_writer->finalize ();
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->finalize ();
}
- int const frames = _last_written_frame + 1;
-
- _picture_asset->set_duration (frames);
-
/* Hard-link the video MXF into the DCP */
boost::filesystem::path video_from;
video_from /= _film->internal_video_mxf_dir();
@@ -406,14 +393,11 @@ 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 ());
+ _picture_mxf->set_file (video_to);
/* Move the audio MXF into the DCP */
- if (_sound_asset) {
+ if (_sound_mxf) {
boost::filesystem::path audio_to;
audio_to /= _film->dir (_film->dcp_name ());
audio_to /= _film->audio_mxf_filename ();
@@ -424,78 +408,63 @@ Writer::finish ()
String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
);
}
-
- _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
- _sound_asset->set_duration (frames);
+
+ _sound_mxf->set_file (audio_to);
}
-
- libdcp::DCP dcp (_film->dir (_film->dcp_name()));
- shared_ptr<libdcp::CPL> cpl (
- new libdcp::CPL (
- _film->dir (_film->dcp_name()),
+ dcp::DCP dcp (_film->dir (_film->dcp_name()));
+
+ shared_ptr<dcp::CPL> cpl (
+ new dcp::CPL (
_film->dcp_name(),
- _film->dcp_content_type()->libdcp_kind (),
- frames,
- _film->video_frame_rate ()
+ _film->dcp_content_type()->libdcp_kind ()
)
);
- dcp.add_cpl (cpl);
+ dcp.add (cpl);
+
+ shared_ptr<dcp::Reel> reel (new dcp::Reel ());
- cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
- _picture_asset,
- _sound_asset,
- shared_ptr<libdcp::SubtitleAsset> ()
- )
- ));
+ shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf);
+ if (mono) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0)));
+ dcp.add (mono);
+ }
+
+ shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
+ if (stereo) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
+ dcp.add (stereo);
+ }
+
+ if (_sound_mxf) {
+ reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+ dcp.add (_sound_mxf);
+ }
+
+ cpl->add (reel);
shared_ptr<Job> job = _job.lock ();
assert (job);
job->sub (_("Computing image digest"));
- _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
- if (_sound_asset) {
+ if (_sound_mxf) {
job->sub (_("Computing audio digest"));
- _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
}
- libdcp::XMLMetadata meta = 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)
+ String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk)
);
}
-/** Tell the writer that frame `f' should be a repeat of the frame before it */
-void
-Writer::repeat (int f, Eyes e)
-{
- boost::mutex::scoped_lock lock (_mutex);
-
- while (_queued_full_in_memory > _maximum_frames_in_memory) {
- _full_condition.wait (lock);
- }
-
- QueueItem qi;
- qi.type = QueueItem::REPEAT;
- qi.frame = f;
- if (_film->three_d() && e == EYES_BOTH) {
- qi.eyes = EYES_LEFT;
- _queue.push_back (qi);
- qi.eyes = EYES_RIGHT;
- _queue.push_back (qi);
- } else {
- qi.eyes = e;
- _queue.push_back (qi);
- }
-
- _empty_condition.notify_all ();
-}
-
bool
Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
{
@@ -506,7 +475,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..2ddf70380 100644
--- a/src/lib/writer.h
+++ b/src/lib/writer.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,6 +17,10 @@
*/
+/** @file src/lib/writer.h
+ * @brief Writer class.
+ */
+
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
@@ -29,15 +33,15 @@ class EncodedData;
class AudioBuffers;
class Job;
-namespace libdcp {
- class MonoPictureAsset;
- class MonoPictureAssetWriter;
- class StereoPictureAsset;
- class StereoPictureAssetWriter;
- class PictureAsset;
- class PictureAssetWriter;
- class SoundAsset;
- class SoundAssetWriter;
+namespace dcp {
+ class MonoPictureMXF;
+ class MonoPictureMXFWriter;
+ class StereoPictureMXF;
+ class StereoPictureMXFWriter;
+ class PictureMXF;
+ class PictureMXFWriter;
+ class SoundMXF;
+ class SoundMXFWriter;
}
struct QueueItem
@@ -51,8 +55,6 @@ public:
state but we use the data that is already on disk.
*/
FAKE,
- /** this is a repeat of the last frame to be written */
- REPEAT
} type;
/** encoded data for FULL */
@@ -67,6 +69,17 @@ public:
bool operator< (QueueItem const & a, QueueItem const & b);
bool operator== (QueueItem const & a, QueueItem const & b);
+/** @class Writer
+ * @brief Class to manage writing JPEG2000 and audio data to MXFs on disk.
+ *
+ * This class creates sound and picture MXFs, then takes EncodedData
+ * or AudioBuffers objects (containing image or sound data respectively)
+ * and writes them to the MXFs.
+ *
+ * ::write() for EncodedData can be called out of order, and the Writer
+ * will sort it out. write() for AudioBuffers must be called in order.
+ */
+
class Writer : public ExceptionStore, public boost::noncopyable
{
public:
@@ -123,15 +136,13 @@ private:
int _full_written;
/** number of FAKE written frames */
int _fake_written;
- /** number of REPEAT written frames */
- int _repeat_written;
/** number of frames pushed to disk and then recovered
due to the limit of frames to be held in memory.
*/
int _pushed_to_disk;
- boost::shared_ptr<libdcp::PictureAsset> _picture_asset;
- boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer;
- boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
- boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+ boost::shared_ptr<dcp::PictureMXF> _picture_mxf;
+ boost::shared_ptr<dcp::PictureMXFWriter> _picture_mxf_writer;
+ boost::shared_ptr<dcp::SoundMXF> _sound_mxf;
+ boost::shared_ptr<dcp::SoundMXFWriter> _sound_mxf_writer;
};
diff --git a/src/lib/wscript b/src/lib/wscript
index d4231fd30..433f50b3f 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -13,11 +13,13 @@ sources = """
config.cc
content.cc
content_factory.cc
+ content_subtitle.cc
cross.cc
dci_metadata.cc
dcp_content_type.cc
+ dcp_video.cc
dcp_video_frame.cc
- decoder.cc
+ dcpomatic_time.cc
dolby_cp750.cc
encoder.cc
examine_content_job.cc
@@ -30,6 +32,7 @@ sources = """
ffmpeg_examiner.cc
film.cc
filter.cc
+ frame_rate_change.cc
internet.cc
image.cc
image_content.cc
@@ -40,10 +43,10 @@ sources = """
kdm.cc
json_server.cc
log.cc
- piece.cc
player.cc
playlist.cc
ratio.cc
+ render_subtitles.cc
resampler.cc
scp_dcp_job.cc
scaler.cc
@@ -53,7 +56,9 @@ sources = """
sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
- subtitle.cc
+ subrip.cc
+ subrip_content.cc
+ subrip_decoder.cc
subtitle_content.cc
subtitle_decoder.cc
timer.cc
@@ -80,7 +85,7 @@ def build(bld):
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
- CURL ZIP QUICKMAIL
+ CURL ZIP QUICKMAIL PANGOMM CAIROMM
"""
if bld.env.TARGET_OSX: