summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ab_transcode_job.cc6
-rw-r--r--src/lib/ab_transcode_job.h6
-rw-r--r--src/lib/ab_transcoder.cc49
-rw-r--r--src/lib/ab_transcoder.h15
-rw-r--r--src/lib/analyse_audio_job.cc31
-rw-r--r--src/lib/audio_content.cc (renamed from src/lib/decoder_factory.h)44
-rw-r--r--src/lib/audio_content.h52
-rw-r--r--src/lib/audio_decoder.cc11
-rw-r--r--src/lib/audio_decoder.h23
-rw-r--r--src/lib/audio_mapping.cc131
-rw-r--r--src/lib/audio_mapping.h62
-rw-r--r--src/lib/audio_source.cc12
-rw-r--r--src/lib/config.cc89
-rw-r--r--src/lib/config.h3
-rw-r--r--src/lib/content.cc69
-rw-r--r--src/lib/content.h66
-rw-r--r--src/lib/dci_metadata.cc33
-rw-r--r--src/lib/dci_metadata.h12
-rw-r--r--src/lib/dcp_video_frame.cc1
-rw-r--r--src/lib/decoder.cc17
-rw-r--r--src/lib/decoder.h13
-rw-r--r--src/lib/decoder_factory.cc60
-rw-r--r--src/lib/encoder.cc62
-rw-r--r--src/lib/encoder.h6
-rw-r--r--src/lib/examine_content_job.cc68
-rw-r--r--src/lib/examine_content_job.h17
-rw-r--r--src/lib/exceptions.h1
-rw-r--r--src/lib/ffmpeg_content.cc291
-rw-r--r--src/lib/ffmpeg_content.h133
-rw-r--r--src/lib/ffmpeg_decoder.cc189
-rw-r--r--src/lib/ffmpeg_decoder.h68
-rw-r--r--src/lib/film.cc944
-rw-r--r--src/lib/film.h239
-rw-r--r--src/lib/filter_graph.cc2
-rw-r--r--src/lib/filter_graph.h2
-rw-r--r--src/lib/format.cc11
-rw-r--r--src/lib/format.h2
-rw-r--r--src/lib/imagemagick_content.cc99
-rw-r--r--src/lib/imagemagick_content.h45
-rw-r--r--src/lib/imagemagick_decoder.cc86
-rw-r--r--src/lib/imagemagick_decoder.h23
-rw-r--r--src/lib/job.cc2
-rw-r--r--src/lib/job.h3
-rw-r--r--src/lib/matcher.cc4
-rw-r--r--src/lib/player.cc338
-rw-r--r--src/lib/player.h82
-rw-r--r--src/lib/playlist.cc271
-rw-r--r--src/lib/playlist.h105
-rw-r--r--src/lib/scp_dcp_job.h2
-rw-r--r--src/lib/server.cc23
-rw-r--r--src/lib/server.h9
-rw-r--r--src/lib/sndfile_content.cc122
-rw-r--r--src/lib/sndfile_content.h71
-rw-r--r--src/lib/sndfile_decoder.cc163
-rw-r--r--src/lib/sndfile_decoder.h36
-rw-r--r--src/lib/stream.cc92
-rw-r--r--src/lib/stream.h121
-rw-r--r--src/lib/transcode_job.cc29
-rw-r--r--src/lib/transcode_job.h11
-rw-r--r--src/lib/transcoder.cc66
-rw-r--r--src/lib/transcoder.h22
-rw-r--r--src/lib/types.cc (renamed from src/lib/options.h)32
-rw-r--r--src/lib/types.h109
-rw-r--r--src/lib/util.cc158
-rw-r--r--src/lib/util.h103
-rw-r--r--src/lib/video_content.cc106
-rw-r--r--src/lib/video_content.h71
-rw-r--r--src/lib/video_decoder.cc36
-rw-r--r--src/lib/video_decoder.h33
-rw-r--r--src/lib/video_source.cc15
-rw-r--r--src/lib/video_source.h2
-rw-r--r--src/lib/writer.cc12
-rw-r--r--src/lib/wscript14
73 files changed, 3293 insertions, 2063 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
index 4ffdd9af6..2bdff47de 100644
--- a/src/lib/ab_transcode_job.cc
+++ b/src/lib/ab_transcode_job.cc
@@ -32,11 +32,9 @@ using std::string;
using boost::shared_ptr;
/** @param f Film to compare.
- * @param o Decode options.
*/
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o)
+ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f)
: Job (f)
- , _decode_opt (o)
{
_film_b.reset (new Film (*_film));
_film_b->set_scaler (Config::instance()->reference_scaler ());
@@ -54,7 +52,7 @@ ABTranscodeJob::run ()
{
try {
/* _film_b is the one with reference filters */
- ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film)));
+ ABTranscoder w (_film_b, _film, shared_from_this ());
w.go ();
set_progress (1);
set_state (FINISHED_OK);
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
index 8e3cbe2d8..cd82d4247 100644
--- a/src/lib/ab_transcode_job.h
+++ b/src/lib/ab_transcode_job.h
@@ -23,7 +23,6 @@
#include <boost/shared_ptr.hpp>
#include "job.h"
-#include "options.h"
class Film;
@@ -38,16 +37,13 @@ class ABTranscodeJob : public Job
{
public:
ABTranscodeJob (
- boost::shared_ptr<Film> f,
- DecodeOptions o
+ boost::shared_ptr<Film> f
);
std::string name () const;
void run ();
private:
- DecodeOptions _decode_opt;
-
/** Copy of our Film using the reference filters and scaler */
boost::shared_ptr<Film> _film_b;
};
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc
index 3a1cd83d7..81aa5a4ba 100644
--- a/src/lib/ab_transcoder.cc
+++ b/src/lib/ab_transcoder.cc
@@ -21,13 +21,10 @@
#include <boost/shared_ptr.hpp>
#include "ab_transcoder.h"
#include "film.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
#include "encoder.h"
#include "job.h"
-#include "options.h"
#include "image.h"
-#include "decoder_factory.h"
+#include "player.h"
#include "matcher.h"
#include "delay_line.h"
#include "gain.h"
@@ -49,31 +46,23 @@ using boost::dynamic_pointer_cast;
* @param e Encoder to use.
*/
-ABTranscoder::ABTranscoder (
- shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
+ABTranscoder::ABTranscoder (shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<Job> j)
: _film_a (a)
, _film_b (b)
+ , _player_a (_film_a->player ())
+ , _player_b (_film_b->player ())
, _job (j)
- , _encoder (e)
+ , _encoder (new Encoder (_film_a))
, _combiner (new Combiner (a->log()))
{
- _da = decoder_factory (_film_a, o);
- _db = decoder_factory (_film_b, o);
-
- if (_film_a->audio_stream()) {
- shared_ptr<AudioStream> st = _film_a->audio_stream();
- _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate()));
- _delay_line.reset (new DelayLine (_film_a->log(), st->channels(), _film_a->audio_delay() * st->sample_rate() / 1000));
+ if (_film_a->has_audio ()) {
+ _matcher.reset (new Matcher (_film_a->log(), _film_a->audio_frame_rate(), _film_a->video_frame_rate()));
+ _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_channels(), _film_a->audio_delay() * _film_a->audio_frame_rate() / 1000));
_gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
}
- /* Set up the decoder to use the film's set streams */
- _da.video->set_subtitle_stream (_film_a->subtitle_stream ());
- _db.video->set_subtitle_stream (_film_a->subtitle_stream ());
- _da.audio->set_audio_stream (_film_a->audio_stream ());
-
- _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
- _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
+ _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
+ _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
if (_matcher) {
_combiner->connect_video (_matcher);
@@ -83,7 +72,7 @@ ABTranscoder::ABTranscoder (
}
if (_matcher && _delay_line) {
- _da.audio->connect_audio (_delay_line);
+ _player_a->connect_audio (_delay_line);
_delay_line->connect_audio (_matcher);
_matcher->connect_audio (_gain);
_gain->connect_audio (_encoder);
@@ -95,23 +84,17 @@ ABTranscoder::go ()
{
_encoder->process_begin ();
- bool done[3] = { false, false, false };
+ bool done[2] = { false, false };
while (1) {
- done[0] = _da.video->pass ();
- done[1] = _db.video->pass ();
-
- if (!done[2] && _da.audio && dynamic_pointer_cast<Decoder> (_da.audio) != dynamic_pointer_cast<Decoder> (_da.video)) {
- done[2] = _da.audio->pass ();
- } else {
- done[2] = true;
- }
+ done[0] = _player_a->pass ();
+ done[1] = _player_b->pass ();
if (_job) {
- _da.video->set_progress (_job);
+ _player_a->set_progress (_job);
}
- if (done[0] && done[1] && done[2]) {
+ if (done[0] && done[1]) {
break;
}
}
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
index 58a08af04..b1b01d724 100644
--- a/src/lib/ab_transcoder.h
+++ b/src/lib/ab_transcoder.h
@@ -25,20 +25,17 @@
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include "util.h"
-#include "decoder_factory.h"
class Job;
class Encoder;
-class VideoDecoder;
-class AudioDecoder;
class Image;
class Log;
-class Subtitle;
class Film;
class Matcher;
class DelayLine;
class Gain;
class Combiner;
+class Player;
/** @class ABTranscoder
* @brief A transcoder which uses one Film for the left half of the screen, and a different one
@@ -50,9 +47,7 @@ public:
ABTranscoder (
boost::shared_ptr<Film> a,
boost::shared_ptr<Film> b,
- DecodeOptions o,
- Job* j,
- boost::shared_ptr<Encoder> e
+ boost::shared_ptr<Job> j
);
void go ();
@@ -60,10 +55,10 @@ public:
private:
boost::shared_ptr<Film> _film_a;
boost::shared_ptr<Film> _film_b;
- Job* _job;
+ boost::shared_ptr<Player> _player_a;
+ boost::shared_ptr<Player> _player_b;
+ boost::shared_ptr<Job> _job;
boost::shared_ptr<Encoder> _encoder;
- Decoders _da;
- Decoders _db;
boost::shared_ptr<Combiner> _combiner;
boost::shared_ptr<Matcher> _matcher;
boost::shared_ptr<DelayLine> _delay_line;
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
index 43eecbcbd..50096d7c1 100644
--- a/src/lib/analyse_audio_job.cc
+++ b/src/lib/analyse_audio_job.cc
@@ -21,9 +21,7 @@
#include "analyse_audio_job.h"
#include "compose.hpp"
#include "film.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "audio_decoder.h"
+#include "player.h"
#include "i18n.h"
@@ -52,29 +50,18 @@ AnalyseAudioJob::name () const
void
AnalyseAudioJob::run ()
{
- if (!_film->audio_stream () || !_film->length()) {
- set_progress (1);
- set_state (FINISHED_ERROR);
- return;
- }
-
- DecodeOptions options;
- options.decode_video = false;
-
- Decoders decoders = decoder_factory (_film, options);
- assert (decoders.audio);
+ shared_ptr<Player> player = _film->player ();
+ player->disable_video ();
- decoders.audio->set_audio_stream (_film->audio_stream ());
- decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+ player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
- int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->source_frame_rate());
- _samples_per_point = max (int64_t (1), total_audio_frames / _num_points);
+ _samples_per_point = max (int64_t (1), _film->audio_length() / _num_points);
- _current.resize (_film->audio_stream()->channels ());
- _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels()));
+ _current.resize (MAX_AUDIO_CHANNELS);
+ _analysis.reset (new AudioAnalysis (MAX_AUDIO_CHANNELS));
- while (!decoders.audio->pass()) {
- set_progress (float (_done) / total_audio_frames);
+ while (!player->pass()) {
+ set_progress (float (_done) / _film->audio_length ());
}
_analysis->write (_film->audio_analysis_path ());
diff --git a/src/lib/decoder_factory.h b/src/lib/audio_content.cc
index 8076b01c7..9968f4725 100644
--- a/src/lib/decoder_factory.h
+++ b/src/lib/audio_content.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,33 +17,29 @@
*/
-#ifndef DVDOMATIC_DECODER_FACTORY_H
-#define DVDOMATIC_DECODER_FACTORY_H
+#include <libcxml/cxml.h>
+#include "audio_content.h"
-/** @file src/decoder_factory.h
- * @brief A method to create appropriate decoders for some content.
- */
+using boost::shared_ptr;
-#include "options.h"
+int const AudioContentProperty::AUDIO_CHANNELS = 200;
+int const AudioContentProperty::AUDIO_LENGTH = 201;
+int const AudioContentProperty::AUDIO_FRAME_RATE = 202;
-class Film;
-class VideoDecoder;
-class AudioDecoder;
+AudioContent::AudioContent (boost::filesystem::path f)
+ : Content (f)
+{
-struct Decoders {
- Decoders () {}
-
- Decoders (boost::shared_ptr<VideoDecoder> v, boost::shared_ptr<AudioDecoder> a)
- : video (v)
- , audio (a)
- {}
+}
- boost::shared_ptr<VideoDecoder> video;
- boost::shared_ptr<AudioDecoder> audio;
-};
+AudioContent::AudioContent (shared_ptr<const cxml::Node> node)
+ : Content (node)
+{
-extern Decoders decoder_factory (
- boost::shared_ptr<Film>, DecodeOptions
- );
+}
-#endif
+AudioContent::AudioContent (AudioContent const & o)
+ : Content (o)
+{
+
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
new file mode 100644
index 000000000..d5dbf266b
--- /dev/null
+++ b/src/lib/audio_content.h
@@ -0,0 +1,52 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_CONTENT_H
+#define DVDOMATIC_AUDIO_CONTENT_H
+
+#include "content.h"
+#include "util.h"
+
+namespace cxml {
+ class Node;
+}
+
+class AudioContentProperty
+{
+public:
+ static int const AUDIO_CHANNELS;
+ static int const AUDIO_LENGTH;
+ static int const AUDIO_FRAME_RATE;
+};
+
+class AudioContent : public virtual Content
+{
+public:
+ AudioContent (boost::filesystem::path);
+ AudioContent (boost::shared_ptr<const cxml::Node>);
+ AudioContent (AudioContent const &);
+
+ virtual int audio_channels () const = 0;
+ virtual ContentAudioFrame audio_length () const = 0;
+ virtual int audio_frame_rate () const = 0;
+ virtual int64_t audio_channel_layout () const = 0;
+
+};
+
+#endif
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index a54c14843..df13a984a 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -18,19 +18,12 @@
*/
#include "audio_decoder.h"
-#include "stream.h"
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> f)
+ : Decoder (f)
{
}
-
-void
-AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s)
-{
- _audio_stream = s;
-}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index 9bef8e0e7..24e2796ae 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -25,34 +25,17 @@
#define DVDOMATIC_AUDIO_DECODER_H
#include "audio_source.h"
-#include "stream.h"
#include "decoder.h"
+class AudioContent;
+
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
*/
class AudioDecoder : public AudioSource, public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
- virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
-
- /** @return Audio stream that we are using */
- boost::shared_ptr<AudioStream> audio_stream () const {
- return _audio_stream;
- }
-
- /** @return All available audio streams */
- std::vector<boost::shared_ptr<AudioStream> > audio_streams () const {
- return _audio_streams;
- }
-
-protected:
- /** Audio stream that we are using */
- boost::shared_ptr<AudioStream> _audio_stream;
- /** All available audio streams */
- std::vector<boost::shared_ptr<AudioStream> > _audio_streams;
+ AudioDecoder (boost::shared_ptr<const Film>);
};
#endif
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
new file mode 100644
index 000000000..b85ea7314
--- /dev/null
+++ b/src/lib/audio_mapping.cc
@@ -0,0 +1,131 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/lexical_cast.hpp>
+#include <libcxml/cxml.h>
+#include "audio_mapping.h"
+
+using std::list;
+using std::cout;
+using std::make_pair;
+using std::pair;
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+void
+AudioMapping::add (Channel c, libdcp::Channel d)
+{
+ _content_to_dcp.push_back (make_pair (c, d));
+}
+
+/* XXX: this is grotty */
+int
+AudioMapping::dcp_channels () const
+{
+ for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (((int) i->second) > 2) {
+ return 6;
+ }
+ }
+
+ return 2;
+}
+
+list<AudioMapping::Channel>
+AudioMapping::dcp_to_content (libdcp::Channel d) const
+{
+ list<AudioMapping::Channel> c;
+ for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (i->second == d) {
+ c.push_back (i->first);
+ }
+ }
+
+ return c;
+}
+
+list<AudioMapping::Channel>
+AudioMapping::content_channels () const
+{
+ list<AudioMapping::Channel> c;
+ for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (find (c.begin(), c.end(), i->first) == c.end ()) {
+ c.push_back (i->first);
+ }
+ }
+
+ return c;
+}
+
+list<libdcp::Channel>
+AudioMapping::content_to_dcp (Channel c) const
+{
+ list<libdcp::Channel> d;
+ for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (i->first == c) {
+ d.push_back (i->second);
+ }
+ }
+
+ return d;
+}
+
+void
+AudioMapping::as_xml (xmlpp::Node* node) const
+{
+ for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ xmlpp::Node* t = node->add_child ("Map");
+ shared_ptr<const AudioContent> c = i->first.content.lock ();
+ t->add_child ("Content")->add_child_text (c->file().string ());
+ t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first.index));
+ t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second));
+ }
+}
+
+void
+AudioMapping::set_from_xml (ContentList const & content, shared_ptr<const cxml::Node> node)
+{
+ list<shared_ptr<cxml::Node> > const c = node->node_children ("Map");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ string const c = (*i)->string_child ("Content");
+ ContentList::const_iterator j = content.begin ();
+ while (j != content.end() && (*j)->file().string() != c) {
+ ++j;
+ }
+
+ if (j == content.end ()) {
+ continue;
+ }
+
+ shared_ptr<const AudioContent> ac = dynamic_pointer_cast<AudioContent> (*j);
+ assert (ac);
+
+ add (AudioMapping::Channel (ac, (*i)->number_child<int> ("ContentIndex")), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")));
+ }
+}
+
+bool
+operator== (AudioMapping::Channel const & a, AudioMapping::Channel const & b)
+{
+ shared_ptr<const AudioContent> sa = a.content.lock ();
+ shared_ptr<const AudioContent> sb = b.content.lock ();
+ return sa == sb && a.index == b.index;
+}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
new file mode 100644
index 000000000..4f2cdb7f8
--- /dev/null
+++ b/src/lib/audio_mapping.h
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_MAPPING_H
+#define DVDOMATIC_AUDIO_MAPPING_H
+
+#include <list>
+#include <string>
+#include <libdcp/types.h>
+#include <boost/shared_ptr.hpp>
+#include "audio_content.h"
+
+class AudioMapping
+{
+public:
+ void as_xml (xmlpp::Node *) const;
+ void set_from_xml (ContentList const &, boost::shared_ptr<const cxml::Node>);
+
+ struct Channel {
+ Channel (boost::weak_ptr<const AudioContent> c, int i)
+ : content (c)
+ , index (i)
+ {}
+
+ boost::weak_ptr<const AudioContent> content;
+ int index;
+ };
+
+ void add (Channel, libdcp::Channel);
+
+ int dcp_channels () const;
+ std::list<Channel> dcp_to_content (libdcp::Channel) const;
+ std::list<std::pair<Channel, libdcp::Channel> > content_to_dcp () const {
+ return _content_to_dcp;
+ }
+
+ std::list<Channel> content_channels () const;
+ std::list<libdcp::Channel> content_to_dcp (Channel) const;
+
+private:
+ std::list<std::pair<Channel, libdcp::Channel> > _content_to_dcp;
+};
+
+extern bool operator== (AudioMapping::Channel const &, AudioMapping::Channel const &);
+
+#endif
diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc
index 53b0dda15..99b59759d 100644
--- a/src/lib/audio_source.cc
+++ b/src/lib/audio_source.cc
@@ -21,10 +21,20 @@
#include "audio_sink.h"
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::bind;
+static void
+process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<AudioBuffers> audio)
+{
+ shared_ptr<AudioSink> p = sink.lock ();
+ if (p) {
+ p->process_audio (audio);
+ }
+}
+
void
AudioSource::connect_audio (shared_ptr<AudioSink> s)
{
- Audio.connect (bind (&AudioSink::process_audio, s, _1));
+ Audio.connect (bind (process_audio_proxy, weak_ptr<AudioSink> (s), _1));
}
diff --git a/src/lib/config.cc b/src/lib/config.cc
index 5dce3748d..2defa0539 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -22,6 +22,7 @@
#include <fstream>
#include <glib.h>
#include <boost/filesystem.hpp>
+#include <libcxml/cxml.h>
#include "config.h"
#include "server.h"
#include "scaler.h"
@@ -34,7 +35,9 @@ using std::vector;
using std::ifstream;
using std::string;
using std::ofstream;
+using std::list;
using boost::shared_ptr;
+using boost::optional;
Config* Config::_instance = 0;
@@ -52,8 +55,51 @@ Config::Config ()
_allowed_dcp_frame_rates.push_back (48);
_allowed_dcp_frame_rates.push_back (50);
_allowed_dcp_frame_rates.push_back (60);
+
+ if (!boost::filesystem::exists (file (false))) {
+ read_old_metadata ();
+ return;
+ }
+
+ cxml::File f (file (false), "Config");
+ optional<string> c;
+
+ _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads");
+ _default_directory = f.string_child ("DefaultDirectory");
+ _server_port = f.number_child<int> ("ServerPort");
+ c = f.optional_string_child ("ReferenceScaler");
+ if (c) {
+ _reference_scaler = Scaler::from_id (c.get ());
+ }
+
+ list<shared_ptr<cxml::Node> > filters = f.node_children ("ReferenceFilter");
+ for (list<shared_ptr<cxml::Node> >::iterator i = filters.begin(); i != filters.end(); ++i) {
+ _reference_filters.push_back (Filter::from_id ((*i)->content ()));
+ }
- ifstream f (file().c_str ());
+ list<shared_ptr<cxml::Node> > servers = f.node_children ("Server");
+ for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) {
+ _servers.push_back (new ServerDescription (*i));
+ }
+
+ _tms_ip = f.string_child ("TMSIP");
+ _tms_path = f.string_child ("TMSPath");
+ _tms_user = f.string_child ("TMSUser");
+ _tms_password = f.string_child ("TMSPassword");
+
+ c = f.optional_string_child ("SoundProcessor");
+ if (c) {
+ _sound_processor = SoundProcessor::from_id (c.get ());
+ }
+
+ _language = f.optional_string_child ("Language");
+ _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+}
+
+void
+Config::read_old_metadata ()
+{
+ ifstream f (file(true).c_str ());
string line;
while (getline (f, line)) {
if (line.empty ()) {
@@ -98,17 +144,21 @@ Config::Config ()
_language = v;
}
- _default_dci_metadata.read (k, v);
+ _default_dci_metadata.read_old_metadata (k, v);
}
}
/** @return Filename to write configuration to */
string
-Config::file () const
+Config::file (bool old) const
{
boost::filesystem::path p;
p /= g_get_user_config_dir ();
- p /= N_(".dvdomatic");
+ if (old) {
+ p /= ".dvdomatic";
+ } else {
+ p /= ".dvdomatic.xml";
+ }
return p.string ();
}
@@ -127,35 +177,38 @@ Config::instance ()
void
Config::write () const
{
- ofstream f (file().c_str ());
- f << N_("num_local_encoding_threads ") << _num_local_encoding_threads << N_("\n")
- << N_("default_directory ") << _default_directory << N_("\n")
- << N_("server_port ") << _server_port << N_("\n");
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("Config");
+ root->add_child("NumLocalEncodingThreads")->add_child_text (boost::lexical_cast<string> (_num_local_encoding_threads));
+ root->add_child("DefaultDirectory")->add_child_text (_default_directory);
+ root->add_child("ServerPort")->add_child_text (boost::lexical_cast<string> (_server_port));
if (_reference_scaler) {
- f << "reference_scaler " << _reference_scaler->id () << "\n";
+ root->add_child("ReferenceScaler")->add_child_text (_reference_scaler->id ());
}
for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
- f << N_("reference_filter ") << (*i)->id () << N_("\n");
+ root->add_child("ReferenceFilter")->add_child_text ((*i)->id ());
}
for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
- f << N_("server ") << (*i)->as_metadata () << N_("\n");
+ (*i)->as_xml (root->add_child ("Server"));
}
- f << N_("tms_ip ") << _tms_ip << N_("\n");
- f << N_("tms_path ") << _tms_path << N_("\n");
- f << N_("tms_user ") << _tms_user << N_("\n");
- f << N_("tms_password ") << _tms_password << N_("\n");
+ root->add_child("TMSIP")->add_child_text (_tms_ip);
+ root->add_child("TMSPath")->add_child_text (_tms_path);
+ root->add_child("TMSUser")->add_child_text (_tms_user);
+ root->add_child("TMSPassword")->add_child_text (_tms_password);
if (_sound_processor) {
- f << "sound_processor " << _sound_processor->id () << "\n";
+ root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
}
if (_language) {
- f << "language " << _language.get() << "\n";
+ root->add_child("Language")->add_child_text (_language.get());
}
- _default_dci_metadata.write (f);
+ _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+
+ doc.write_to_file_formatted (file (false));
}
string
diff --git a/src/lib/config.h b/src/lib/config.h
index 011ca716f..13d36d236 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -177,7 +177,8 @@ public:
private:
Config ();
- std::string file () const;
+ std::string file (bool) const;
+ void read_old_metadata ();
/** number of threads to use for J2K encoding on the local machine */
int _num_local_encoding_threads;
diff --git a/src/lib/content.cc b/src/lib/content.cc
new file mode 100644
index 000000000..9c3bcd39d
--- /dev/null
+++ b/src/lib/content.cc
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/thread/mutex.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "content.h"
+#include "util.h"
+
+using std::string;
+using boost::shared_ptr;
+
+Content::Content (boost::filesystem::path f)
+ : _file (f)
+{
+
+}
+
+Content::Content (shared_ptr<const cxml::Node> node)
+{
+ _file = node->string_child ("File");
+ _digest = node->string_child ("Digest");
+}
+
+Content::Content (Content const & o)
+ : boost::enable_shared_from_this<Content> (o)
+ , _file (o._file)
+ , _digest (o._digest)
+{
+
+}
+
+void
+Content::as_xml (xmlpp::Node* node) const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ node->add_child("File")->add_child_text (_file.string());
+ node->add_child("Digest")->add_child_text (_digest);
+}
+
+void
+Content::examine (shared_ptr<Film>, shared_ptr<Job>, bool)
+{
+ string const d = md5_digest (_file);
+ boost::mutex::scoped_lock lm (_mutex);
+ _digest = d;
+}
+
+void
+Content::signal_changed (int p)
+{
+ Changed (shared_from_this (), p);
+}
diff --git a/src/lib/content.h b/src/lib/content.h
new file mode 100644
index 000000000..fc2672c95
--- /dev/null
+++ b/src/lib/content.h
@@ -0,0 +1,66 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_CONTENT_H
+#define DVDOMATIC_CONTENT_H
+
+#include <boost/filesystem.hpp>
+#include <boost/signals2.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+ class Node;
+}
+
+class Job;
+class Film;
+
+class Content : public boost::enable_shared_from_this<Content>
+{
+public:
+ Content (boost::filesystem::path);
+ Content (boost::shared_ptr<const cxml::Node>);
+ Content (Content const &);
+
+ virtual void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+ virtual std::string summary () const = 0;
+ virtual std::string information () const = 0;
+ virtual void as_xml (xmlpp::Node *) const;
+ virtual boost::shared_ptr<Content> clone () const = 0;
+
+ boost::filesystem::path file () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _file;
+ }
+
+ boost::signals2::signal<void (boost::weak_ptr<Content>, int)> Changed;
+
+protected:
+ void signal_changed (int);
+
+ mutable boost::mutex _mutex;
+
+private:
+ boost::filesystem::path _file;
+ std::string _digest;
+};
+
+#endif
diff --git a/src/lib/dci_metadata.cc b/src/lib/dci_metadata.cc
index 758886db4..f25b3ddb0 100644
--- a/src/lib/dci_metadata.cc
+++ b/src/lib/dci_metadata.cc
@@ -18,26 +18,39 @@
*/
#include <iostream>
+#include <libcxml/cxml.h>
#include "dci_metadata.h"
#include "i18n.h"
-using namespace std;
+using std::string;
+using boost::shared_ptr;
+
+DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+{
+ audio_language = node->string_child ("AudioLanguage");
+ subtitle_language = node->string_child ("SubtitleLanguage");
+ territory = node->string_child ("Territory");
+ rating = node->string_child ("Rating");
+ studio = node->string_child ("Studio");
+ facility = node->string_child ("Facility");
+ package_type = node->string_child ("PackageType");
+}
void
-DCIMetadata::write (ostream& f) const
+DCIMetadata::as_xml (xmlpp::Node* root) const
{
- f << N_("audio_language ") << audio_language << N_("\n");
- f << N_("subtitle_language ") << subtitle_language << N_("\n");
- f << N_("territory ") << territory << N_("\n");
- f << N_("rating ") << rating << N_("\n");
- f << N_("studio ") << studio << N_("\n");
- f << N_("facility ") << facility << N_("\n");
- f << N_("package_type ") << package_type << N_("\n");
+ root->add_child("AudioLanguage")->add_child_text (audio_language);
+ root->add_child("SubtitleLanguage")->add_child_text (subtitle_language);
+ root->add_child("Territory")->add_child_text (territory);
+ root->add_child("Rating")->add_child_text (rating);
+ root->add_child("Studio")->add_child_text (studio);
+ root->add_child("Facility")->add_child_text (facility);
+ root->add_child("PackageType")->add_child_text (package_type);
}
void
-DCIMetadata::read (string k, string v)
+DCIMetadata::read_old_metadata (string k, string v)
{
if (k == N_("audio_language")) {
audio_language = v;
diff --git a/src/lib/dci_metadata.h b/src/lib/dci_metadata.h
index eecdc7655..f61dae5a8 100644
--- a/src/lib/dci_metadata.h
+++ b/src/lib/dci_metadata.h
@@ -21,12 +21,20 @@
#define DVDOMATIC_DCI_METADATA_H
#include <string>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+ class Node;
+}
class DCIMetadata
{
public:
- void read (std::string, std::string);
- void write (std::ostream &) const;
+ DCIMetadata () {}
+ DCIMetadata (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+ void read_old_metadata (std::string, std::string);
std::string audio_language;
std::string subtitle_language;
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index d674393a9..e9499871a 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -47,7 +47,6 @@
#include "dcp_video_frame.h"
#include "lut.h"
#include "config.h"
-#include "options.h"
#include "exceptions.h"
#include "server.h"
#include "util.h"
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 52b22fa06..c40446919 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -22,36 +22,21 @@
*/
#include <iostream>
-#include <stdint.h>
-#include <boost/lexical_cast.hpp>
#include "film.h"
-#include "format.h"
-#include "options.h"
#include "exceptions.h"
-#include "image.h"
#include "util.h"
-#include "log.h"
#include "decoder.h"
-#include "delay_line.h"
-#include "subtitle.h"
-#include "filter_graph.h"
#include "i18n.h"
using std::string;
-using std::stringstream;
-using std::min;
-using std::pair;
-using std::list;
using boost::shared_ptr;
-using boost::optional;
/** @param f Film.
* @param o Decode options.
*/
-Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o)
+Decoder::Decoder (shared_ptr<const Film> f)
: _film (f)
- , _opt (o)
{
_film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1));
}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index f2f523516..34accf6c7 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -30,11 +30,9 @@
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include "util.h"
-#include "stream.h"
#include "video_source.h"
#include "audio_source.h"
#include "film.h"
-#include "options.h"
class Image;
class Log;
@@ -53,24 +51,19 @@ class FilterGraph;
class Decoder
{
public:
- Decoder (boost::shared_ptr<Film>, DecodeOptions);
+ Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
virtual bool pass () = 0;
virtual bool seek (double);
virtual bool seek_to_last ();
- boost::signals2::signal<void()> OutputChanged;
-
protected:
- /** our Film */
- boost::shared_ptr<Film> _film;
- /** our decode options */
- DecodeOptions _opt;
+ boost::shared_ptr<const Film> _film;
private:
virtual void film_changed (Film::Property) {}
-
+
boost::signals2::scoped_connection _film_connection;
};
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
deleted file mode 100644
index f7f9f4074..000000000
--- a/src/lib/decoder_factory.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/decoder_factory.cc
- * @brief A method to create an appropriate decoder for some content.
- */
-
-#include <boost/filesystem.hpp>
-#include "ffmpeg_decoder.h"
-#include "imagemagick_decoder.h"
-#include "film.h"
-#include "sndfile_decoder.h"
-#include "decoder_factory.h"
-
-using std::string;
-using std::pair;
-using std::make_pair;
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-
-Decoders
-decoder_factory (
- shared_ptr<Film> f, DecodeOptions o
- )
-{
- if (f->content().empty()) {
- return Decoders ();
- }
-
- if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
- /* A single image file, or a directory of them */
- return Decoders (
- shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o)),
- shared_ptr<AudioDecoder> (new SndfileDecoder (f, o))
- );
- }
-
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o));
- if (f->use_content_audio()) {
- return Decoders (fd, fd);
- }
-
- return Decoders (fd, shared_ptr<AudioDecoder> (new SndfileDecoder (f, o)));
-}
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 7b338407e..46d11c556 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -27,7 +27,6 @@
#include <libdcp/picture_asset.h>
#include "encoder.h"
#include "util.h"
-#include "options.h"
#include "film.h"
#include "log.h"
#include "exceptions.h"
@@ -38,6 +37,8 @@
#include "format.h"
#include "cross.h"
#include "writer.h"
+#include "player.h"
+#include "audio_mapping.h"
#include "i18n.h"
@@ -48,7 +49,8 @@ using std::vector;
using std::list;
using std::cout;
using std::make_pair;
-using namespace boost;
+using boost::shared_ptr;
+using boost::optional;
int const Encoder::_history_size = 25;
@@ -77,22 +79,22 @@ Encoder::~Encoder ()
void
Encoder::process_begin ()
{
- if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
+ if (_film->has_audio() && _film->audio_frame_rate() != _film->target_audio_sample_rate()) {
#ifdef HAVE_SWRESAMPLE
stringstream s;
- s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_stream()->sample_rate(), _film->target_audio_sample_rate());
+ s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_frame_rate(), _film->target_audio_sample_rate());
_film->log()->log (s.str ());
/* We will be using planar float data when we call the resampler */
_swr_context = swr_alloc_set_opts (
0,
- _film->audio_stream()->channel_layout(),
+ _film->audio_channel_layout(),
AV_SAMPLE_FMT_FLTP,
_film->target_audio_sample_rate(),
- _film->audio_stream()->channel_layout(),
+ _film->audio_channel_layout(),
AV_SAMPLE_FMT_FLTP,
- _film->audio_stream()->sample_rate(),
+ _film->audio_frame_rate(),
0, 0
);
@@ -126,9 +128,9 @@ void
Encoder::process_end ()
{
#if HAVE_SWRESAMPLE
- if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) {
+ if (_film->has_audio() && _film->audio_channels() && _swr_context) {
- shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256));
+ shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_channels(), 256));
while (1) {
int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
@@ -142,7 +144,7 @@ Encoder::process_end ()
}
out->set_frames (frames);
- write_audio (out);
+ _writer->write (out);
}
swr_free (&_swr_context);
@@ -193,7 +195,7 @@ Encoder::process_end ()
* or 0 if not known.
*/
float
-Encoder::current_frames_per_second () const
+Encoder::current_encoding_rate () const
{
boost::mutex::scoped_lock lock (_history_mutex);
if (int (_time_history.size()) < _history_size) {
@@ -231,9 +233,9 @@ Encoder::frame_done ()
}
void
-Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub)
+Encoder::process_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
{
- FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
+ FrameRateConversion frc (_film->video_frame_rate(), _film->dcp_frame_rate());
if (frc.skip && (_video_frames_in % 2)) {
++_video_frames_in;
@@ -269,7 +271,7 @@ Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Su
/* Queue this new frame for encoding */
pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
TIMING ("adding to queue of %1", _queue.size ());
- _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+ _queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
_film->subtitle_offset(), _film->subtitle_scale(),
@@ -301,9 +303,9 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data)
if (_swr_context) {
/* Compute the resampled frames count and add 32 for luck */
- int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32;
+ int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_frame_rate()) + 32;
- shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames));
+ shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_channels(), max_resampled_frames));
/* Resample audio */
int const resampled_frames = swr_convert (
@@ -321,7 +323,7 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data)
}
#endif
- write_audio (data);
+ _writer->write (data);
}
void
@@ -360,7 +362,7 @@ Encoder::encoder_thread (ServerDescription* server)
}
TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
- boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+ shared_ptr<DCPVideoFrame> vf = _queue.front ();
_film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 from queue"), boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
_queue.pop_front ();
@@ -421,27 +423,3 @@ Encoder::encoder_thread (ServerDescription* server)
_condition.notify_all ();
}
}
-
-void
-Encoder::write_audio (shared_ptr<const AudioBuffers> data)
-{
- AudioMapping m (_film->audio_channels ());
- if (m.dcp_channels() != _film->audio_channels()) {
-
- /* Remap (currently just for mono -> 5.1) */
-
- shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ()));
- for (int i = 0; i < m.dcp_channels(); ++i) {
- optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i));
- if (!s) {
- b->make_silent (i);
- } else {
- memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float));
- }
- }
-
- data = b;
- }
-
- _writer->write (data);
-}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index 86880bc34..70e6eea9a 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -81,15 +81,13 @@ public:
/** Called when a processing run has finished */
virtual void process_end ();
- float current_frames_per_second () const;
+ float current_encoding_rate () const;
int video_frames_out () const;
private:
void frame_done ();
- void write_audio (boost::shared_ptr<const AudioBuffers> data);
-
void encoder_thread (ServerDescription *);
void terminate_threads ();
@@ -106,7 +104,7 @@ private:
static int const _history_size;
/** Number of video frames received so far */
- SourceFrame _video_frames_in;
+ ContentVideoFrame _video_frames_in;
/** Number of video frames written for the DCP so far */
int _video_frames_out;
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
index 4b30c9431..aad7f265e 100644
--- a/src/lib/examine_content_job.cc
+++ b/src/lib/examine_content_job.cc
@@ -17,29 +17,20 @@
*/
-/** @file src/examine_content_job.cc
- * @brief A class to run through content at high speed to find its length.
- */
-
#include <boost/filesystem.hpp>
#include "examine_content_job.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "decoder.h"
-#include "transcoder.h"
#include "log.h"
-#include "film.h"
-#include "video_decoder.h"
+#include "content.h"
#include "i18n.h"
using std::string;
-using std::vector;
-using std::pair;
using boost::shared_ptr;
-ExamineContentJob::ExamineContentJob (shared_ptr<Film> f)
+ExamineContentJob::ExamineContentJob (shared_ptr<Film> f, shared_ptr<Content> c, bool q)
: Job (f)
+ , _content (c)
+ , _quick (q)
{
}
@@ -51,60 +42,13 @@ ExamineContentJob::~ExamineContentJob ()
string
ExamineContentJob::name () const
{
- if (_film->name().empty ()) {
- return _("Examine content");
- }
-
- return String::compose (_("Examine content of %1"), _film->name());
+ return _("Examine content");
}
void
ExamineContentJob::run ()
{
- descend (0.5);
- _film->set_content_digest (md5_digest (_film->content_path ()));
- ascend ();
-
- descend (0.5);
-
- /* Set the film's length to either
- a) a length judged by running through the content or
- b) the length from a decoder's header.
- */
- if (!_film->trust_content_header()) {
- /* Decode the content to get an accurate length */
-
- /* We don't want to use any existing length here, as progress
- will be messed up.
- */
- _film->unset_length ();
- _film->set_crop (Crop ());
-
- DecodeOptions o;
- o.decode_audio = false;
-
- Decoders decoders = decoder_factory (_film, o);
-
- set_progress_unknown ();
- while (!decoders.video->pass()) {
- /* keep going */
- }
-
- _film->set_length (decoders.video->video_frame());
-
- _film->log()->log (String::compose (N_("Video length examined as %1 frames"), _film->length().get()));
-
- } else {
-
- /* Get a quick decoder to get the content's length from its header */
-
- Decoders d = decoder_factory (_film, DecodeOptions());
- _film->set_length (d.video->length());
-
- _film->log()->log (String::compose (N_("Video length obtained from header as %1 frames"), _film->length().get()));
- }
-
- ascend ();
+ _content->examine (_film, shared_from_this (), _quick);
set_progress (1);
set_state (FINISHED_OK);
}
diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h
index 8ee4f0d60..dc0d53fff 100644
--- a/src/lib/examine_content_job.h
+++ b/src/lib/examine_content_job.h
@@ -17,22 +17,23 @@
*/
-/** @file src/examine_content_job.h
- * @brief A class to obtain the length and MD5 digest of a content file.
- */
-
+#include <boost/shared_ptr.hpp>
#include "job.h"
-/** @class ExamineContentJob
- * @brief A class to obtain the length and MD5 digest of a content file.
- */
+class Content;
+class Log;
+
class ExamineContentJob : public Job
{
public:
- ExamineContentJob (boost::shared_ptr<Film>);
+ ExamineContentJob (boost::shared_ptr<Film>, boost::shared_ptr<Content>, bool);
~ExamineContentJob ();
std::string name () const;
void run ();
+
+private:
+ boost::shared_ptr<Content> _content;
+ bool _quick;
};
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index e45a62353..6920556e5 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -112,6 +112,7 @@ class OpenFileError : public FileError
{
public:
/** @param f File that we were trying to open */
+ /* XXX: should be boost::filesystem::path */
OpenFileError (std::string f);
};
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
new file mode 100644
index 000000000..d36abe2c3
--- /dev/null
+++ b/src/lib/ffmpeg_content.cc
@@ -0,0 +1,291 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "ffmpeg_content.h"
+#include "ffmpeg_decoder.h"
+#include "compose.hpp"
+#include "job.h"
+#include "util.h"
+#include "log.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::vector;
+using std::list;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
+int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
+int const FFmpegContentProperty::AUDIO_STREAMS = 102;
+int const FFmpegContentProperty::AUDIO_STREAM = 103;
+
+FFmpegContent::FFmpegContent (boost::filesystem::path f)
+ : Content (f)
+ , VideoContent (f)
+ , AudioContent (f)
+{
+
+}
+
+FFmpegContent::FFmpegContent (shared_ptr<const cxml::Node> node)
+ : Content (node)
+ , VideoContent (node)
+ , AudioContent (node)
+{
+ list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ _subtitle_streams.push_back (FFmpegSubtitleStream (*i));
+ if ((*i)->optional_number_child<int> ("Selected")) {
+ _subtitle_stream = _subtitle_streams.back ();
+ }
+ }
+
+ c = node->node_children ("AudioStream");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ _audio_streams.push_back (FFmpegAudioStream (*i));
+ if ((*i)->optional_number_child<int> ("Selected")) {
+ _audio_stream = _audio_streams.back ();
+ }
+ }
+}
+
+FFmpegContent::FFmpegContent (FFmpegContent const & o)
+ : Content (o)
+ , VideoContent (o)
+ , AudioContent (o)
+ , _subtitle_streams (o._subtitle_streams)
+ , _subtitle_stream (o._subtitle_stream)
+ , _audio_streams (o._audio_streams)
+ , _audio_stream (o._audio_stream)
+{
+
+}
+
+void
+FFmpegContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("FFmpeg");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+
+ boost::mutex::scoped_lock lm (_mutex);
+
+ for (vector<FFmpegSubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
+ xmlpp::Node* t = node->add_child("SubtitleStream");
+ if (_subtitle_stream && *i == _subtitle_stream.get()) {
+ t->add_child("Selected")->add_child_text("1");
+ }
+ i->as_xml (t);
+ }
+
+ for (vector<FFmpegAudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
+ xmlpp::Node* t = node->add_child("AudioStream");
+ if (_audio_stream && *i == _audio_stream.get()) {
+ t->add_child("Selected")->add_child_text("1");
+ }
+ i->as_xml (t);
+ }
+}
+
+void
+FFmpegContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
+{
+ job->set_progress_unknown ();
+
+ Content::examine (film, job, quick);
+
+ shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false, true));
+
+ ContentVideoFrame video_length = 0;
+ if (quick) {
+ video_length = decoder->video_length ();
+ film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ()));
+ } else {
+ while (!decoder->pass ()) {
+ /* keep going */
+ }
+
+ video_length = decoder->video_frame ();
+ film->log()->log (String::compose ("Video length examined as %1 frames", decoder->video_frame ()));
+ }
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ _video_length = video_length;
+
+ _subtitle_streams = decoder->subtitle_streams ();
+ if (!_subtitle_streams.empty ()) {
+ _subtitle_stream = _subtitle_streams.front ();
+ }
+
+ _audio_streams = decoder->audio_streams ();
+ if (!_audio_streams.empty ()) {
+ _audio_stream = _audio_streams.front ();
+ }
+ }
+
+ take_from_video_decoder (decoder);
+
+ signal_changed (VideoContentProperty::VIDEO_LENGTH);
+ signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
+ signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+ signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
+ signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+ signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+}
+
+string
+FFmpegContent::summary () const
+{
+ return String::compose (_("Movie: %1"), file().filename().string());
+}
+
+string
+FFmpegContent::information () const
+{
+ if (video_length() == 0 || video_frame_rate() == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
+ s << VideoContent::information ();
+
+ return s.str ();
+}
+
+void
+FFmpegContent::set_subtitle_stream (FFmpegSubtitleStream s)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _subtitle_stream = s;
+ }
+
+ signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+}
+
+void
+FFmpegContent::set_audio_stream (FFmpegAudioStream s)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_stream = s;
+ }
+
+ signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+}
+
+ContentAudioFrame
+FFmpegContent::audio_length () const
+{
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return video_frames_to_audio_frames (_video_length, audio_frame_rate(), video_frame_rate());
+}
+
+int
+FFmpegContent::audio_channels () const
+{
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return _audio_stream->channels ();
+}
+
+int
+FFmpegContent::audio_frame_rate () const
+{
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return _audio_stream->frame_rate;
+}
+
+int64_t
+FFmpegContent::audio_channel_layout () const
+{
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return _audio_stream->channel_layout;
+}
+
+bool
+operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+{
+ return a.id == b.id;
+}
+
+bool
+operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+{
+ return a.id == b.id;
+}
+
+FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
+{
+ name = node->string_child ("Name");
+ id = node->number_child<int> ("Id");
+ frame_rate = node->number_child<int> ("FrameRate");
+ channel_layout = node->number_child<int64_t> ("ChannelLayout");
+}
+
+void
+FFmpegAudioStream::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("Name")->add_child_text (name);
+ root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+ root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
+ root->add_child("ChannelLayout")->add_child_text (lexical_cast<string> (channel_layout));
+}
+
+/** Construct a SubtitleStream from a value returned from to_string().
+ * @param t String returned from to_string().
+ * @param v State file version.
+ */
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+{
+ name = node->string_child ("Name");
+ id = node->number_child<int> ("Id");
+}
+
+void
+FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("Name")->add_child_text (name);
+ root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+}
+
+shared_ptr<Content>
+FFmpegContent::clone () const
+{
+ return shared_ptr<Content> (new FFmpegContent (*this));
+}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
new file mode 100644
index 000000000..cc603e680
--- /dev/null
+++ b/src/lib/ffmpeg_content.h
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_FFMPEG_CONTENT_H
+#define DVDOMATIC_FFMPEG_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+#include "audio_content.h"
+
+class FFmpegAudioStream
+{
+public:
+ FFmpegAudioStream (std::string n, int i, int f, int64_t c)
+ : name (n)
+ , id (i)
+ , frame_rate (f)
+ , channel_layout (c)
+ {}
+
+ FFmpegAudioStream (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ int channels () const {
+ return av_get_channel_layout_nb_channels (channel_layout);
+ }
+
+ std::string name;
+ int id;
+ int frame_rate;
+ int64_t channel_layout;
+};
+
+extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
+
+class FFmpegSubtitleStream
+{
+public:
+ FFmpegSubtitleStream (std::string n, int i)
+ : name (n)
+ , id (i)
+ {}
+
+ FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ std::string name;
+ int id;
+};
+
+extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
+
+class FFmpegContentProperty : public VideoContentProperty
+{
+public:
+ static int const SUBTITLE_STREAMS;
+ static int const SUBTITLE_STREAM;
+ static int const AUDIO_STREAMS;
+ static int const AUDIO_STREAM;
+};
+
+class FFmpegContent : public VideoContent, public AudioContent
+{
+public:
+ FFmpegContent (boost::filesystem::path);
+ FFmpegContent (boost::shared_ptr<const cxml::Node>);
+ FFmpegContent (FFmpegContent const &);
+
+ boost::shared_ptr<FFmpegContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+ std::string summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *) const;
+ boost::shared_ptr<Content> clone () const;
+
+ /* AudioContent */
+ int audio_channels () const;
+ ContentAudioFrame audio_length () const;
+ int audio_frame_rate () const;
+ int64_t audio_channel_layout () const;
+
+ std::vector<FFmpegSubtitleStream> subtitle_streams () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _subtitle_streams;
+ }
+
+ boost::optional<FFmpegSubtitleStream> subtitle_stream () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _subtitle_stream;
+ }
+
+ std::vector<FFmpegAudioStream> audio_streams () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_streams;
+ }
+
+ boost::optional<FFmpegAudioStream> audio_stream () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_stream;
+ }
+
+ void set_subtitle_stream (FFmpegSubtitleStream);
+ void set_audio_stream (FFmpegAudioStream);
+
+private:
+ std::vector<FFmpegSubtitleStream> _subtitle_streams;
+ boost::optional<FFmpegSubtitleStream> _subtitle_stream;
+ std::vector<FFmpegAudioStream> _audio_streams;
+ boost::optional<FFmpegAudioStream> _audio_stream;
+};
+
+#endif
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index ac25844e3..a0949f635 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -41,7 +41,6 @@ extern "C" {
#include "transcoder.h"
#include "job.h"
#include "filter.h"
-#include "options.h"
#include "exceptions.h"
#include "image.h"
#include "util.h"
@@ -62,10 +61,13 @@ using boost::optional;
using boost::dynamic_pointer_cast;
using libdcp::Size;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , VideoDecoder (f, o)
- , AudioDecoder (f, o)
+boost::mutex FFmpegDecoder::_mutex;
+
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles, bool video_sync)
+ : Decoder (f)
+ , VideoDecoder (f)
+ , AudioDecoder (f)
+ , _ffmpeg_content (c)
, _format_context (0)
, _video_stream (-1)
, _frame (0)
@@ -75,23 +77,29 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
, _audio_codec (0)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
+ , _decode_video (video)
+ , _decode_audio (audio)
+ , _decode_subtitles (subtitles)
+ , _video_sync (video_sync)
{
setup_general ();
setup_video ();
setup_audio ();
setup_subtitle ();
- if (!o.video_sync) {
+ if (!video_sync) {
_first_video = 0;
}
}
FFmpegDecoder::~FFmpegDecoder ()
{
+ boost::mutex::scoped_lock lm (_mutex);
+
if (_audio_codec_context) {
avcodec_close (_audio_codec_context);
}
-
+
if (_video_codec_context) {
avcodec_close (_video_codec_context);
}
@@ -110,15 +118,15 @@ FFmpegDecoder::setup_general ()
{
av_register_all ();
- if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
- throw OpenFileError (_film->content_path ());
+ if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) {
+ throw OpenFileError (_ffmpeg_content->file().string ());
}
if (avformat_find_stream_info (_format_context, 0) < 0) {
throw DecodeError (_("could not find stream information"));
}
- /* Find video, audio and subtitle streams and choose the first of each */
+ /* Find video, audio and subtitle streams */
for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
AVStream* s = _format_context->streams[i];
@@ -135,17 +143,11 @@ FFmpegDecoder::setup_general ()
}
_audio_streams.push_back (
- shared_ptr<AudioStream> (
- new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
- )
+ FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
);
} else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
- _subtitle_streams.push_back (
- shared_ptr<SubtitleStream> (
- new SubtitleStream (stream_name (s), i)
- )
- );
+ _subtitle_streams.push_back (FFmpegSubtitleStream (stream_name (s), i));
}
}
@@ -162,6 +164,8 @@ FFmpegDecoder::setup_general ()
void
FFmpegDecoder::setup_video ()
{
+ boost::mutex::scoped_lock lm (_mutex);
+
_video_codec_context = _format_context->streams[_video_stream]->codec;
_video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
@@ -177,14 +181,13 @@ FFmpegDecoder::setup_video ()
void
FFmpegDecoder::setup_audio ()
{
- if (!_audio_stream) {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (!_ffmpeg_content->audio_stream ()) {
return;
}
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
- _audio_codec_context = _format_context->streams[ffa->id()]->codec;
+ _audio_codec_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
_audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
if (_audio_codec == 0) {
@@ -199,11 +202,13 @@ FFmpegDecoder::setup_audio ()
void
FFmpegDecoder::setup_subtitle ()
{
- if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
return;
}
- _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
+ _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
_subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
if (_subtitle_codec == 0) {
@@ -238,13 +243,13 @@ FFmpegDecoder::pass ()
int frame_finished;
- if (_opt.decode_video) {
+ if (_decode_video) {
while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
filter_and_emit_video (_frame);
}
}
- if (_audio_stream && _opt.decode_audio) {
+ if (_ffmpeg_content->audio_stream() && _decode_audio) {
decode_audio_packet ();
}
@@ -253,9 +258,7 @@ FFmpegDecoder::pass ()
avcodec_get_frame_defaults (_frame);
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
- if (_packet.stream_index == _video_stream && _opt.decode_video) {
+ if (_packet.stream_index == _video_stream && _decode_video) {
int frame_finished;
int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
@@ -265,16 +268,16 @@ FFmpegDecoder::pass ()
_film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
}
- if (_opt.video_sync) {
+ if (_video_sync) {
out_with_sync ();
} else {
filter_and_emit_video (_frame);
}
}
- } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
+ } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
decode_audio_packet ();
- } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles && _first_video) {
+ } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles && _first_video) {
int got_subtitle;
AVSubtitle sub;
@@ -306,19 +309,16 @@ FFmpegDecoder::pass ()
shared_ptr<AudioBuffers>
FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
{
- assert (_film->audio_channels());
+ assert (_ffmpeg_content->audio_channels());
assert (bytes_per_audio_sample());
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
/* Deinterleave and convert to float */
- assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
+ assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
int const total_samples = size / bytes_per_audio_sample();
- int const frames = total_samples / _film->audio_channels();
- shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
+ int const frames = total_samples / _ffmpeg_content->audio_channels();
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
switch (audio_sample_format()) {
case AV_SAMPLE_FMT_S16:
@@ -330,7 +330,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
audio->data(channel)[sample] = float(*p++) / (1 << 15);
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -341,7 +341,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
case AV_SAMPLE_FMT_S16P:
{
int16_t** p = reinterpret_cast<int16_t **> (data);
- for (int i = 0; i < _film->audio_channels(); ++i) {
+ for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
for (int j = 0; j < frames; ++j) {
audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
}
@@ -358,7 +358,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -375,7 +375,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
audio->data(channel)[sample] = *p++;
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -386,7 +386,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
case AV_SAMPLE_FMT_FLTP:
{
float** p = reinterpret_cast<float**> (data);
- for (int i = 0; i < _film->audio_channels(); ++i) {
+ for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
memcpy (audio->data(i), p[i], frames * sizeof(float));
}
}
@@ -400,7 +400,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
}
float
-FFmpegDecoder::frames_per_second () const
+FFmpegDecoder::video_frame_rate () const
{
AVStream* s = _format_context->streams[_video_stream];
@@ -489,21 +489,6 @@ FFmpegDecoder::bytes_per_audio_sample () const
}
void
-FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
-{
- AudioDecoder::set_audio_stream (s);
- setup_audio ();
-}
-
-void
-FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- VideoDecoder::set_subtitle_stream (s);
- setup_subtitle ();
- OutputChanged ();
-}
-
-void
FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
{
boost::mutex::scoped_lock lm (_filter_graphs_mutex);
@@ -561,63 +546,11 @@ FFmpegDecoder::do_seek (double p, bool backwards)
return r < 0;
}
-shared_ptr<FFmpegAudioStream>
-FFmpegAudioStream::create (string t, optional<int> v)
-{
- if (!v) {
- /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
- return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
- }
-
- stringstream s (t);
- string type;
- s >> type;
- if (type != N_("ffmpeg")) {
- return shared_ptr<FFmpegAudioStream> ();
- }
-
- return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-}
-
-FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
-{
- stringstream n (t);
-
- int name_index = 4;
- if (!version) {
- name_index = 2;
- int channels;
- n >> _id >> channels;
- _channel_layout = av_get_default_channel_layout (channels);
- _sample_rate = 0;
- } else {
- string type;
- /* Current (marked version 1) */
- n >> type >> _id >> _sample_rate >> _channel_layout;
- assert (type == N_("ffmpeg"));
- }
-
- for (int i = 0; i < name_index; ++i) {
- size_t const s = t.find (' ');
- if (s != string::npos) {
- t = t.substr (s + 1);
- }
- }
-
- _name = t;
-}
-
-string
-FFmpegAudioStream::to_string () const
-{
- return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
-}
-
void
FFmpegDecoder::out_with_sync ()
{
/* Where we are in the output, in seconds */
- double const out_pts_seconds = video_frame() / frames_per_second();
+ double const out_pts_seconds = video_frame() / video_frame_rate();
/* Where we are in the source, in seconds */
double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
@@ -634,17 +567,17 @@ FFmpegDecoder::out_with_sync ()
/* Difference between where we are and where we should be */
double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
- double const one_frame = 1 / frames_per_second();
+ double const one_frame = 1 / video_frame_rate();
/* Insert frames if required to get out_pts_seconds up to pts_seconds */
if (delta > one_frame) {
int const extra = rint (delta / one_frame);
for (int i = 0; i < extra; ++i) {
- repeat_last_video ();
+ repeat_last_video (frame_time ());
_film->log()->log (
String::compose (
N_("Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)"),
- out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
+ out_pts_seconds, video_frame(), source_pts_seconds, video_frame_rate()
)
);
}
@@ -669,7 +602,6 @@ FFmpegDecoder::film_changed (Film::Property p)
boost::mutex::scoped_lock lm (_filter_graphs_mutex);
_filter_graphs.clear ();
}
- OutputChanged ();
break;
default:
@@ -678,10 +610,10 @@ FFmpegDecoder::film_changed (Film::Property p)
}
/** @return Length (in video frames) according to our content's header */
-SourceFrame
-FFmpegDecoder::length () const
+ContentVideoFrame
+FFmpegDecoder::video_length () const
{
- return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
+ return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
}
double
@@ -693,9 +625,6 @@ FFmpegDecoder::frame_time () const
void
FFmpegDecoder::decode_audio_packet ()
{
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
/* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
several times.
*/
@@ -716,9 +645,9 @@ FFmpegDecoder::decode_audio_packet ()
was before this packet. Until then audio is thrown away.
*/
- if ((_first_video && _first_video.get() <= source_pts_seconds) || !_opt.decode_video) {
+ if ((_first_video && _first_video.get() <= source_pts_seconds) || !_decode_video) {
- if (!_first_audio && _opt.decode_video) {
+ if (!_first_audio && _decode_video) {
_first_audio = source_pts_seconds;
/* This is our first audio frame, and if we've arrived here we must have had our
@@ -727,17 +656,17 @@ FFmpegDecoder::decode_audio_packet ()
*/
/* frames of silence that we must push */
- int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ());
+ int const s = rint ((_first_audio.get() - _first_video.get()) * _ffmpeg_content->audio_frame_rate ());
_film->log()->log (
String::compose (
N_("First video at %1, first audio at %2, pushing %3 audio frames of silence for %4 channels (%5 bytes per sample)"),
- _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample()
+ _first_video.get(), _first_audio.get(), s, _ffmpeg_content->audio_channels(), bytes_per_audio_sample()
)
);
if (s) {
- shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s));
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), s));
audio->make_silent ();
Audio (audio);
}
@@ -747,7 +676,7 @@ FFmpegDecoder::decode_audio_packet ()
0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
);
- assert (_audio_codec_context->channels == _film->audio_channels());
+ assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
Audio (deinterleave_audio (_frame->data, data_size));
}
}
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 1bb14ce9c..cd37d20c6 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -36,6 +36,7 @@ extern "C" {
#include "video_decoder.h"
#include "audio_decoder.h"
#include "film.h"
+#include "ffmpeg_content.h"
struct AVFilterGraph;
struct AVCodecContext;
@@ -50,62 +51,41 @@ class Options;
class Image;
class Log;
-class FFmpegAudioStream : public AudioStream
-{
-public:
- FFmpegAudioStream (std::string n, int i, int s, int64_t c)
- : AudioStream (s, c)
- , _name (n)
- , _id (i)
- {}
-
- std::string to_string () const;
-
- std::string name () const {
- return _name;
- }
-
- int id () const {
- return _id;
- }
-
- static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v);
-
-private:
- friend class stream_test;
-
- FFmpegAudioStream (std::string t, boost::optional<int> v);
-
- std::string _name;
- int _id;
-};
-
/** @class FFmpegDecoder
* @brief A decoder using FFmpeg to decode content.
*/
class FFmpegDecoder : public VideoDecoder, public AudioDecoder
{
public:
- FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions);
+ FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles, bool video_sync);
~FFmpegDecoder ();
- float frames_per_second () const;
+ float video_frame_rate () const;
libdcp::Size native_size () const;
- SourceFrame length () const;
+ ContentVideoFrame video_length () const;
int time_base_numerator () const;
int time_base_denominator () const;
int sample_aspect_ratio_numerator () const;
int sample_aspect_ratio_denominator () const;
- void set_audio_stream (boost::shared_ptr<AudioStream>);
- void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+ std::vector<FFmpegSubtitleStream> subtitle_streams () const {
+ return _subtitle_streams;
+ }
+
+ std::vector<FFmpegAudioStream> audio_streams () const {
+ return _audio_streams;
+ }
bool seek (double);
bool seek_to_last ();
+ bool pass ();
private:
- bool pass ();
+ /* No copy construction */
+ FFmpegDecoder (FFmpegDecoder const &);
+ FFmpegDecoder& operator= (FFmpegDecoder const &);
+
bool do_seek (double p, bool);
PixelFormat pixel_format () const;
AVSampleFormat audio_sample_format () const;
@@ -129,6 +109,8 @@ private:
std::string stream_name (AVStream* s) const;
+ boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
AVFormatContext* _format_context;
int _video_stream;
@@ -148,4 +130,18 @@ private:
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
boost::mutex _filter_graphs_mutex;
+
+ std::vector<FFmpegSubtitleStream> _subtitle_streams;
+ std::vector<FFmpegAudioStream> _audio_streams;
+
+ bool _decode_video;
+ bool _decode_audio;
+ bool _decode_subtitles;
+ bool _video_sync;
+
+ /* It would appear (though not completely verified) that one must have
+ a mutex around calls to avcodec_open* and avcodec_close... and here
+ it is.
+ */
+ static boost::mutex _mutex;
};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index bd11c1eb5..b36dc8f9c 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -29,29 +29,31 @@
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
#include "film.h"
#include "format.h"
#include "job.h"
#include "filter.h"
-#include "transcoder.h"
#include "util.h"
#include "job_manager.h"
#include "ab_transcode_job.h"
#include "transcode_job.h"
#include "scp_dcp_job.h"
#include "log.h"
-#include "options.h"
#include "exceptions.h"
#include "examine_content_job.h"
#include "scaler.h"
-#include "decoder_factory.h"
#include "config.h"
#include "version.h"
#include "ui_signaller.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "sndfile_decoder.h"
#include "analyse_audio_job.h"
+#include "playlist.h"
+#include "player.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_content.h"
+#include "sndfile_content.h"
+#include "dcp_content_type.h"
#include "i18n.h"
@@ -67,6 +69,7 @@ using std::setfill;
using std::min;
using std::make_pair;
using std::endl;
+using std::list;
using boost::shared_ptr;
using boost::lexical_cast;
using boost::to_upper_copy;
@@ -86,18 +89,17 @@ int const Film::state_version = 4;
*/
Film::Film (string d, bool must_exist)
- : _use_dci_name (true)
- , _trust_content_header (true)
+ : _playlist (new Playlist)
+ , _use_dci_name (true)
+ , _trust_content_headers (true)
, _dcp_content_type (0)
- , _format (0)
+ , _format (Format::from_id ("185"))
, _scaler (Scaler::from_id ("bicubic"))
, _trim_start (0)
, _trim_end (0)
- , _dcp_ab (false)
- , _use_content_audio (true)
+ , _ab (false)
, _audio_gain (0)
, _audio_delay (0)
- , _still_duration (10)
, _with_subtitles (false)
, _subtitle_offset (0)
, _subtitle_scale (1)
@@ -105,10 +107,11 @@ Film::Film (string d, bool must_exist)
, _j2k_bandwidth (200000000)
, _dci_metadata (Config::instance()->default_dci_metadata ())
, _dcp_frame_rate (0)
- , _source_frame_rate (0)
, _dirty (false)
{
set_dci_date_today ();
+
+ _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
/* Make state.directory a complete path without ..s (where possible)
(Code swiped from Adam Bowen on stackoverflow)
@@ -138,8 +141,6 @@ Film::Film (string d, bool must_exist)
}
}
- _sndfile_stream = SndfileStream::create ();
-
if (must_exist) {
read_metadata ();
}
@@ -151,11 +152,11 @@ Film::Film (Film const & o)
: boost::enable_shared_from_this<Film> (o)
/* note: the copied film shares the original's log */
, _log (o._log)
+ , _playlist (new Playlist)
, _directory (o._directory)
, _name (o._name)
, _use_dci_name (o._use_dci_name)
- , _content (o._content)
- , _trust_content_header (o._trust_content_header)
+ , _trust_content_headers (o._trust_content_headers)
, _dcp_content_type (o._dcp_content_type)
, _format (o._format)
, _crop (o._crop)
@@ -163,37 +164,26 @@ Film::Film (Film const & o)
, _scaler (o._scaler)
, _trim_start (o._trim_start)
, _trim_end (o._trim_end)
- , _dcp_ab (o._dcp_ab)
- , _content_audio_stream (o._content_audio_stream)
- , _external_audio (o._external_audio)
- , _use_content_audio (o._use_content_audio)
+ , _ab (o._ab)
, _audio_gain (o._audio_gain)
, _audio_delay (o._audio_delay)
- , _still_duration (o._still_duration)
- , _subtitle_stream (o._subtitle_stream)
, _with_subtitles (o._with_subtitles)
, _subtitle_offset (o._subtitle_offset)
, _subtitle_scale (o._subtitle_scale)
, _colour_lut (o._colour_lut)
, _j2k_bandwidth (o._j2k_bandwidth)
, _dci_metadata (o._dci_metadata)
- , _dci_date (o._dci_date)
, _dcp_frame_rate (o._dcp_frame_rate)
- , _size (o._size)
- , _length (o._length)
- , _content_digest (o._content_digest)
- , _content_audio_streams (o._content_audio_streams)
- , _sndfile_stream (o._sndfile_stream)
- , _subtitle_streams (o._subtitle_streams)
- , _source_frame_rate (o._source_frame_rate)
+ , _dci_date (o._dci_date)
, _dirty (o._dirty)
{
+ for (ContentList::const_iterator i = o._content.begin(); i != o._content.end(); ++i) {
+ _content.push_back ((*i)->clone ());
+ }
-}
-
-Film::~Film ()
-{
-
+ _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
+
+ _playlist->setup (_content);
}
string
@@ -201,6 +191,10 @@ Film::video_state_identifier () const
{
assert (format ());
+ return "XXX";
+
+#if 0
+
pair<string, string> f = Filter::ffmpeg_strings (filters());
stringstream s;
@@ -213,12 +207,13 @@ Film::video_state_identifier () const
<< "_" << j2k_bandwidth()
<< "_" << boost::lexical_cast<int> (colour_lut());
- if (dcp_ab()) {
+ if (ab()) {
pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
}
return s.str ();
+#endif
}
/** @return The path to the directory to write video frame info files to */
@@ -249,7 +244,7 @@ Film::audio_analysis_path () const
{
boost::filesystem::path p;
p /= "analysis";
- p /= content_digest();
+ p /= "XXX";//content_digest();
return file (p.string ());
}
@@ -271,12 +266,12 @@ Film::make_dcp ()
log()->log (String::compose ("Starting to make DCP on %1", buffer));
}
- log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
- if (length()) {
- log()->log (String::compose ("Content length %1", length().get()));
- }
- log()->log (String::compose ("Content digest %1", content_digest()));
- log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
+// log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
+// if (length()) {
+// log()->log (String::compose ("Content length %1", length().get()));
+// }
+// log()->log (String::compose ("Content digest %1", content_digest()));
+// log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
#ifdef DVDOMATIC_DEBUG
@@ -308,19 +303,16 @@ Film::make_dcp ()
throw MissingSettingError (_("name"));
}
- DecodeOptions od;
- od.decode_subtitles = with_subtitles ();
-
shared_ptr<Job> r;
- if (dcp_ab()) {
- r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
+ if (ab()) {
+ r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
} else {
- r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
+ r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
}
}
-/** Start a job to analyse the audio of our content file */
+/** Start a job to analyse the audio in our Playlist */
void
Film::analyse_audio ()
{
@@ -333,17 +325,12 @@ Film::analyse_audio ()
JobManager::instance()->add (_analyse_audio_job);
}
-/** Start a job to examine our content file */
+/** Start a job to examine a piece of content */
void
-Film::examine_content ()
+Film::examine_content (shared_ptr<Content> c)
{
- if (_examine_content_job) {
- return;
- }
-
- _examine_content_job.reset (new ExamineContentJob (shared_from_this()));
- _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
- JobManager::instance()->add (_examine_content_job);
+ shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c, trust_content_headers ()));
+ JobManager::instance()->add (j);
}
void
@@ -358,12 +345,6 @@ Film::analyse_audio_finished ()
_analyse_audio_job.reset ();
}
-void
-Film::examine_content_finished ()
-{
- _examine_content_job.reset ();
-}
-
/** Start a job to send our DCP to the configured TMS */
void
Film::send_dcp_to_tms ()
@@ -395,77 +376,55 @@ Film::encoded_frames () const
void
Film::write_metadata () const
{
+ ContentList the_content = content ();
+
boost::mutex::scoped_lock lm (_state_mutex);
boost::filesystem::create_directories (directory());
- string const m = file ("metadata");
- ofstream f (m.c_str ());
- if (!f.good ()) {
- throw CreateFileError (m);
- }
-
- f << "version " << state_version << endl;
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("Metadata");
- /* User stuff */
- f << "name " << _name << endl;
- f << "use_dci_name " << _use_dci_name << endl;
- f << "content " << _content << endl;
- f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
+ root->add_child("Version")->add_child_text (boost::lexical_cast<string> (state_version));
+ root->add_child("Name")->add_child_text (_name);
+ root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
+ root->add_child("TrustContentHeaders")->add_child_text (_trust_content_headers ? "1" : "0");
if (_dcp_content_type) {
- f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
+ root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
}
if (_format) {
- f << "format " << _format->as_metadata () << endl;
- }
- f << "left_crop " << _crop.left << endl;
- f << "right_crop " << _crop.right << endl;
- f << "top_crop " << _crop.top << endl;
- f << "bottom_crop " << _crop.bottom << endl;
- for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
- f << "filter " << (*i)->id () << endl;
- }
- f << "scaler " << _scaler->id () << endl;
- f << "trim_start " << _trim_start << endl;
- f << "trim_end " << _trim_end << endl;
- f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
- if (_content_audio_stream) {
- f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
+ root->add_child("Format")->add_child_text (_format->id ());
}
- for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
- f << "external_audio " << *i << endl;
- }
- f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
- f << "audio_gain " << _audio_gain << endl;
- f << "audio_delay " << _audio_delay << endl;
- f << "still_duration " << _still_duration << endl;
- if (_subtitle_stream) {
- f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
- }
- f << "with_subtitles " << _with_subtitles << endl;
- f << "subtitle_offset " << _subtitle_offset << endl;
- f << "subtitle_scale " << _subtitle_scale << endl;
- f << "colour_lut " << _colour_lut << endl;
- f << "j2k_bandwidth " << _j2k_bandwidth << endl;
- _dci_metadata.write (f);
- f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
- f << "dcp_frame_rate " << _dcp_frame_rate << endl;
- f << "width " << _size.width << endl;
- f << "height " << _size.height << endl;
- f << "length " << _length.get_value_or(0) << endl;
- f << "content_digest " << _content_digest << endl;
-
- for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
- f << "content_audio_stream " << (*i)->to_string () << endl;
- }
-
- f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
+ root->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
+ root->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
+ root->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
+ root->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
- for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
- f << "subtitle_stream " << (*i)->to_string () << endl;
+ for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+ root->add_child("Filter")->add_child_text ((*i)->id ());
}
-
- f << "source_frame_rate " << _source_frame_rate << endl;
+
+ root->add_child("Scaler")->add_child_text (_scaler->id ());
+ root->add_child("TrimStart")->add_child_text (boost::lexical_cast<string> (_trim_start));
+ root->add_child("TrimEnd")->add_child_text (boost::lexical_cast<string> (_trim_end));
+ root->add_child("AB")->add_child_text (_ab ? "1" : "0");
+ root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain));
+ root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay));
+ root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
+ root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset));
+ root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale));
+ root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut));
+ root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth));
+ _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+ root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate));
+ root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+ _audio_mapping.as_xml (root->add_child("AudioMapping"));
+
+ for (ContentList::iterator i = the_content.begin(); i != the_content.end(); ++i) {
+ (*i)->as_xml (root->add_child ("Content"));
+ }
+
+ doc.write_to_file_formatted (file ("metadata.xml"));
_dirty = false;
}
@@ -476,160 +435,80 @@ Film::read_metadata ()
{
boost::mutex::scoped_lock lm (_state_mutex);
- _external_audio.clear ();
- _content_audio_streams.clear ();
- _subtitle_streams.clear ();
-
- boost::optional<int> version;
-
- /* Backward compatibility things */
- boost::optional<int> audio_sample_rate;
- boost::optional<int> audio_stream_index;
- boost::optional<int> subtitle_stream_index;
-
- ifstream f (file ("metadata").c_str());
- if (!f.good()) {
- throw OpenFileError (file ("metadata"));
+ if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+ throw StringError (_("This film was created with an older version of DVD-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!"));
}
-
- multimap<string, string> kv = read_key_value (f);
- /* We need version before anything else */
- multimap<string, string>::iterator v = kv.find ("version");
- if (v != kv.end ()) {
- version = atoi (v->second.c_str());
- }
+ cxml::File f (file ("metadata.xml"), "Metadata");
- for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
- string const k = i->first;
- string const v = i->second;
+ _name = f.string_child ("Name");
+ _use_dci_name = f.bool_child ("UseDCIName");
+ _trust_content_headers = f.bool_child ("TrustContentHeaders");
- if (k == "audio_sample_rate") {
- audio_sample_rate = atoi (v.c_str());
- }
-
- /* User-specified stuff */
- if (k == "name") {
- _name = v;
- } else if (k == "use_dci_name") {
- _use_dci_name = (v == "1");
- } else if (k == "content") {
- _content = v;
- } else if (k == "trust_content_header") {
- _trust_content_header = (v == "1");
- } else if (k == "dcp_content_type") {
- if (version < 3) {
- _dcp_content_type = DCPContentType::from_pretty_name (v);
- } else {
- _dcp_content_type = DCPContentType::from_dci_name (v);
- }
- } else if (k == "format") {
- _format = Format::from_metadata (v);
- } else if (k == "left_crop") {
- _crop.left = atoi (v.c_str ());
- } else if (k == "right_crop") {
- _crop.right = atoi (v.c_str ());
- } else if (k == "top_crop") {
- _crop.top = atoi (v.c_str ());
- } else if (k == "bottom_crop") {
- _crop.bottom = atoi (v.c_str ());
- } else if (k == "filter") {
- _filters.push_back (Filter::from_id (v));
- } else if (k == "scaler") {
- _scaler = Scaler::from_id (v);
- } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
- _trim_start = atoi (v.c_str ());
- } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
- _trim_end = atoi (v.c_str ());
- } else if (k == "dcp_ab") {
- _dcp_ab = (v == "1");
- } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
- if (!version) {
- audio_stream_index = atoi (v.c_str ());
- } else {
- _content_audio_stream = audio_stream_factory (v, version);
- }
- } else if (k == "external_audio") {
- _external_audio.push_back (v);
- } else if (k == "use_content_audio") {
- _use_content_audio = (v == "1");
- } else if (k == "audio_gain") {
- _audio_gain = atof (v.c_str ());
- } else if (k == "audio_delay") {
- _audio_delay = atoi (v.c_str ());
- } else if (k == "still_duration") {
- _still_duration = atoi (v.c_str ());
- } else if (k == "selected_subtitle_stream") {
- if (!version) {
- subtitle_stream_index = atoi (v.c_str ());
- } else {
- _subtitle_stream = subtitle_stream_factory (v, version);
- }
- } else if (k == "with_subtitles") {
- _with_subtitles = (v == "1");
- } else if (k == "subtitle_offset") {
- _subtitle_offset = atoi (v.c_str ());
- } else if (k == "subtitle_scale") {
- _subtitle_scale = atof (v.c_str ());
- } else if (k == "colour_lut") {
- _colour_lut = atoi (v.c_str ());
- } else if (k == "j2k_bandwidth") {
- _j2k_bandwidth = atoi (v.c_str ());
- } else if (k == "dci_date") {
- _dci_date = boost::gregorian::from_undelimited_string (v);
- } else if (k == "dcp_frame_rate") {
- _dcp_frame_rate = atoi (v.c_str ());
+ {
+ optional<string> c = f.optional_string_child ("DCPContentType");
+ if (c) {
+ _dcp_content_type = DCPContentType::from_dci_name (c.get ());
}
+ }
- _dci_metadata.read (k, v);
-
- /* Cached stuff */
- if (k == "width") {
- _size.width = atoi (v.c_str ());
- } else if (k == "height") {
- _size.height = atoi (v.c_str ());
- } else if (k == "length") {
- int const vv = atoi (v.c_str ());
- if (vv) {
- _length = vv;
- }
- } else if (k == "content_digest") {
- _content_digest = v;
- } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
- _content_audio_streams.push_back (audio_stream_factory (v, version));
- } else if (k == "external_audio_stream") {
- _sndfile_stream = audio_stream_factory (v, version);
- } else if (k == "subtitle_stream") {
- _subtitle_streams.push_back (subtitle_stream_factory (v, version));
- } else if (k == "source_frame_rate") {
- _source_frame_rate = atof (v.c_str ());
- } else if (version < 4 && k == "frames_per_second") {
- _source_frame_rate = atof (v.c_str ());
- /* Fill in what would have been used for DCP frame rate by the older version */
- _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
+ {
+ optional<string> c = f.optional_string_child ("Format");
+ if (c) {
+ _format = Format::from_id (c.get ());
}
}
- if (!version) {
- if (audio_sample_rate) {
- /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
- for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
- (*i)->set_sample_rate (audio_sample_rate.get());
- }
- }
+ _crop.left = f.number_child<int> ("LeftCrop");
+ _crop.right = f.number_child<int> ("RightCrop");
+ _crop.top = f.number_child<int> ("TopCrop");
+ _crop.bottom = f.number_child<int> ("BottomCrop");
- /* also the selected stream was specified as an index */
- if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) {
- _content_audio_stream = _content_audio_streams[audio_stream_index.get()];
+ {
+ list<shared_ptr<cxml::Node> > c = f.node_children ("Filter");
+ for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+ _filters.push_back (Filter::from_id ((*i)->content ()));
}
+ }
- /* similarly the subtitle */
- if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) {
- _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()];
+ _scaler = Scaler::from_id (f.string_child ("Scaler"));
+ _trim_start = f.number_child<int> ("TrimStart");
+ _trim_end = f.number_child<int> ("TrimEnd");
+ _ab = f.bool_child ("AB");
+ _audio_gain = f.number_child<float> ("AudioGain");
+ _audio_delay = f.number_child<int> ("AudioDelay");
+ _with_subtitles = f.bool_child ("WithSubtitles");
+ _subtitle_offset = f.number_child<float> ("SubtitleOffset");
+ _subtitle_scale = f.number_child<float> ("SubtitleScale");
+ _colour_lut = f.number_child<int> ("ColourLUT");
+ _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+ _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+ _dcp_frame_rate = f.number_child<int> ("DCPFrameRate");
+ _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+
+ list<shared_ptr<cxml::Node> > c = f.node_children ("Content");
+ for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+
+ string const type = (*i)->string_child ("Type");
+ boost::shared_ptr<Content> c;
+
+ if (type == "FFmpeg") {
+ c.reset (new FFmpegContent (*i));
+ } else if (type == "ImageMagick") {
+ c.reset (new ImageMagickContent (*i));
+ } else if (type == "Sndfile") {
+ c.reset (new SndfileContent (*i));
}
+
+ _content.push_back (c);
}
-
+
+ /* This must come after we've loaded the content, as we're looking things up in _content */
+ _audio_mapping.set_from_xml (_content, f.node_child ("AudioMapping"));
+
_dirty = false;
+
+ _playlist->setup (_content);
}
libdcp::Size
@@ -676,47 +555,18 @@ Film::file (string f) const
return p.string ();
}
-/** @return full path of the content (actual video) file
- * of the Film.
- */
-string
-Film::content_path () const
-{
- boost::mutex::scoped_lock lm (_state_mutex);
- if (boost::filesystem::path(_content).has_root_directory ()) {
- return _content;
- }
-
- return file (_content);
-}
-
-ContentType
-Film::content_type () const
-{
- if (boost::filesystem::is_directory (_content)) {
- /* Directory of images, we assume */
- return VIDEO;
- }
-
- if (still_image_file (_content)) {
- return STILL;
- }
-
- return VIDEO;
-}
-
/** @return The sampling rate that we will resample the audio to */
int
Film::target_audio_sample_rate () const
{
- if (!audio_stream()) {
+ if (!has_audio ()) {
return 0;
}
/* Resample to a DCI-approved sample rate */
- double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
+ double t = dcp_audio_sample_rate (audio_frame_rate());
- FrameRateConversion frc (source_frame_rate(), dcp_frame_rate());
+ FrameRateConversion frc (video_frame_rate(), dcp_frame_rate());
/* Compensate if the DCP is being run at a different frame rate
to the source; that is, if the video is run such that it will
@@ -725,18 +575,12 @@ Film::target_audio_sample_rate () const
*/
if (frc.change_speed) {
- t *= source_frame_rate() * frc.factor() / dcp_frame_rate();
+ t *= video_frame_rate() * frc.factor() / dcp_frame_rate();
}
return rint (t);
}
-int
-Film::still_duration_in_frames () const
-{
- return still_duration() * source_frame_rate();
-}
-
/** @return a DCI-compliant name for a DCP of this film */
string
Film::dci_name (bool if_created_now) const
@@ -783,7 +627,7 @@ Film::dci_name (bool if_created_now) const
}
}
- switch (audio_channels()) {
+ switch (audio_channels ()) {
case 1:
d << "_10";
break;
@@ -862,110 +706,21 @@ Film::set_use_dci_name (bool u)
}
void
-Film::set_content (string c)
-{
- string check = directory ();
-
- boost::filesystem::path slash ("/");
- string platform_slash = slash.make_preferred().string ();
-
- if (!ends_with (check, platform_slash)) {
- check += platform_slash;
- }
-
- if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
- c = c.substr (_directory.length() + 1);
- }
-
- string old_content;
-
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (c == _content) {
- return;
- }
-
- old_content = _content;
- _content = c;
- }
-
- /* Reset streams here in case the new content doesn't have one or the other */
- _content_audio_stream = shared_ptr<AudioStream> ();
- _subtitle_stream = shared_ptr<SubtitleStream> ();
-
- /* Start off using content audio */
- set_use_content_audio (true);
-
- /* Create a temporary decoder so that we can get information
- about the content.
- */
-
- try {
- Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
-
- set_size (d.video->native_size ());
- set_source_frame_rate (d.video->frames_per_second ());
- set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
- set_subtitle_streams (d.video->subtitle_streams ());
- if (d.audio) {
- set_content_audio_streams (d.audio->audio_streams ());
- }
-
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = c;
- }
-
- signal_changed (CONTENT);
-
- /* Start off with the first audio and subtitle streams */
- if (d.audio && !d.audio->audio_streams().empty()) {
- set_content_audio_stream (d.audio->audio_streams().front());
- }
-
- if (!d.video->subtitle_streams().empty()) {
- set_subtitle_stream (d.video->subtitle_streams().front());
- }
-
- examine_content ();
-
- } catch (...) {
-
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = old_content;
- throw;
-
- }
-
- /* Default format */
- switch (content_type()) {
- case STILL:
- set_format (Format::from_id ("var-185"));
- break;
- case VIDEO:
- set_format (Format::from_id ("185"));
- break;
- }
-
- /* Still image DCPs must use external audio */
- if (content_type() == STILL) {
- set_use_content_audio (false);
- }
-}
-
-void
-Film::set_trust_content_header (bool t)
+Film::set_trust_content_headers (bool t)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _trust_content_header = t;
+ _trust_content_headers = t;
}
- signal_changed (TRUST_CONTENT_HEADER);
+ signal_changed (TRUST_CONTENT_HEADERS);
- if (!_trust_content_header && !content().empty()) {
+ if (!_trust_content_headers && !content().empty()) {
/* We just said that we don't trust the content's header */
- examine_content ();
+ ContentList c = content ();
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+ examine_content (*i);
+ }
}
}
@@ -1097,50 +852,13 @@ Film::set_trim_end (int t)
}
void
-Film::set_dcp_ab (bool a)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_ab = a;
- }
- signal_changed (DCP_AB);
-}
-
-void
-Film::set_content_audio_stream (shared_ptr<AudioStream> s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_audio_stream = s;
- }
- signal_changed (CONTENT_AUDIO_STREAM);
-}
-
-void
-Film::set_external_audio (vector<string> a)
+Film::set_ab (bool a)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _external_audio = a;
+ _ab = a;
}
-
- shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
- if (decoder->audio_stream()) {
- _sndfile_stream = decoder->audio_stream ();
- }
-
- signal_changed (EXTERNAL_AUDIO);
-}
-
-void
-Film::set_use_content_audio (bool e)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _use_content_audio = e;
- }
-
- signal_changed (USE_CONTENT_AUDIO);
+ signal_changed (AB);
}
void
@@ -1164,26 +882,6 @@ Film::set_audio_delay (int d)
}
void
-Film::set_still_duration (int d)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _still_duration = d;
- }
- signal_changed (STILL_DURATION);
-}
-
-void
-Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_stream = s;
- }
- signal_changed (SUBTITLE_STREAM);
-}
-
-void
Film::set_with_subtitles (bool w)
{
{
@@ -1255,184 +953,314 @@ Film::set_dcp_frame_rate (int f)
}
void
-Film::set_size (libdcp::Size s)
+Film::signal_changed (Property p)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _size = s;
+ _dirty = true;
+ }
+
+ switch (p) {
+ case Film::CONTENT:
+ _playlist->setup (content ());
+ set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
+ set_audio_mapping (_playlist->default_audio_mapping ());
+ break;
+ default:
+ break;
+ }
+
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (Changed), p));
}
- signal_changed (SIZE);
}
void
-Film::set_length (SourceFrame l)
+Film::set_dci_date_today ()
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _length = l;
- }
- signal_changed (LENGTH);
+ _dci_date = boost::gregorian::day_clock::local_day ();
}
-void
-Film::unset_length ()
+string
+Film::info_path (int f) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _length = boost::none;
+ boost::filesystem::path p;
+ p /= info_dir ();
+
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << f << ".md5";
+
+ p /= s.str();
+
+ /* info_dir() will already have added any initial bit of the path,
+ so don't call file() on this.
+ */
+ return p.string ();
+}
+
+string
+Film::j2c_path (int f, bool t) const
+{
+ boost::filesystem::path p;
+ p /= "j2c";
+ p /= video_state_identifier ();
+
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << f << ".j2c";
+
+ if (t) {
+ s << ".tmp";
}
- signal_changed (LENGTH);
+
+ p /= s.str();
+ return file (p.string ());
}
-void
-Film::set_content_digest (string d)
+/** Make an educated guess as to whether we have a complete DCP
+ * or not.
+ * @return true if we do.
+ */
+
+bool
+Film::have_dcp () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_digest = d;
+ try {
+ libdcp::DCP dcp (dir (dcp_name()));
+ dcp.read ();
+ } catch (...) {
+ return false;
}
- _dirty = true;
+
+ return true;
+}
+
+shared_ptr<Player>
+Film::player () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
}
void
-Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
+Film::add_content (shared_ptr<Content> c)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _content_audio_streams = s;
+ _content.push_back (c);
}
- signal_changed (CONTENT_AUDIO_STREAMS);
+
+ signal_changed (CONTENT);
+
+ examine_content (c);
}
void
-Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
+Film::remove_content (shared_ptr<Content> c)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_streams = s;
+ ContentList::iterator i = find (_content.begin(), _content.end(), c);
+ if (i != _content.end ()) {
+ _content.erase (i);
+ }
}
- signal_changed (SUBTITLE_STREAMS);
+
+ signal_changed (CONTENT);
}
void
-Film::set_source_frame_rate (float f)
+Film::move_content_earlier (shared_ptr<Content> c)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _source_frame_rate = f;
+ ContentList::iterator i = find (_content.begin(), _content.end(), c);
+ if (i == _content.begin () || i == _content.end()) {
+ return;
+ }
+
+ ContentList::iterator j = i;
+ --j;
+
+ swap (*i, *j);
+ _playlist->setup (_content);
}
- signal_changed (SOURCE_FRAME_RATE);
+
+ signal_changed (CONTENT);
}
-
+
void
-Film::signal_changed (Property p)
+Film::move_content_later (shared_ptr<Content> c)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _dirty = true;
- }
+ ContentList::iterator i = find (_content.begin(), _content.end(), c);
+ if (i == _content.end()) {
+ return;
+ }
- if (ui_signaller) {
- ui_signaller->emit (boost::bind (boost::ref (Changed), p));
+ ContentList::iterator j = i;
+ ++j;
+ if (j == _content.end ()) {
+ return;
+ }
+
+ swap (*i, *j);
+ _playlist->setup (_content);
}
+
+ signal_changed (CONTENT);
+
+}
+
+ContentAudioFrame
+Film::audio_length () const
+{
+ return _playlist->audio_length ();
}
int
Film::audio_channels () const
{
- shared_ptr<AudioStream> s = audio_stream ();
- if (!s) {
- return 0;
- }
+ return _playlist->audio_channels ();
+}
- return s->channels ();
+int
+Film::audio_frame_rate () const
+{
+ return _playlist->audio_frame_rate ();
}
-void
-Film::set_dci_date_today ()
+int64_t
+Film::audio_channel_layout () const
{
- _dci_date = boost::gregorian::day_clock::local_day ();
+ return _playlist->audio_channel_layout ();
}
-boost::shared_ptr<AudioStream>
-Film::audio_stream () const
+bool
+Film::has_audio () const
{
- if (use_content_audio()) {
- return _content_audio_stream;
- }
+ return _playlist->has_audio ();
+}
- return _sndfile_stream;
+float
+Film::video_frame_rate () const
+{
+ return _playlist->video_frame_rate ();
}
-string
-Film::info_path (int f) const
+libdcp::Size
+Film::video_size () const
{
- boost::filesystem::path p;
- p /= info_dir ();
+ return _playlist->video_size ();
+}
- stringstream s;
- s.width (8);
- s << setfill('0') << f << ".md5";
+ContentVideoFrame
+Film::video_length () const
+{
+ return _playlist->video_length ();
+}
- p /= s.str();
+/** Unfortunately this is needed as the GUI has FFmpeg-specific controls */
+shared_ptr<FFmpegContent>
+Film::ffmpeg () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+
+ for (ContentList::const_iterator i = _content.begin (); i != _content.end(); ++i) {
+ shared_ptr<FFmpegContent> f = boost::dynamic_pointer_cast<FFmpegContent> (*i);
+ if (f) {
+ return f;
+ }
+ }
- /* info_dir() will already have added any initial bit of the path,
- so don't call file() on this.
- */
- return p.string ();
+ return shared_ptr<FFmpegContent> ();
}
-string
-Film::j2c_path (int f, bool t) const
+vector<FFmpegSubtitleStream>
+Film::ffmpeg_subtitle_streams () const
{
- boost::filesystem::path p;
- p /= "j2c";
- p /= video_state_identifier ();
+ shared_ptr<FFmpegContent> f = ffmpeg ();
+ if (f) {
+ return f->subtitle_streams ();
+ }
- stringstream s;
- s.width (8);
- s << setfill('0') << f << ".j2c";
+ return vector<FFmpegSubtitleStream> ();
+}
- if (t) {
- s << ".tmp";
+boost::optional<FFmpegSubtitleStream>
+Film::ffmpeg_subtitle_stream () const
+{
+ shared_ptr<FFmpegContent> f = ffmpeg ();
+ if (f) {
+ return f->subtitle_stream ();
}
- p /= s.str();
- return file (p.string ());
+ return boost::none;
}
-/** Make an educated guess as to whether we have a complete DCP
- * or not.
- * @return true if we do.
- */
+vector<FFmpegAudioStream>
+Film::ffmpeg_audio_streams () const
+{
+ shared_ptr<FFmpegContent> f = ffmpeg ();
+ if (f) {
+ return f->audio_streams ();
+ }
-bool
-Film::have_dcp () const
+ return vector<FFmpegAudioStream> ();
+}
+
+boost::optional<FFmpegAudioStream>
+Film::ffmpeg_audio_stream () const
{
- try {
- libdcp::DCP dcp (dir (dcp_name()));
- dcp.read ();
- } catch (...) {
- return false;
+ shared_ptr<FFmpegContent> f = ffmpeg ();
+ if (f) {
+ return f->audio_stream ();
}
- return true;
+ return boost::none;
}
-bool
-Film::has_audio () const
+void
+Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s)
{
- if (use_content_audio()) {
- return audio_stream();
+ shared_ptr<FFmpegContent> f = ffmpeg ();
+ if (f) {
+ f->set_subtitle_stream (s);
}
+}
- vector<string> const e = external_audio ();
- for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
- if (!i->empty ()) {
- return true;
- }
+void
+Film::set_ffmpeg_audio_stream (FFmpegAudioStream s)
+{
+ shared_ptr<FFmpegContent> f = ffmpeg ();
+ if (f) {
+ f->set_audio_stream (s);
+ }
+}
+
+void
+Film::set_audio_mapping (AudioMapping m)
+{
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _audio_mapping = m;
}
- return false;
+ signal_changed (AUDIO_MAPPING);
}
+void
+Film::content_changed (boost::weak_ptr<Content> c, int p)
+{
+ if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
+ set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
+ } else if (p == AudioContentProperty::AUDIO_CHANNELS) {
+ set_audio_mapping (_playlist->default_audio_mapping ());
+ }
+
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
+ }
+}
diff --git a/src/lib/film.h b/src/lib/film.h
index adc4b0eec..532d32bdc 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -18,8 +18,8 @@
*/
/** @file src/film.h
- * @brief A representation of a piece of video (with sound), including naming,
- * the source content file, and how it should be presented in a DCP.
+ * @brief A representation of some audio and video content, and details of
+ * how they should be presented in a DCP.
*/
#ifndef DVDOMATIC_FILM_H
@@ -32,14 +32,13 @@
#include <boost/thread.hpp>
#include <boost/signals2.hpp>
#include <boost/enable_shared_from_this.hpp>
-extern "C" {
-#include <libavcodec/avcodec.h>
-}
-#include "dcp_content_type.h"
#include "util.h"
-#include "stream.h"
#include "dci_metadata.h"
+#include "types.h"
+#include "ffmpeg_content.h"
+#include "audio_mapping.h"
+class DCPContentType;
class Format;
class Job;
class Filter;
@@ -47,19 +46,19 @@ class Log;
class ExamineContentJob;
class AnalyseAudioJob;
class ExternalAudioStream;
+class Content;
+class Player;
+class Playlist;
/** @class Film
- * @brief A representation of a video, maybe with sound.
- *
- * A representation of a piece of video (maybe with sound), including naming,
- * the source content file, and how it should be presented in a DCP.
+ * @brief A representation of some audio and video content, and details of
+ * how they should be presented in a DCP.
*/
class Film : public boost::enable_shared_from_this<Film>
{
public:
Film (std::string d, bool must_exist = true);
Film (Film const &);
- ~Film ();
std::string info_dir () const;
std::string j2c_path (int f, bool t) const;
@@ -68,10 +67,9 @@ public:
std::string video_mxf_filename () const;
std::string audio_analysis_path () const;
- void examine_content ();
+ void examine_content (boost::shared_ptr<Content>);
void analyse_audio ();
void send_dcp_to_tms ();
-
void make_dcp ();
/** @return Logger.
@@ -86,13 +84,9 @@ public:
std::string file (std::string f) const;
std::string dir (std::string d) const;
- std::string content_path () const;
- ContentType content_type () const;
-
int target_audio_sample_rate () const;
void write_metadata () const;
- void read_metadata ();
libdcp::Size cropped_size (libdcp::Size) const;
std::string dci_name (bool if_created_now) const;
@@ -103,11 +97,29 @@ public:
return _dirty;
}
+ bool have_dcp () const;
+
+ boost::shared_ptr<Player> player () const;
+
+ /* Proxies for some Playlist methods */
+
+ ContentAudioFrame audio_length () const;
int audio_channels () const;
+ int audio_frame_rate () const;
+ int64_t audio_channel_layout () const;
+ bool has_audio () const;
+
+ float video_frame_rate () const;
+ libdcp::Size video_size () const;
+ ContentVideoFrame video_length () const;
- void set_dci_date_today ();
+ std::vector<FFmpegSubtitleStream> ffmpeg_subtitle_streams () const;
+ boost::optional<FFmpegSubtitleStream> ffmpeg_subtitle_stream () const;
+ std::vector<FFmpegAudioStream> ffmpeg_audio_streams () const;
+ boost::optional<FFmpegAudioStream> ffmpeg_audio_stream () const;
- bool have_dcp () const;
+ void set_ffmpeg_subtitle_stream (FFmpegSubtitleStream);
+ void set_ffmpeg_audio_stream (FFmpegAudioStream);
/** Identifiers for the parts of our state;
used for signalling changes.
@@ -116,8 +128,9 @@ public:
NONE,
NAME,
USE_DCI_NAME,
+ TRUST_CONTENT_HEADERS,
+ /** The content list has changed (i.e. content has been added, moved around or removed) */
CONTENT,
- TRUST_CONTENT_HEADER,
DCP_CONTENT_TYPE,
FORMAT,
CROP,
@@ -125,26 +138,17 @@ public:
SCALER,
TRIM_START,
TRIM_END,
- DCP_AB,
- CONTENT_AUDIO_STREAM,
- EXTERNAL_AUDIO,
- USE_CONTENT_AUDIO,
+ AB,
AUDIO_GAIN,
AUDIO_DELAY,
- STILL_DURATION,
- SUBTITLE_STREAM,
WITH_SUBTITLES,
SUBTITLE_OFFSET,
SUBTITLE_SCALE,
COLOUR_LUT,
J2K_BANDWIDTH,
DCI_METADATA,
- SIZE,
- LENGTH,
- CONTENT_AUDIO_STREAMS,
- SUBTITLE_STREAMS,
- SOURCE_FRAME_RATE,
- DCP_FRAME_RATE
+ DCP_FRAME_RATE,
+ AUDIO_MAPPING
};
@@ -165,14 +169,14 @@ public:
return _use_dci_name;
}
- std::string content () const {
+ bool trust_content_headers () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _content;
+ return _trust_content_headers;
}
- bool trust_content_header () const {
+ ContentList content () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _trust_content_header;
+ return _content;
}
DCPContentType const * dcp_content_type () const {
@@ -210,26 +214,11 @@ public:
return _trim_end;
}
- bool dcp_ab () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_ab;
- }
-
- boost::shared_ptr<AudioStream> content_audio_stream () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_audio_stream;
- }
-
- std::vector<std::string> external_audio () const {
+ bool ab () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _external_audio;
+ return _ab;
}
- bool use_content_audio () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _use_content_audio;
- }
-
float audio_gain () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _audio_gain;
@@ -240,18 +229,6 @@ public:
return _audio_delay;
}
- int still_duration () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _still_duration;
- }
-
- int still_duration_in_frames () const;
-
- boost::shared_ptr<SubtitleStream> subtitle_stream () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_stream;
- }
-
bool with_subtitles () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _with_subtitles;
@@ -286,51 +263,22 @@ public:
boost::mutex::scoped_lock lm (_state_mutex);
return _dcp_frame_rate;
}
-
- libdcp::Size size () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _size;
- }
-
- boost::optional<SourceFrame> length () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _length;
- }
-
- std::string content_digest () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_digest;
- }
-
- std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_audio_streams;
- }
- std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
+ AudioMapping audio_mapping () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_streams;
- }
-
- float source_frame_rate () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (content_type() == STILL) {
- return 24;
- }
-
- return _source_frame_rate;
+ return _audio_mapping;
}
- boost::shared_ptr<AudioStream> audio_stream () const;
- bool has_audio () const;
-
/* SET */
void set_directory (std::string);
void set_name (std::string);
void set_use_dci_name (bool);
- void set_content (std::string);
- void set_trust_content_header (bool);
+ void set_trust_content_headers (bool);
+ void add_content (boost::shared_ptr<Content>);
+ void remove_content (boost::shared_ptr<Content>);
+ void move_content_earlier (boost::shared_ptr<Content>);
+ void move_content_later (boost::shared_ptr<Content>);
void set_dcp_content_type (DCPContentType const *);
void set_format (Format const *);
void set_crop (Crop);
@@ -342,14 +290,9 @@ public:
void set_scaler (Scaler const *);
void set_trim_start (int);
void set_trim_end (int);
- void set_dcp_ab (bool);
- void set_content_audio_stream (boost::shared_ptr<AudioStream>);
- void set_external_audio (std::vector<std::string>);
- void set_use_content_audio (bool);
+ void set_ab (bool);
void set_audio_gain (float);
void set_audio_delay (int);
- void set_still_duration (int);
- void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
void set_with_subtitles (bool);
void set_subtitle_offset (int);
void set_subtitle_scale (float);
@@ -357,17 +300,15 @@ public:
void set_j2k_bandwidth (int);
void set_dci_metadata (DCIMetadata);
void set_dcp_frame_rate (int);
- void set_size (libdcp::Size);
- void set_length (SourceFrame);
- void unset_length ();
- void set_content_digest (std::string);
- void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
- void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
- void set_source_frame_rate (float);
-
- /** Emitted when some property has changed */
+ void set_dci_date_today ();
+ void set_audio_mapping (AudioMapping);
+
+ /** Emitted when some property has of the Film has changed */
mutable boost::signals2::signal<void (Property)> Changed;
+ /** Emitted when some property of our content has changed */
+ mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+
boost::signals2::signal<void ()> AudioAnalysisSucceeded;
/** Current version number of the state file */
@@ -375,18 +316,19 @@ public:
private:
+ void signal_changed (Property);
+ void analyse_audio_finished ();
+ std::string video_state_identifier () const;
+ void read_metadata ();
+ void content_changed (boost::weak_ptr<Content>, int);
+ boost::shared_ptr<FFmpegContent> ffmpeg () const;
+ void setup_default_audio_mapping ();
+
/** Log to write to */
boost::shared_ptr<Log> _log;
-
- /** Any running ExamineContentJob, or 0 */
- boost::shared_ptr<ExamineContentJob> _examine_content_job;
/** Any running AnalyseAudioJob, or 0 */
boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
-
- void signal_changed (Property);
- void examine_content_finished ();
- void analyse_audio_finished ();
- std::string video_state_identifier () const;
+ boost::shared_ptr<Playlist> _playlist;
/** Complete path to directory containing the film metadata;
* must not be relative.
@@ -399,15 +341,8 @@ private:
std::string _name;
/** True if a auto-generated DCI-compliant name should be used for our DCP */
bool _use_dci_name;
- /** File or directory containing content; may be relative to our directory
- * or an absolute path.
- */
- std::string _content;
- /** If this is true, we will believe the length specified by the content
- * file's header; if false, we will run through the whole content file
- * the first time we see it in order to obtain the length.
- */
- bool _trust_content_header;
+ bool _trust_content_headers;
+ ContentList _content;
/** The type of content that this Film represents (feature, trailer etc.) */
DCPContentType const * _dcp_content_type;
/** The format to present this Film in (flat, scope, etc.) */
@@ -426,22 +361,11 @@ private:
is the video without any filters or post-processing, and the right half
has the specified filters and post-processing.
*/
- bool _dcp_ab;
- /** The audio stream to use from our content */
- boost::shared_ptr<AudioStream> _content_audio_stream;
- /** List of filenames of external audio files, in channel order
- (L, R, C, Lfe, Ls, Rs)
- */
- std::vector<std::string> _external_audio;
- /** true to use audio from our content file; false to use external audio */
- bool _use_content_audio;
+ bool _ab;
/** Gain to apply to audio in dB */
float _audio_gain;
/** Delay to apply to audio (positive moves audio later) in milliseconds */
int _audio_delay;
- /** Duration to make still-sourced films (in seconds) */
- int _still_duration;
- boost::shared_ptr<SubtitleStream> _subtitle_stream;
/** True if subtitles should be shown for this film */
bool _with_subtitles;
/** y offset for placing subtitles, in source pixels; +ve is further down
@@ -457,30 +381,13 @@ private:
int _colour_lut;
/** bandwidth for J2K files in bits per second */
int _j2k_bandwidth;
-
/** DCI naming stuff */
DCIMetadata _dci_metadata;
- /** The date that we should use in a DCI name */
- boost::gregorian::date _dci_date;
/** Frames per second to run our DCP at */
int _dcp_frame_rate;
-
- /* Data which are cached to speed things up */
-
- /** Size, in pixels, of the source (ignoring cropping) */
- libdcp::Size _size;
- /** The length of the source, in video frames (as far as we know) */
- boost::optional<SourceFrame> _length;
- /** MD5 digest of our content file */
- std::string _content_digest;
- /** The audio streams in our content */
- std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams;
- /** A stream to represent possible external audio (will always exist) */
- boost::shared_ptr<AudioStream> _sndfile_stream;
- /** the subtitle streams that we can use */
- std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
- /** Frames per second of the source */
- float _source_frame_rate;
+ /** The date that we should use in a DCI name */
+ boost::gregorian::date _dci_date;
+ AudioMapping _audio_mapping;
/** true if our state has changed since we last saved it */
mutable bool _dirty;
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index 045cbaa6a..a52c030fe 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -57,7 +57,7 @@ using libdcp::Size;
* @param s Size of the images to process.
* @param p Pixel format of the images to process.
*/
-FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index 7e4e8422b..db86a677d 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -37,7 +37,7 @@ class FFmpegDecoder;
class FilterGraph
{
public:
- FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<const Film>, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
bool can_process (libdcp::Size s, AVPixelFormat p) const;
std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
diff --git a/src/lib/format.cc b/src/lib/format.cc
index 0ca97303e..5eda9eb88 100644
--- a/src/lib/format.cc
+++ b/src/lib/format.cc
@@ -29,6 +29,7 @@
#include <iostream>
#include "format.h"
#include "film.h"
+#include "playlist.h"
#include "i18n.h"
@@ -199,14 +200,14 @@ FixedFormat::FixedFormat (int r, libdcp::Size dcp, string id, string n, string d
int
Format::dcp_padding (shared_ptr<const Film> f) const
{
- int p = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_integer(f) / 100.0)) / 2.0);
+ int pad = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_integer(f) / 100.0)) / 2.0);
/* This comes out -ve for Scope; bodge it */
- if (p < 0) {
- p = 0;
+ if (pad < 0) {
+ pad = 0;
}
- return p;
+ return pad;
}
float
@@ -230,7 +231,7 @@ VariableFormat::ratio_as_integer (shared_ptr<const Film> f) const
float
VariableFormat::ratio_as_float (shared_ptr<const Film> f) const
{
- return float (f->size().width) / f->size().height;
+ return float (f->video_size().width) / f->video_size().height;
}
/** @return A name to be presented to the user */
diff --git a/src/lib/format.h b/src/lib/format.h
index 783ff25ce..7958ca534 100644
--- a/src/lib/format.h
+++ b/src/lib/format.h
@@ -49,7 +49,7 @@ public:
/** @return the ratio of the container (including any padding) as a floating point number */
float container_ratio_as_float () const;
- int dcp_padding (boost::shared_ptr<const Film> f) const;
+ int dcp_padding (boost::shared_ptr<const Film>) const;
/** @return size in pixels of the images that we should
* put in a DCP for this ratio. This size will not correspond
diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc
new file mode 100644
index 000000000..59fde40bb
--- /dev/null
+++ b/src/lib/imagemagick_content.cc
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "imagemagick_content.h"
+#include "imagemagick_decoder.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+ImageMagickContent::ImageMagickContent (boost::filesystem::path f)
+ : Content (f)
+ , VideoContent (f)
+{
+
+}
+
+ImageMagickContent::ImageMagickContent (shared_ptr<const cxml::Node> node)
+ : Content (node)
+ , VideoContent (node)
+{
+
+}
+
+string
+ImageMagickContent::summary () const
+{
+ return String::compose (_("Image: %1"), file().filename().string());
+}
+
+bool
+ImageMagickContent::valid_file (boost::filesystem::path f)
+{
+ string ext = f.extension().string();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+ return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp");
+}
+
+void
+ImageMagickContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("ImageMagick");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+}
+
+void
+ImageMagickContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
+{
+ Content::examine (film, job, quick);
+ shared_ptr<ImageMagickDecoder> decoder (new ImageMagickDecoder (film, shared_from_this()));
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ /* Initial length */
+ _video_length = 10 * 24;
+ }
+
+ take_from_video_decoder (decoder);
+
+ signal_changed (VideoContentProperty::VIDEO_LENGTH);
+}
+
+shared_ptr<Content>
+ImageMagickContent::clone () const
+{
+ return shared_ptr<Content> (new ImageMagickContent (*this));
+}
+
+void
+ImageMagickContent::set_video_length (ContentVideoFrame len)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _video_length = len;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_LENGTH);
+}
diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h
new file mode 100644
index 000000000..b1e7f9495
--- /dev/null
+++ b/src/lib/imagemagick_content.h
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+ class Node;
+}
+
+class ImageMagickContent : public VideoContent
+{
+public:
+ ImageMagickContent (boost::filesystem::path);
+ ImageMagickContent (boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<ImageMagickContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<ImageMagickContent> (Content::shared_from_this ());
+ };
+
+ void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+ std::string summary () const;
+ void as_xml (xmlpp::Node *) const;
+ boost::shared_ptr<Content> clone () const;
+
+ void set_video_length (ContentVideoFrame);
+
+ static bool valid_file (boost::filesystem::path);
+};
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
index 5dc0b7b06..6a2be1a7c 100644
--- a/src/lib/imagemagick_decoder.cc
+++ b/src/lib/imagemagick_decoder.cc
@@ -20,6 +20,7 @@
#include <iostream>
#include <boost/filesystem.hpp>
#include <Magick++.h>
+#include "imagemagick_content.h"
#include "imagemagick_decoder.h"
#include "image.h"
#include "film.h"
@@ -31,57 +32,46 @@ using std::cout;
using boost::shared_ptr;
using libdcp::Size;
-ImageMagickDecoder::ImageMagickDecoder (
- boost::shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , VideoDecoder (f, o)
+ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
+ : Decoder (f)
+ , VideoDecoder (f)
+ , _imagemagick_content (c)
+ , _position (0)
{
- if (boost::filesystem::is_directory (_film->content_path())) {
- for (
- boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (_film->content_path());
- i != boost::filesystem::directory_iterator();
- ++i) {
-
- if (still_image_file (i->path().string())) {
- _files.push_back (i->path().string());
- }
- }
- } else {
- _files.push_back (_film->content_path ());
- }
-
- _iter = _files.begin ();
+
}
libdcp::Size
ImageMagickDecoder::native_size () const
{
- if (_files.empty ()) {
- throw DecodeError (_("no still image files found"));
- }
-
- /* Look at the first file and assume its size holds for all */
using namespace MagickCore;
- Magick::Image* image = new Magick::Image (_film->content_path ());
+ Magick::Image* image = new Magick::Image (_imagemagick_content->file().string());
libdcp::Size const s = libdcp::Size (image->columns(), image->rows());
delete image;
return s;
}
+int
+ImageMagickDecoder::video_length () const
+{
+ return _imagemagick_content->video_length ();
+}
+
bool
ImageMagickDecoder::pass ()
{
- if (_iter == _files.end()) {
- if (video_frame() >= _film->still_duration_in_frames()) {
- return true;
- }
+ if (_position < 0 || _position >= _imagemagick_content->video_length ()) {
+ return true;
+ }
- repeat_last_video ();
+ if (have_last_video ()) {
+ repeat_last_video (double (_position) / 24);
+ _position++;
return false;
}
- Magick::Image* magick_image = new Magick::Image (_film->content_path ());
+ Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ());
libdcp::Size size = native_size ();
shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false));
@@ -102,9 +92,9 @@ ImageMagickDecoder::pass ()
image = image->crop (_film->crop(), true);
- emit_video (image, 0);
+ emit_video (image, double (_position) / 24);
- ++_iter;
+ ++_position;
return false;
}
@@ -118,10 +108,8 @@ ImageMagickDecoder::pixel_format () const
bool
ImageMagickDecoder::seek_to_last ()
{
- if (_iter == _files.end()) {
- _iter = _files.begin();
- } else {
- --_iter;
+ if (_position > 0) {
+ --_position;
}
return false;
@@ -130,23 +118,13 @@ ImageMagickDecoder::seek_to_last ()
bool
ImageMagickDecoder::seek (double t)
{
- int const f = t * frames_per_second();
-
- _iter = _files.begin ();
- for (int i = 0; i < f; ++i) {
- if (_iter == _files.end()) {
- return true;
- }
- ++_iter;
- }
-
- return false;
-}
+ int const f = t * _imagemagick_content->video_frame_rate ();
-void
-ImageMagickDecoder::film_changed (Film::Property p)
-{
- if (p == Film::CROP) {
- OutputChanged ();
+ if (f >= _imagemagick_content->video_length()) {
+ _position = 0;
+ return true;
}
+
+ _position = f;
+ return false;
}
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
index 2f4e2c967..cb524b44b 100644
--- a/src/lib/imagemagick_decoder.h
+++ b/src/lib/imagemagick_decoder.h
@@ -23,22 +23,19 @@ namespace Magick {
class Image;
}
+class ImageMagickContent;
+
class ImageMagickDecoder : public VideoDecoder
{
public:
- ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions);
+ ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
- float frames_per_second () const {
- /* We don't know */
- return 0;
+ float video_frame_rate () const {
+ return 24;
}
libdcp::Size native_size () const;
-
- SourceFrame length () const {
- /* We don't know */
- return 0;
- }
+ ContentVideoFrame video_length () const;
int audio_channels () const {
return 0;
@@ -54,9 +51,9 @@ public:
bool seek (double);
bool seek_to_last ();
+ bool pass ();
protected:
- bool pass ();
PixelFormat pixel_format () const;
int time_base_numerator () const {
@@ -78,8 +75,6 @@ protected:
}
private:
- void film_changed (Film::Property);
-
- std::list<std::string> _files;
- std::list<std::string>::iterator _iter;
+ boost::shared_ptr<const ImageMagickContent> _imagemagick_content;
+ ContentVideoFrame _position;
};
diff --git a/src/lib/job.cc b/src/lib/job.cc
index ace02b8b3..ff0332d6d 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -34,8 +34,6 @@ using std::list;
using std::stringstream;
using boost::shared_ptr;
-/** @param s Film that we are operating on.
- */
Job::Job (shared_ptr<Film> f)
: _film (f)
, _thread (0)
diff --git a/src/lib/job.h b/src/lib/job.h
index fd036bce2..f5175c525 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -38,7 +38,7 @@ class Film;
class Job : public boost::enable_shared_from_this<Job>
{
public:
- Job (boost::shared_ptr<Film> s);
+ Job (boost::shared_ptr<Film>);
virtual ~Job() {}
/** @return user-readable name of this job */
@@ -87,7 +87,6 @@ protected:
void set_state (State);
void set_error (std::string s, std::string d);
- /** Film for this job */
boost::shared_ptr<Film> _film;
private:
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
index 48f6ed912..77ed9b651 100644
--- a/src/lib/matcher.cc
+++ b/src/lib/matcher.cc
@@ -37,7 +37,7 @@ Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
}
void
-Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s)
+Matcher::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
{
Video (i, same, s);
_video_frames++;
@@ -47,7 +47,7 @@ Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr
}
void
-Matcher::process_audio (boost::shared_ptr<AudioBuffers> b)
+Matcher::process_audio (shared_ptr<AudioBuffers> b)
{
Audio (b);
_audio_frames += b->frames ();
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644
index 000000000..756c3b854
--- /dev/null
+++ b/src/lib/player.cc
@@ -0,0 +1,338 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "player.h"
+#include "film.h"
+#include "ffmpeg_decoder.h"
+#include "imagemagick_decoder.h"
+#include "sndfile_decoder.h"
+#include "sndfile_content.h"
+#include "playlist.h"
+#include "job.h"
+
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+
+Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
+ : _film (f)
+ , _playlist (p)
+ , _video (true)
+ , _audio (true)
+ , _subtitles (true)
+ , _have_valid_decoders (false)
+ , _ffmpeg_decoder_done (false)
+ , _video_sync (true)
+{
+ _playlist->Changed.connect (bind (&Player::playlist_changed, this));
+ _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
+}
+
+void
+Player::disable_video ()
+{
+ _video = false;
+}
+
+void
+Player::disable_audio ()
+{
+ _audio = false;
+}
+
+void
+Player::disable_subtitles ()
+{
+ _subtitles = false;
+}
+
+bool
+Player::pass ()
+{
+ if (!_have_valid_decoders) {
+ setup_decoders ();
+ _have_valid_decoders = true;
+ }
+
+ bool done = true;
+
+ if (_playlist->video_from() == Playlist::VIDEO_FFMPEG || _playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
+ if (!_ffmpeg_decoder_done) {
+ if (_ffmpeg_decoder->pass ()) {
+ _ffmpeg_decoder_done = true;
+ } else {
+ done = false;
+ }
+ }
+ }
+
+ if (_playlist->video_from() == Playlist::VIDEO_IMAGEMAGICK) {
+ if (_imagemagick_decoder != _imagemagick_decoders.end ()) {
+ if ((*_imagemagick_decoder)->pass ()) {
+ _imagemagick_decoder++;
+ }
+
+ if (_imagemagick_decoder != _imagemagick_decoders.end ()) {
+ done = false;
+ }
+ }
+ }
+
+ if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+ for (list<shared_ptr<SndfileDecoder> >::iterator i = _sndfile_decoders.begin(); i != _sndfile_decoders.end(); ++i) {
+ if (!(*i)->pass ()) {
+ done = false;
+ }
+ }
+
+ Audio (_sndfile_buffers);
+ _sndfile_buffers.reset ();
+ }
+
+ return done;
+}
+
+void
+Player::set_progress (shared_ptr<Job> job)
+{
+ /* Assume progress can be divined from how far through the video we are */
+ switch (_playlist->video_from ()) {
+ case Playlist::VIDEO_NONE:
+ break;
+ case Playlist::VIDEO_FFMPEG:
+ if (_playlist->video_length ()) {
+ job->set_progress (float(_ffmpeg_decoder->video_frame()) / _playlist->video_length ());
+ }
+ break;
+ case Playlist::VIDEO_IMAGEMAGICK:
+ {
+ int n = 0;
+ for (list<shared_ptr<ImageMagickDecoder> >::iterator i = _imagemagick_decoders.begin(); i != _imagemagick_decoders.end(); ++i) {
+ if (_imagemagick_decoder == i) {
+ job->set_progress (float (n) / _imagemagick_decoders.size ());
+ }
+ ++n;
+ }
+ break;
+ }
+ }
+}
+
+void
+Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
+{
+ Video (i, same, s);
+}
+
+void
+Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b)
+{
+ if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+ AudioMapping mapping = _film->audio_mapping ();
+ if (!_sndfile_buffers) {
+ _sndfile_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ()));
+ _sndfile_buffers->make_silent ();
+ }
+
+ for (int i = 0; i < b->channels(); ++i) {
+ list<libdcp::Channel> dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i));
+ for (list<libdcp::Channel>::iterator j = dcp.begin(); j != dcp.end(); ++j) {
+ _sndfile_buffers->accumulate (b, i, static_cast<int> (*j));
+ }
+ }
+
+ } else {
+ Audio (b);
+ }
+}
+
+/** @return true on error */
+bool
+Player::seek (double t)
+{
+ if (!_have_valid_decoders) {
+ setup_decoders ();
+ _have_valid_decoders = true;
+ }
+
+ bool r = false;
+
+ switch (_playlist->video_from()) {
+ case Playlist::VIDEO_NONE:
+ break;
+ case Playlist::VIDEO_FFMPEG:
+ if (!_ffmpeg_decoder || _ffmpeg_decoder->seek (t)) {
+ r = true;
+ }
+ /* We're seeking, so all `all done' bets are off */
+ _ffmpeg_decoder_done = false;
+ break;
+ case Playlist::VIDEO_IMAGEMAGICK:
+ /* Find the decoder that contains this position */
+ _imagemagick_decoder = _imagemagick_decoders.begin ();
+ while (_imagemagick_decoder != _imagemagick_decoders.end ()) {
+ double const this_length = (*_imagemagick_decoder)->video_length() / _film->video_frame_rate ();
+ if (t < this_length) {
+ break;
+ }
+ t -= this_length;
+ ++_imagemagick_decoder;
+ }
+
+ if (_imagemagick_decoder != _imagemagick_decoders.end()) {
+ (*_imagemagick_decoder)->seek (t);
+ } else {
+ r = true;
+ }
+ break;
+ }
+
+ /* XXX: don't seek audio because we don't need to... */
+
+ return r;
+}
+
+bool
+Player::seek_to_last ()
+{
+ if (!_have_valid_decoders) {
+ setup_decoders ();
+ _have_valid_decoders = true;
+ }
+
+ bool r = false;
+
+ switch (_playlist->video_from ()) {
+ case Playlist::VIDEO_NONE:
+ break;
+ case Playlist::VIDEO_FFMPEG:
+ if (!_ffmpeg_decoder || _ffmpeg_decoder->seek_to_last ()) {
+ r = true;
+ }
+
+ /* We're seeking, so all `all done' bets are off */
+ _ffmpeg_decoder_done = false;
+ break;
+ case Playlist::VIDEO_IMAGEMAGICK:
+ if ((*_imagemagick_decoder)->seek_to_last ()) {
+ r = true;
+ }
+ break;
+ }
+
+ /* XXX: don't seek audio because we don't need to... */
+
+ return r;
+}
+
+void
+Player::setup_decoders ()
+{
+ if ((_video && _playlist->video_from() == Playlist::VIDEO_FFMPEG) || (_audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG)) {
+ _ffmpeg_decoder.reset (
+ new FFmpegDecoder (
+ _film,
+ _playlist->ffmpeg(),
+ _video && _playlist->video_from() == Playlist::VIDEO_FFMPEG,
+ _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
+ _subtitles && _film->with_subtitles(),
+ _video_sync
+ )
+ );
+ }
+
+ if (_video && _playlist->video_from() == Playlist::VIDEO_FFMPEG) {
+ _ffmpeg_decoder->connect_video (shared_from_this ());
+ }
+
+ if (_audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
+ _ffmpeg_decoder->Audio.connect (bind (&Player::process_audio, this, _playlist->ffmpeg (), _1));
+ }
+
+ if (_video && _playlist->video_from() == Playlist::VIDEO_IMAGEMAGICK) {
+ list<shared_ptr<const ImageMagickContent> > ic = _playlist->imagemagick ();
+ for (list<shared_ptr<const ImageMagickContent> >::iterator i = ic.begin(); i != ic.end(); ++i) {
+ shared_ptr<ImageMagickDecoder> d (new ImageMagickDecoder (_film, *i));
+ _imagemagick_decoders.push_back (d);
+ d->connect_video (shared_from_this ());
+ }
+
+ _imagemagick_decoder = _imagemagick_decoders.begin ();
+ }
+
+ if (_audio && _playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+ list<shared_ptr<const SndfileContent> > sc = _playlist->sndfile ();
+ for (list<shared_ptr<const SndfileContent> >::iterator i = sc.begin(); i != sc.end(); ++i) {
+ shared_ptr<SndfileDecoder> d (new SndfileDecoder (_film, *i));
+ _sndfile_decoders.push_back (d);
+ d->Audio.connect (bind (&Player::process_audio, this, *i, _1));
+ }
+ }
+}
+
+void
+Player::disable_video_sync ()
+{
+ _video_sync = false;
+}
+
+double
+Player::last_video_time () const
+{
+ switch (_playlist->video_from ()) {
+ case Playlist::VIDEO_NONE:
+ return 0;
+ case Playlist::VIDEO_FFMPEG:
+ return _ffmpeg_decoder->last_source_time ();
+ case Playlist::VIDEO_IMAGEMAGICK:
+ {
+ double t = 0;
+ for (list<shared_ptr<ImageMagickDecoder> >::const_iterator i = _imagemagick_decoders.begin(); i != _imagemagick_decoder; ++i) {
+ t += (*i)->video_length() / (*i)->video_frame_rate ();
+ }
+
+ return t + (*_imagemagick_decoder)->last_source_time ();
+ }
+ }
+
+ return 0;
+}
+
+void
+Player::content_changed (weak_ptr<Content> w, int p)
+{
+ shared_ptr<Content> c = w.lock ();
+ if (!c) {
+ return;
+ }
+
+ if (p == VideoContentProperty::VIDEO_LENGTH) {
+ if (dynamic_pointer_cast<FFmpegContent> (c)) {
+ _have_valid_decoders = false;
+ }
+ }
+}
+
+void
+Player::playlist_changed ()
+{
+ _have_valid_decoders = false;
+}
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644
index 000000000..afc856316
--- /dev/null
+++ b/src/lib/player.h
@@ -0,0 +1,82 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_PLAYER_H
+#define DVDOMATIC_PLAYER_H
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "video_source.h"
+#include "audio_source.h"
+#include "video_sink.h"
+#include "audio_sink.h"
+
+class FFmpegDecoder;
+class ImageMagickDecoder;
+class SndfileDecoder;
+class Job;
+class Film;
+class Playlist;
+class AudioContent;
+
+class Player : public VideoSource, public AudioSource, public VideoSink, public boost::enable_shared_from_this<Player>
+{
+public:
+ Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
+
+ void disable_video ();
+ void disable_audio ();
+ void disable_subtitles ();
+ void disable_video_sync ();
+
+ bool pass ();
+ void set_progress (boost::shared_ptr<Job>);
+ bool seek (double);
+ bool seek_to_last ();
+
+ double last_video_time () const;
+
+private:
+ void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s);
+ void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<AudioBuffers>);
+ void setup_decoders ();
+ void playlist_changed ();
+ void content_changed (boost::weak_ptr<Content>, int);
+
+ boost::shared_ptr<const Film> _film;
+ boost::shared_ptr<const Playlist> _playlist;
+
+ bool _video;
+ bool _audio;
+ bool _subtitles;
+
+ bool _have_valid_decoders;
+ boost::shared_ptr<FFmpegDecoder> _ffmpeg_decoder;
+ bool _ffmpeg_decoder_done;
+ std::list<boost::shared_ptr<ImageMagickDecoder> > _imagemagick_decoders;
+ std::list<boost::shared_ptr<ImageMagickDecoder> >::iterator _imagemagick_decoder;
+ std::list<boost::shared_ptr<SndfileDecoder> > _sndfile_decoders;
+
+ boost::shared_ptr<AudioBuffers> _sndfile_buffers;
+
+ bool _video_sync;
+};
+
+#endif
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
new file mode 100644
index 000000000..3f7905fa9
--- /dev/null
+++ b/src/lib/playlist.cc
@@ -0,0 +1,271 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include "playlist.h"
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "ffmpeg_content.h"
+#include "ffmpeg_decoder.h"
+#include "imagemagick_content.h"
+#include "imagemagick_decoder.h"
+#include "job.h"
+
+using std::list;
+using std::cout;
+using std::vector;
+using std::min;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+
+Playlist::Playlist ()
+ : _video_from (VIDEO_NONE)
+ , _audio_from (AUDIO_NONE)
+{
+
+}
+
+void
+Playlist::setup (ContentList content)
+{
+ _video_from = VIDEO_NONE;
+ _audio_from = AUDIO_NONE;
+
+ _ffmpeg.reset ();
+ _imagemagick.clear ();
+ _sndfile.clear ();
+
+ for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
+ i->disconnect ();
+ }
+
+ _content_connections.clear ();
+
+ for (ContentList::const_iterator i = content.begin(); i != content.end(); ++i) {
+ shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
+ if (fc) {
+ assert (!_ffmpeg);
+ _ffmpeg = fc;
+ _video_from = VIDEO_FFMPEG;
+ if (_audio_from == AUDIO_NONE) {
+ _audio_from = AUDIO_FFMPEG;
+ }
+ }
+
+ shared_ptr<ImageMagickContent> ic = dynamic_pointer_cast<ImageMagickContent> (*i);
+ if (ic) {
+ _imagemagick.push_back (ic);
+ if (_video_from == VIDEO_NONE) {
+ _video_from = VIDEO_IMAGEMAGICK;
+ }
+ }
+
+ shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (*i);
+ if (sc) {
+ _sndfile.push_back (sc);
+ _audio_from = AUDIO_SNDFILE;
+ }
+
+ _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2)));
+ }
+
+ Changed ();
+}
+
+ContentAudioFrame
+Playlist::audio_length () const
+{
+ switch (_audio_from) {
+ case AUDIO_NONE:
+ return 0;
+ case AUDIO_FFMPEG:
+ return _ffmpeg->audio_length ();
+ case AUDIO_SNDFILE:
+ {
+ ContentAudioFrame l = 0;
+ for (list<shared_ptr<const SndfileContent> >::const_iterator i = _sndfile.begin(); i != _sndfile.end(); ++i) {
+ l += (*i)->audio_length ();
+ }
+ return l;
+ }
+ }
+
+ return 0;
+}
+
+int
+Playlist::audio_channels () const
+{
+ switch (_audio_from) {
+ case AUDIO_NONE:
+ return 0;
+ case AUDIO_FFMPEG:
+ return _ffmpeg->audio_channels ();
+ case AUDIO_SNDFILE:
+ {
+ int c = 0;
+ for (list<shared_ptr<const SndfileContent> >::const_iterator i = _sndfile.begin(); i != _sndfile.end(); ++i) {
+ c += (*i)->audio_channels ();
+ }
+ return c;
+ }
+ }
+
+ return 0;
+}
+
+int
+Playlist::audio_frame_rate () const
+{
+ switch (_audio_from) {
+ case AUDIO_NONE:
+ return 0;
+ case AUDIO_FFMPEG:
+ return _ffmpeg->audio_frame_rate ();
+ case AUDIO_SNDFILE:
+ return _sndfile.front()->audio_frame_rate ();
+ }
+
+ return 0;
+}
+
+int64_t
+Playlist::audio_channel_layout () const
+{
+ switch (_audio_from) {
+ case AUDIO_NONE:
+ return 0;
+ case AUDIO_FFMPEG:
+ return _ffmpeg->audio_channel_layout ();
+ case AUDIO_SNDFILE:
+ /* XXX */
+ return 0;
+ }
+
+ return 0;
+}
+
+float
+Playlist::video_frame_rate () const
+{
+ switch (_video_from) {
+ case VIDEO_NONE:
+ return 0;
+ case VIDEO_FFMPEG:
+ return _ffmpeg->video_frame_rate ();
+ case VIDEO_IMAGEMAGICK:
+ return 24;
+ }
+
+ return 0;
+}
+
+libdcp::Size
+Playlist::video_size () const
+{
+ switch (_video_from) {
+ case VIDEO_NONE:
+ return libdcp::Size ();
+ case VIDEO_FFMPEG:
+ return _ffmpeg->video_size ();
+ case VIDEO_IMAGEMAGICK:
+ /* XXX */
+ return _imagemagick.front()->video_size ();
+ }
+
+ return libdcp::Size ();
+}
+
+ContentVideoFrame
+Playlist::video_length () const
+{
+ switch (_video_from) {
+ case VIDEO_NONE:
+ return 0;
+ case VIDEO_FFMPEG:
+ return _ffmpeg->video_length ();
+ case VIDEO_IMAGEMAGICK:
+ {
+ ContentVideoFrame l = 0;
+ for (list<shared_ptr<const ImageMagickContent> >::const_iterator i = _imagemagick.begin(); i != _imagemagick.end(); ++i) {
+ l += (*i)->video_length ();
+ }
+ return l;
+ }
+ }
+
+ return 0;
+}
+
+bool
+Playlist::has_audio () const
+{
+ return _audio_from != AUDIO_NONE;
+}
+
+void
+Playlist::content_changed (weak_ptr<Content> c, int p)
+{
+ ContentChanged (c, p);
+}
+
+AudioMapping
+Playlist::default_audio_mapping () const
+{
+ AudioMapping m;
+
+ switch (_audio_from) {
+ case AUDIO_NONE:
+ break;
+ case AUDIO_FFMPEG:
+ if (_ffmpeg->audio_channels() == 1) {
+ /* Map mono sources to centre */
+ m.add (AudioMapping::Channel (_ffmpeg, 0), libdcp::CENTRE);
+ } else {
+ int const N = min (_ffmpeg->audio_channels (), MAX_AUDIO_CHANNELS);
+ /* Otherwise just start with a 1:1 mapping */
+ for (int i = 0; i < N; ++i) {
+ m.add (AudioMapping::Channel (_ffmpeg, i), (libdcp::Channel) i);
+ }
+ }
+ break;
+
+ case AUDIO_SNDFILE:
+ {
+ int n = 0;
+ for (list<shared_ptr<const SndfileContent> >::const_iterator i = _sndfile.begin(); i != _sndfile.end(); ++i) {
+ cout << "sndfile " << (*i)->audio_channels() << "\n";
+ for (int j = 0; j < (*i)->audio_channels(); ++j) {
+ m.add (AudioMapping::Channel (*i, j), (libdcp::Channel) n);
+ ++n;
+ if (n >= MAX_AUDIO_CHANNELS) {
+ break;
+ }
+ }
+ if (n >= MAX_AUDIO_CHANNELS) {
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return m;
+}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
new file mode 100644
index 000000000..1d189cb07
--- /dev/null
+++ b/src/lib/playlist.h
@@ -0,0 +1,105 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "video_source.h"
+#include "audio_source.h"
+#include "video_sink.h"
+#include "audio_sink.h"
+#include "ffmpeg_content.h"
+#include "audio_mapping.h"
+
+class Content;
+class FFmpegContent;
+class FFmpegDecoder;
+class ImageMagickContent;
+class ImageMagickDecoder;
+class SndfileContent;
+class SndfileDecoder;
+class Job;
+class Film;
+
+class Playlist
+{
+public:
+ Playlist ();
+
+ void setup (ContentList);
+
+ ContentAudioFrame audio_length () const;
+ int audio_channels () const;
+ int audio_frame_rate () const;
+ int64_t audio_channel_layout () const;
+ bool has_audio () const;
+
+ float video_frame_rate () const;
+ libdcp::Size video_size () const;
+ ContentVideoFrame video_length () const;
+
+ AudioMapping default_audio_mapping () const;
+
+ enum VideoFrom {
+ VIDEO_NONE,
+ VIDEO_FFMPEG,
+ VIDEO_IMAGEMAGICK
+ };
+
+ enum AudioFrom {
+ AUDIO_NONE,
+ AUDIO_FFMPEG,
+ AUDIO_SNDFILE
+ };
+
+ VideoFrom video_from () const {
+ return _video_from;
+ }
+
+ AudioFrom audio_from () const {
+ return _audio_from;
+ }
+
+ boost::shared_ptr<const FFmpegContent> ffmpeg () const {
+ return _ffmpeg;
+ }
+
+ std::list<boost::shared_ptr<const ImageMagickContent> > imagemagick () const {
+ return _imagemagick;
+ }
+
+ std::list<boost::shared_ptr<const SndfileContent> > sndfile () const {
+ return _sndfile;
+ }
+
+ mutable boost::signals2::signal<void ()> Changed;
+ mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+
+private:
+ void content_changed (boost::weak_ptr<Content>, int);
+
+ VideoFrom _video_from;
+ AudioFrom _audio_from;
+
+ boost::shared_ptr<const FFmpegContent> _ffmpeg;
+ std::list<boost::shared_ptr<const ImageMagickContent> > _imagemagick;
+ std::list<boost::shared_ptr<const SndfileContent> > _sndfile;
+
+ std::list<boost::signals2::connection> _content_connections;
+};
diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h
index 08d8e2c78..8c16d53fb 100644
--- a/src/lib/scp_dcp_job.h
+++ b/src/lib/scp_dcp_job.h
@@ -34,7 +34,7 @@ public:
private:
void set_status (std::string);
-
+
mutable boost::mutex _status_mutex;
std::string _status;
};
diff --git a/src/lib/server.cc b/src/lib/server.cc
index 9c5a77f68..ca0bec580 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -29,6 +29,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/scoped_array.hpp>
+#include <libcxml/cxml.h>
#include "server.h"
#include "util.h"
#include "scaler.h"
@@ -51,6 +52,19 @@ using boost::bind;
using boost::scoped_array;
using libdcp::Size;
+ServerDescription::ServerDescription (shared_ptr<const cxml::Node> node)
+{
+ _host_name = node->string_child ("HostName");
+ _threads = node->number_child<int> ("Threads");
+}
+
+void
+ServerDescription::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("HostName")->add_child_text (_host_name);
+ root->add_child("Threads")->add_child_text (boost::lexical_cast<string> (_threads));
+}
+
/** Create a server description from a string of metadata returned from as_metadata().
* @param v Metadata.
* @return ServerDescription, or 0.
@@ -68,15 +82,6 @@ ServerDescription::create_from_metadata (string v)
return new ServerDescription (b[0], atoi (b[1].c_str ()));
}
-/** @return Description of this server as text */
-string
-ServerDescription::as_metadata () const
-{
- stringstream s;
- s << _host_name << N_(" ") << _threads;
- return s.str ();
-}
-
Server::Server (shared_ptr<Log> log)
: _log (log)
{
diff --git a/src/lib/server.h b/src/lib/server.h
index 89aeca626..398401a55 100644
--- a/src/lib/server.h
+++ b/src/lib/server.h
@@ -26,10 +26,15 @@
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/thread/condition.hpp>
+#include <libxml++/libxml++.h>
#include "log.h"
class Socket;
+namespace cxml {
+ class Node;
+}
+
/** @class ServerDescription
* @brief Class to describe a server to which we can send encoding work.
*/
@@ -44,6 +49,8 @@ public:
, _threads (t)
{}
+ ServerDescription (boost::shared_ptr<const cxml::Node>);
+
/** @return server's host name or IP address in string form */
std::string host_name () const {
return _host_name;
@@ -62,7 +69,7 @@ public:
_threads = t;
}
- std::string as_metadata () const;
+ void as_xml (xmlpp::Node *) const;
static ServerDescription * create_from_metadata (std::string v);
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
new file mode 100644
index 000000000..539b0dfb5
--- /dev/null
+++ b/src/lib/sndfile_content.cc
@@ -0,0 +1,122 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "compose.hpp"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SndfileContent::SndfileContent (boost::filesystem::path f)
+ : Content (f)
+ , AudioContent (f)
+ , _audio_channels (0)
+ , _audio_length (0)
+ , _audio_frame_rate (0)
+{
+
+}
+
+SndfileContent::SndfileContent (shared_ptr<const cxml::Node> node)
+ : Content (node)
+ , AudioContent (node)
+{
+ _audio_channels = node->number_child<int> ("AudioChannels");
+ _audio_length = node->number_child<ContentAudioFrame> ("AudioLength");
+ _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
+}
+
+string
+SndfileContent::summary () const
+{
+ return String::compose (_("Sound file: %1"), file().filename().string());
+}
+
+string
+SndfileContent::information () const
+{
+ if (_audio_frame_rate == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (
+ _("%1 channels, %2kHz, %3 samples"),
+ audio_channels(),
+ audio_frame_rate() / 1000.0,
+ audio_length()
+ );
+
+ return s.str ();
+}
+
+bool
+SndfileContent::valid_file (boost::filesystem::path f)
+{
+ /* XXX: more extensions */
+ string ext = f.extension().string();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+ return (ext == ".wav" || ext == ".aif" || ext == ".aiff");
+}
+
+shared_ptr<Content>
+SndfileContent::clone () const
+{
+ return shared_ptr<Content> (new SndfileContent (*this));
+}
+
+void
+SndfileContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
+{
+ job->set_progress_unknown ();
+ Content::examine (film, job, quick);
+
+ SndfileDecoder dec (film, shared_from_this());
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_channels = dec.audio_channels ();
+ _audio_length = dec.audio_length ();
+ _audio_frame_rate = dec.audio_frame_rate ();
+ }
+
+ signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+ signal_changed (AudioContentProperty::AUDIO_LENGTH);
+ signal_changed (AudioContentProperty::AUDIO_FRAME_RATE);
+}
+
+void
+SndfileContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("Sndfile");
+ Content::as_xml (node);
+ node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels));
+ node->add_child("AudioLength")->add_child_text (lexical_cast<string> (_audio_length));
+ node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (_audio_frame_rate));
+}
+
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
new file mode 100644
index 000000000..27c5f3615
--- /dev/null
+++ b/src/lib/sndfile_content.h
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern "C" {
+#include <libavutil/audioconvert.h>
+}
+#include "audio_content.h"
+
+namespace cxml {
+ class Node;
+}
+
+class SndfileContent : public AudioContent
+{
+public:
+ SndfileContent (boost::filesystem::path);
+ SndfileContent (boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<SndfileContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+ std::string summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *) const;
+ boost::shared_ptr<Content> clone () const;
+
+ /* AudioContent */
+ int audio_channels () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_channels;
+ }
+
+ ContentAudioFrame audio_length () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_length;
+ }
+
+ int audio_frame_rate () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_frame_rate;
+ }
+
+ int64_t audio_channel_layout () const {
+ return av_get_default_channel_layout (audio_channels ());
+ }
+
+ static bool valid_file (boost::filesystem::path);
+
+private:
+ int _audio_channels;
+ ContentAudioFrame _audio_length;
+ int _audio_frame_rate;
+};
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
index 0e3e5e234..c7311112a 100644
--- a/src/lib/sndfile_decoder.cc
+++ b/src/lib/sndfile_decoder.cc
@@ -19,6 +19,7 @@
#include <iostream>
#include <sndfile.h>
+#include "sndfile_content.h"
#include "sndfile_decoder.h"
#include "film.h"
#include "exceptions.h"
@@ -27,162 +28,62 @@
using std::vector;
using std::string;
-using std::stringstream;
using std::min;
using std::cout;
using boost::shared_ptr;
-using boost::optional;
-SndfileDecoder::SndfileDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , AudioDecoder (f, o)
+SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
+ : Decoder (f)
+ , AudioDecoder (f)
+ , _sndfile_content (c)
{
- sf_count_t frames;
- vector<SNDFILE*> sf = open_files (frames);
- close_files (sf);
-}
-
-vector<SNDFILE*>
-SndfileDecoder::open_files (sf_count_t & frames)
-{
- vector<string> const files = _film->external_audio ();
-
- int N = 0;
- for (size_t i = 0; i < files.size(); ++i) {
- if (!files[i].empty()) {
- N = i + 1;
- }
+ _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info);
+ if (!_sndfile) {
+ throw DecodeError (_("could not open audio file for reading"));
}
- if (N == 0) {
- return vector<SNDFILE*> ();
- }
-
- bool first = true;
- frames = 0;
-
- vector<SNDFILE*> sndfiles;
- for (size_t i = 0; i < (size_t) N; ++i) {
- if (files[i].empty ()) {
- sndfiles.push_back (0);
- } else {
- SF_INFO info;
- SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info);
- if (!s) {
- throw DecodeError (_("could not open external audio file for reading"));
- }
-
- if (info.channels != 1) {
- throw DecodeError (_("external audio files must be mono"));
- }
-
- sndfiles.push_back (s);
+ _remaining = _info.frames;
+}
- if (first) {
- shared_ptr<SndfileStream> st (
- new SndfileStream (
- info.samplerate, av_get_default_channel_layout (N)
- )
- );
-
- _audio_streams.push_back (st);
- _audio_stream = st;
- frames = info.frames;
- first = false;
- } else {
- if (info.frames != frames) {
- throw DecodeError (_("external audio files have differing lengths"));
- }
- }
- }
+SndfileDecoder::~SndfileDecoder ()
+{
+ if (_sndfile) {
+ sf_close (_sndfile);
}
-
- return sndfiles;
}
bool
SndfileDecoder::pass ()
{
- sf_count_t frames;
- vector<SNDFILE*> sndfiles = open_files (frames);
- if (sndfiles.empty()) {
- return true;
- }
-
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
*/
- sf_count_t const block = _audio_stream->sample_rate() / 2;
-
- shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block));
- while (frames > 0) {
- sf_count_t const this_time = min (block, frames);
- for (size_t i = 0; i < sndfiles.size(); ++i) {
- if (!sndfiles[i]) {
- audio->make_silent (i);
- } else {
- sf_read_float (sndfiles[i], audio->data(i), block);
- }
- }
-
- audio->set_frames (this_time);
- Audio (audio);
- frames -= this_time;
- }
-
- close_files (sndfiles);
-
- return true;
-}
-
-void
-SndfileDecoder::close_files (vector<SNDFILE*> const & sndfiles)
-{
- for (size_t i = 0; i < sndfiles.size(); ++i) {
- sf_close (sndfiles[i]);
- }
-}
-
-shared_ptr<SndfileStream>
-SndfileStream::create ()
-{
- return shared_ptr<SndfileStream> (new SndfileStream);
-}
-
-shared_ptr<SndfileStream>
-SndfileStream::create (string t, optional<int> v)
-{
- if (!v) {
- /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
- return shared_ptr<SndfileStream> ();
- }
-
- stringstream s (t);
- string type;
- s >> type;
- if (type != N_("external")) {
- return shared_ptr<SndfileStream> ();
- }
+ sf_count_t const block = _sndfile_content->audio_frame_rate() / 2;
+ sf_count_t const this_time = min (block, _remaining);
+
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_sndfile_content->audio_channels(), this_time));
+ sf_read_float (_sndfile, audio->data(0), this_time);
+ audio->set_frames (this_time);
+ Audio (audio);
+ _remaining -= this_time;
- return shared_ptr<SndfileStream> (new SndfileStream (t, v));
+ return (_remaining == 0);
}
-SndfileStream::SndfileStream (string t, optional<int> v)
+int
+SndfileDecoder::audio_channels () const
{
- assert (v);
-
- stringstream s (t);
- string type;
- s >> type >> _sample_rate >> _channel_layout;
+ return _info.channels;
}
-SndfileStream::SndfileStream ()
+ContentAudioFrame
+SndfileDecoder::audio_length () const
{
-
+ return _info.frames;
}
-string
-SndfileStream::to_string () const
+int
+SndfileDecoder::audio_frame_rate () const
{
- return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout);
+ return _info.samplerate;
}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
index e16eab673..2900afea0 100644
--- a/src/lib/sndfile_decoder.h
+++ b/src/lib/sndfile_decoder.h
@@ -20,35 +20,27 @@
#include <sndfile.h>
#include "decoder.h"
#include "audio_decoder.h"
-#include "stream.h"
-class SndfileStream : public AudioStream
-{
-public:
- SndfileStream (int sample_rate, int64_t layout)
- : AudioStream (sample_rate, layout)
- {}
-
- std::string to_string () const;
-
- static boost::shared_ptr<SndfileStream> create ();
- static boost::shared_ptr<SndfileStream> create (std::string t, boost::optional<int> v);
-
-private:
- friend class stream_test;
-
- SndfileStream ();
- SndfileStream (std::string t, boost::optional<int> v);
-};
+class SndfileContent;
class SndfileDecoder : public AudioDecoder
{
public:
- SndfileDecoder (boost::shared_ptr<Film>, DecodeOptions);
+ SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+ ~SndfileDecoder ();
bool pass ();
+ int audio_channels () const;
+ ContentAudioFrame audio_length () const;
+ int audio_frame_rate () const;
+
private:
- std::vector<SNDFILE*> open_files (sf_count_t &);
- void close_files (std::vector<SNDFILE*> const &);
+ SNDFILE* open_file (sf_count_t &);
+ void close_file (SNDFILE*);
+
+ boost::shared_ptr<const SndfileContent> _sndfile_content;
+ SNDFILE* _sndfile;
+ SF_INFO _info;
+ ContentAudioFrame _remaining;
};
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
deleted file mode 100644
index bfe7b5eb4..000000000
--- a/src/lib/stream.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <sstream>
-#include "compose.hpp"
-#include "stream.h"
-#include "ffmpeg_decoder.h"
-#include "sndfile_decoder.h"
-
-#include "i18n.h"
-
-using std::string;
-using std::stringstream;
-using boost::shared_ptr;
-using boost::optional;
-
-/** Construct a SubtitleStream from a value returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- */
-SubtitleStream::SubtitleStream (string t, boost::optional<int>)
-{
- stringstream n (t);
- n >> _id;
-
- size_t const s = t.find (' ');
- if (s != string::npos) {
- _name = t.substr (s + 1);
- }
-}
-
-/** @return A canonical string representation of this stream */
-string
-SubtitleStream::to_string () const
-{
- return String::compose (N_("%1 %2"), _id, _name);
-}
-
-/** Create a SubtitleStream from a value returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- */
-shared_ptr<SubtitleStream>
-SubtitleStream::create (string t, optional<int> v)
-{
- return shared_ptr<SubtitleStream> (new SubtitleStream (t, v));
-}
-
-/** Create an AudioStream from a string returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- * @return AudioStream, or 0.
- */
-shared_ptr<AudioStream>
-audio_stream_factory (string t, optional<int> v)
-{
- shared_ptr<AudioStream> s;
-
- s = FFmpegAudioStream::create (t, v);
- if (!s) {
- s = SndfileStream::create (t, v);
- }
-
- return s;
-}
-
-/** Create a SubtitleStream from a string returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- * @return SubtitleStream, or 0.
- */
-shared_ptr<SubtitleStream>
-subtitle_stream_factory (string t, optional<int> v)
-{
- return SubtitleStream::create (t, v);
-}
diff --git a/src/lib/stream.h b/src/lib/stream.h
deleted file mode 100644
index 16b06e4bc..000000000
--- a/src/lib/stream.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/lib/stream.h
- * @brief Representations of audio and subtitle streams.
- *
- * Some content may have multiple `streams' of audio and/or subtitles; perhaps
- * for multiple languages, or for stereo / surround mixes. These classes represent
- * those streams, and know about their details.
- */
-
-#ifndef DVDOMATIC_STREAM_H
-#define DVDOMATIC_STREAM_H
-
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/optional.hpp>
-extern "C" {
-#include <libavutil/audioconvert.h>
-}
-
-/** @class Stream
- * @brief Parent class for streams.
- */
-class Stream
-{
-public:
- virtual ~Stream () {}
- virtual std::string to_string () const = 0;
-};
-
-/** @class AudioStream
- * @brief A stream of audio data.
- */
-struct AudioStream : public Stream
-{
-public:
- AudioStream (int r, int64_t l)
- : _sample_rate (r)
- , _channel_layout (l)
- {}
-
- /* Only used for backwards compatibility for state file version < 1 */
- void set_sample_rate (int s) {
- _sample_rate = s;
- }
-
- int channels () const {
- return av_get_channel_layout_nb_channels (_channel_layout);
- }
-
- int sample_rate () const {
- return _sample_rate;
- }
-
- int64_t channel_layout () const {
- return _channel_layout;
- }
-
-protected:
- AudioStream ()
- : _sample_rate (0)
- , _channel_layout (0)
- {}
-
- int _sample_rate;
- int64_t _channel_layout;
-};
-
-/** @class SubtitleStream
- * @brief A stream of subtitle data.
- */
-class SubtitleStream : public Stream
-{
-public:
- SubtitleStream (std::string n, int i)
- : _name (n)
- , _id (i)
- {}
-
- std::string to_string () const;
-
- std::string name () const {
- return _name;
- }
-
- int id () const {
- return _id;
- }
-
- static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v);
-
-private:
- friend class stream_test;
-
- SubtitleStream (std::string t, boost::optional<int> v);
-
- std::string _name;
- int _id;
-};
-
-boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version);
-boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version);
-
-#endif
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 234ebe051..0c3b8c37b 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -39,11 +39,9 @@ using std::setprecision;
using boost::shared_ptr;
/** @param s Film to use.
- * @param o Decode options.
*/
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o)
+TranscodeJob::TranscodeJob (shared_ptr<Film> f)
: Job (f)
- , _decode_opt (o)
{
}
@@ -62,9 +60,8 @@ TranscodeJob::run ()
_film->log()->log (N_("Transcode job starting"));
_film->log()->log (String::compose (N_("Audio delay is %1ms"), _film->audio_delay()));
- _encoder.reset (new Encoder (_film));
- Transcoder w (_film, _decode_opt, this, _encoder);
- w.go ();
+ _transcoder.reset (new Transcoder (_film, shared_from_this ()));
+ _transcoder->go ();
set_progress (1);
set_state (FINISHED_OK);
@@ -83,11 +80,11 @@ TranscodeJob::run ()
string
TranscodeJob::status () const
{
- if (!_encoder) {
+ if (!_transcoder) {
return _("0%");
}
- float const fps = _encoder->current_frames_per_second ();
+ float const fps = _transcoder->current_encoding_rate ();
if (fps == 0) {
return Job::status ();
}
@@ -106,24 +103,28 @@ TranscodeJob::status () const
int
TranscodeJob::remaining_time () const
{
- float fps = _encoder->current_frames_per_second ();
+ if (!_transcoder) {
+ return 0;
+ }
+
+ float fps = _transcoder->current_encoding_rate ();
+
if (fps == 0) {
return 0;
}
- if (!_film->length()) {
+ if (!_film->video_length()) {
return 0;
}
/* Compute approximate proposed length here, as it's only here that we need it */
- int length = _film->length().get();
- FrameRateConversion const frc (_film->source_frame_rate(), _film->dcp_frame_rate());
+ int length = _film->video_length();
+ FrameRateConversion const frc (_film->video_frame_rate(), _film->dcp_frame_rate());
if (frc.skip) {
length /= 2;
}
/* If we are repeating it shouldn't affect transcode time, so don't take it into account */
- /* We assume that dcp_length() is valid, if it is set */
- int const left = length - _encoder->video_frames_out();
+ int const left = length - _transcoder->video_frames_out();
return left / fps;
}
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
index 9b69e4e65..7880a925e 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -23,9 +23,8 @@
#include <boost/shared_ptr.hpp>
#include "job.h"
-#include "options.h"
-class Encoder;
+class Transcoder;
/** @class TranscodeJob
* @brief A job which transcodes from one format to another.
@@ -33,16 +32,14 @@ class Encoder;
class TranscodeJob : public Job
{
public:
- TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od);
+ TranscodeJob (boost::shared_ptr<Film> f);
std::string name () const;
void run ();
std::string status () const;
-protected:
+private:
int remaining_time () const;
-private:
- DecodeOptions _decode_opt;
- boost::shared_ptr<Encoder> _encoder;
+ boost::shared_ptr<Transcoder> _transcoder;
};
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index e0f3a03a2..6744e9193 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -28,14 +28,13 @@
#include <boost/signals2.hpp>
#include "transcoder.h"
#include "encoder.h"
-#include "decoder_factory.h"
#include "film.h"
#include "matcher.h"
#include "delay_line.h"
-#include "options.h"
#include "gain.h"
#include "video_decoder.h"
#include "audio_decoder.h"
+#include "player.h"
using std::string;
using boost::shared_ptr;
@@ -43,72 +42,45 @@ using boost::dynamic_pointer_cast;
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
- * @param o Decode options.
* @param j Job that we are running under, or 0.
* @param e Encoder to use.
*/
-Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<Job> j)
: _job (j)
- , _encoder (e)
- , _decoders (decoder_factory (f, o))
+ , _player (f->player ())
+ , _encoder (new Encoder (f))
{
- assert (_encoder);
-
- if (f->audio_stream()) {
- shared_ptr<AudioStream> st = f->audio_stream();
- _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate()));
- _delay_line.reset (new DelayLine (f->log(), st->channels(), f->audio_delay() * st->sample_rate() / 1000));
+ if (f->has_audio ()) {
+ _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate()));
+ _delay_line.reset (new DelayLine (f->log(), f->audio_channels(), f->audio_delay() * f->audio_frame_rate() / 1000));
_gain.reset (new Gain (f->log(), f->audio_gain()));
}
- /* Set up the decoder to use the film's set streams */
- _decoders.video->set_subtitle_stream (f->subtitle_stream ());
- if (_decoders.audio) {
- _decoders.audio->set_audio_stream (f->audio_stream ());
- }
-
if (_matcher) {
- _decoders.video->connect_video (_matcher);
+ _player->connect_video (_matcher);
_matcher->connect_video (_encoder);
} else {
- _decoders.video->connect_video (_encoder);
+ _player->connect_video (_encoder);
}
- if (_matcher && _delay_line && _decoders.audio) {
- _decoders.audio->connect_audio (_delay_line);
+ if (_matcher && _delay_line && f->has_audio ()) {
+ _player->connect_audio (_delay_line);
_delay_line->connect_audio (_matcher);
_matcher->connect_audio (_gain);
_gain->connect_audio (_encoder);
}
}
-/** Run the decoder, passing its output to the encoder, until the decoder
- * has no more data to present.
- */
void
Transcoder::go ()
{
_encoder->process_begin ();
try {
- bool done[2] = { false, false };
-
while (1) {
- if (!done[0]) {
- done[0] = _decoders.video->pass ();
- if (_job) {
- _decoders.video->set_progress (_job);
- }
- }
-
- if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
- done[1] = _decoders.audio->pass ();
- } else {
- done[1] = true;
- }
-
- if (done[0] && done[1]) {
+ if (_player->pass ()) {
break;
}
+ _player->set_progress (_job);
}
} catch (...) {
@@ -127,3 +99,15 @@ Transcoder::go ()
}
_encoder->process_end ();
}
+
+float
+Transcoder::current_encoding_rate () const
+{
+ return _encoder->current_encoding_rate ();
+}
+
+int
+Transcoder::video_frames_out () const
+{
+ return _encoder->video_frames_out ();
+}
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index b0c263d07..ecc8ebf62 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -18,26 +18,21 @@
*/
/** @file src/transcoder.h
- * @brief A class which takes a Film and some Options, then uses those to transcode the film.
*
* A decoder is selected according to the content type, and the encoder can be specified
* as a parameter to the constructor.
*/
-#include "decoder_factory.h"
-
class Film;
class Job;
class Encoder;
class Matcher;
class VideoFilter;
class Gain;
-class VideoDecoder;
-class AudioDecoder;
class DelayLine;
+class Player;
/** @class Transcoder
- * @brief A class which takes a Film and some Options, then uses those to transcode the film.
*
* A decoder is selected according to the content type, and the encoder can be specified
* as a parameter to the constructor.
@@ -47,24 +42,19 @@ class Transcoder
public:
Transcoder (
boost::shared_ptr<Film> f,
- DecodeOptions o,
- Job* j,
- boost::shared_ptr<Encoder> e
+ boost::shared_ptr<Job> j
);
void go ();
- boost::shared_ptr<VideoDecoder> video_decoder () const {
- return _decoders.video;
- }
+ float current_encoding_rate () const;
+ int video_frames_out () const;
protected:
/** A Job that is running this Transcoder, or 0 */
- Job* _job;
- /** The encoder that we will use */
+ boost::shared_ptr<Job> _job;
+ boost::shared_ptr<Player> _player;
boost::shared_ptr<Encoder> _encoder;
- /** The decoders that we will use */
- Decoders _decoders;
boost::shared_ptr<Matcher> _matcher;
boost::shared_ptr<DelayLine> _delay_line;
boost::shared_ptr<Gain> _gain;
diff --git a/src/lib/options.h b/src/lib/types.cc
index 0d2c07fd5..1e0f48327 100644
--- a/src/lib/options.h
+++ b/src/lib/types.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,27 +17,15 @@
*/
-#ifndef DVDOMATIC_OPTIONS_H
-#define DVDOMATIC_OPTIONS_H
+#include "types.h"
-/** @file src/options.h
- * @brief Options for a decoding operation.
- */
+bool operator== (Crop const & a, Crop const & b)
+{
+ return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
+}
-class DecodeOptions
+bool operator!= (Crop const & a, Crop const & b)
{
-public:
- DecodeOptions ()
- : decode_video (true)
- , decode_audio (true)
- , decode_subtitles (false)
- , video_sync (true)
- {}
-
- bool decode_video;
- bool decode_audio;
- bool decode_subtitles;
- bool video_sync;
-};
-
-#endif
+ return !(a == b);
+}
+
diff --git a/src/lib/types.h b/src/lib/types.h
new file mode 100644
index 000000000..f821a74ac
--- /dev/null
+++ b/src/lib/types.h
@@ -0,0 +1,109 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_TYPES_H
+#define DVDOMATIC_TYPES_H
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <libdcp/util.h>
+
+class Content;
+
+typedef std::vector<boost::shared_ptr<Content> > ContentList;
+typedef int64_t ContentAudioFrame;
+typedef int ContentVideoFrame;
+
+/** @struct Crop
+ * @brief A description of the crop of an image or video.
+ */
+struct Crop
+{
+ Crop () : left (0), right (0), top (0), bottom (0) {}
+
+ /** Number of pixels to remove from the left-hand side */
+ int left;
+ /** Number of pixels to remove from the right-hand side */
+ int right;
+ /** Number of pixels to remove from the top */
+ int top;
+ /** Number of pixels to remove from the bottom */
+ int bottom;
+};
+
+extern bool operator== (Crop const & a, Crop const & b);
+extern bool operator!= (Crop const & a, Crop const & b);
+
+/** @struct Position
+ * @brief A position.
+ */
+struct Position
+{
+ Position ()
+ : x (0)
+ , y (0)
+ {}
+
+ Position (int x_, int y_)
+ : x (x_)
+ , y (y_)
+ {}
+
+ /** x coordinate */
+ int x;
+ /** y coordinate */
+ int y;
+};
+
+/** @struct Rect
+ * @brief A rectangle.
+ */
+struct Rect
+{
+ Rect ()
+ : x (0)
+ , y (0)
+ , width (0)
+ , height (0)
+ {}
+
+ Rect (int x_, int y_, int w_, int h_)
+ : x (x_)
+ , y (y_)
+ , width (w_)
+ , height (h_)
+ {}
+
+ int x;
+ int y;
+ int width;
+ int height;
+
+ Position position () const {
+ return Position (x, y);
+ }
+
+ libdcp::Size size () const {
+ return libdcp::Size (width, height);
+ }
+
+ Rect intersection (Rect const & other) const;
+};
+
+#endif
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 557e9a34b..06da94294 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -63,8 +63,26 @@ extern "C" {
#include "i18n.h"
-using namespace std;
-using namespace boost;
+using std::string;
+using std::stringstream;
+using std::setfill;
+using std::ostream;
+using std::endl;
+using std::vector;
+using std::hex;
+using std::setw;
+using std::ifstream;
+using std::ios;
+using std::min;
+using std::max;
+using std::list;
+using std::multimap;
+using std::istream;
+using std::numeric_limits;
+using std::pair;
+using boost::shared_ptr;
+using boost::thread;
+using boost::lexical_cast;
using libdcp::Size;
thread::id ui_thread;
@@ -243,7 +261,7 @@ dvdomatic_setup ()
Filter::setup_filters ();
SoundProcessor::setup_sound_processors ();
- ui_thread = this_thread::get_id ();
+ ui_thread = boost::this_thread::get_id ();
}
#ifdef DVDOMATIC_WINDOWS
@@ -348,11 +366,11 @@ md5_digest (void const * data, int size)
* @return MD5 digest of file's contents.
*/
string
-md5_digest (string file)
+md5_digest (boost::filesystem::path file)
{
- ifstream f (file.c_str(), ios::binary);
+ ifstream f (file.string().c_str(), ios::binary);
if (!f.good ()) {
- throw OpenFileError (file);
+ throw OpenFileError (file.string());
}
f.seekg (0, ios::end);
@@ -477,16 +495,6 @@ dcp_audio_sample_rate (int fs)
return 96000;
}
-bool operator== (Crop const & a, Crop const & b)
-{
- return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
-}
-
-bool operator!= (Crop const & a, Crop const & b)
-{
- return !(a == b);
-}
-
/** @param index Colour LUT index.
* @return Human-readable name.
*/
@@ -509,16 +517,16 @@ Socket::Socket (int timeout)
, _socket (_io_service)
, _timeout (timeout)
{
- _deadline.expires_at (posix_time::pos_infin);
+ _deadline.expires_at (boost::posix_time::pos_infin);
check ();
}
void
Socket::check ()
{
- if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) {
+ if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
_socket.close ();
- _deadline.expires_at (posix_time::pos_infin);
+ _deadline.expires_at (boost::posix_time::pos_infin);
}
_deadline.async_wait (boost::bind (&Socket::check, this));
@@ -528,14 +536,14 @@ Socket::check ()
* @param endpoint End-point to connect to.
*/
void
-Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint)
+Socket::connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint)
{
- _deadline.expires_from_now (posix_time::seconds (_timeout));
- system::error_code ec = asio::error::would_block;
- _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1);
+ _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ boost::system::error_code ec = boost::asio::error::would_block;
+ _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
do {
_io_service.run_one();
- } while (ec == asio::error::would_block);
+ } while (ec == boost::asio::error::would_block);
if (ec || !_socket.is_open ()) {
throw NetworkError (_("connect timed out"));
@@ -549,14 +557,14 @@ Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint)
void
Socket::write (uint8_t const * data, int size)
{
- _deadline.expires_from_now (posix_time::seconds (_timeout));
- system::error_code ec = asio::error::would_block;
+ _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ boost::system::error_code ec = boost::asio::error::would_block;
- asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+ boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
do {
_io_service.run_one ();
- } while (ec == asio::error::would_block);
+ } while (ec == boost::asio::error::would_block);
if (ec) {
throw NetworkError (ec.message ());
@@ -577,14 +585,14 @@ Socket::write (uint32_t v)
void
Socket::read (uint8_t* data, int size)
{
- _deadline.expires_from_now (posix_time::seconds (_timeout));
- system::error_code ec = asio::error::would_block;
+ _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ boost::system::error_code ec = boost::asio::error::would_block;
- asio::async_read (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+ boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
do {
_io_service.run_one ();
- } while (ec == asio::error::would_block);
+ } while (ec == boost::asio::error::would_block);
if (ec) {
throw NetworkError (ec.message ());
@@ -861,37 +869,39 @@ AudioBuffers::move (int from, int to, int frames)
}
}
+/** Add data from from `from', `from_channel' to our channel `to_channel' */
+void
+AudioBuffers::accumulate (shared_ptr<AudioBuffers> from, int from_channel, int to_channel)
+{
+ int const N = frames ();
+ assert (from->frames() == N);
+
+ float* s = from->data (from_channel);
+ float* d = _data[to_channel];
+
+ for (int i = 0; i < N; ++i) {
+ *d++ += *s++;
+ }
+}
+
/** Trip an assert if the caller is not in the UI thread */
void
ensure_ui_thread ()
{
- assert (this_thread::get_id() == ui_thread);
+ assert (boost::this_thread::get_id() == ui_thread);
}
-/** @param v Source video frame.
+/** @param v Content video frame.
* @param audio_sample_rate Source audio sample rate.
* @param frames_per_second Number of video frames per second.
* @return Equivalent number of audio frames for `v'.
*/
int64_t
-video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second)
{
return ((int64_t) v * audio_sample_rate / frames_per_second);
}
-/** @param f Filename.
- * @return true if this file is a still image, false if it is something else.
- */
-bool
-still_image_file (string f)
-{
- string ext = boost::filesystem::path(f).extension().string();
-
- transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-
- return (ext == N_(".tif") || ext == N_(".tiff") || ext == N_(".jpg") || ext == N_(".jpeg") || ext == N_(".png") || ext == N_(".bmp"));
-}
-
/** @return A pair containing CPU model name and the number of processors */
pair<string, int>
cpu_info ()
@@ -938,58 +948,6 @@ audio_channel_name (int c)
return channels[c];
}
-AudioMapping::AudioMapping (int c)
- : _source_channels (c)
-{
-
-}
-
-optional<libdcp::Channel>
-AudioMapping::source_to_dcp (int c) const
-{
- if (c >= _source_channels) {
- return optional<libdcp::Channel> ();
- }
-
- if (_source_channels == 1) {
- /* mono sources to centre */
- return libdcp::CENTRE;
- }
-
- return static_cast<libdcp::Channel> (c);
-}
-
-optional<int>
-AudioMapping::dcp_to_source (libdcp::Channel c) const
-{
- if (_source_channels == 1) {
- if (c == libdcp::CENTRE) {
- return 0;
- } else {
- return optional<int> ();
- }
- }
-
- if (static_cast<int> (c) >= _source_channels) {
- return optional<int> ();
- }
-
- return static_cast<int> (c);
-}
-
-int
-AudioMapping::dcp_channels () const
-{
- if (_source_channels == 1) {
- /* The source is mono, so to put the mono channel into
- the centre we need to generate a 5.1 soundtrack.
- */
- return 6;
- }
-
- return _source_channels;
-}
-
FrameRateConversion::FrameRateConversion (float source, int dcp)
: skip (false)
, repeat (false)
diff --git a/src/lib/util.h b/src/lib/util.h
index 3d251cf06..f4af7c22b 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -37,6 +37,7 @@ extern "C" {
#include <libavfilter/avfilter.h>
}
#include "compose.hpp"
+#include "types.h"
#ifdef DVDOMATIC_DEBUG
#define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
@@ -57,7 +58,7 @@ extern double seconds (struct timeval);
extern void dvdomatic_setup ();
extern void dvdomatic_setup_i18n (std::string);
extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
-extern std::string md5_digest (std::string);
+extern std::string md5_digest (boost::filesystem::path);
extern std::string md5_digest (void const *, int);
extern void ensure_ui_thread ();
extern std::string audio_channel_name (int);
@@ -65,8 +66,6 @@ extern std::string audio_channel_name (int);
extern boost::filesystem::path mo_path ();
#endif
-typedef int SourceFrame;
-
struct FrameRateConversion
{
FrameRateConversion (float, int);
@@ -104,87 +103,6 @@ struct FrameRateConversion
int best_dcp_frame_rate (float);
-enum ContentType {
- STILL, ///< content is still images
- VIDEO ///< content is a video
-};
-
-/** @struct Crop
- * @brief A description of the crop of an image or video.
- */
-struct Crop
-{
- Crop () : left (0), right (0), top (0), bottom (0) {}
-
- /** Number of pixels to remove from the left-hand side */
- int left;
- /** Number of pixels to remove from the right-hand side */
- int right;
- /** Number of pixels to remove from the top */
- int top;
- /** Number of pixels to remove from the bottom */
- int bottom;
-};
-
-extern bool operator== (Crop const & a, Crop const & b);
-extern bool operator!= (Crop const & a, Crop const & b);
-
-/** @struct Position
- * @brief A position.
- */
-struct Position
-{
- Position ()
- : x (0)
- , y (0)
- {}
-
- Position (int x_, int y_)
- : x (x_)
- , y (y_)
- {}
-
- /** x coordinate */
- int x;
- /** y coordinate */
- int y;
-};
-
-/** @struct Rect
- * @brief A rectangle.
- */
-struct Rect
-{
- Rect ()
- : x (0)
- , y (0)
- , width (0)
- , height (0)
- {}
-
- Rect (int x_, int y_, int w_, int h_)
- : x (x_)
- , y (y_)
- , width (w_)
- , height (h_)
- {}
-
- int x;
- int y;
- int width;
- int height;
-
- Position position () const {
- return Position (x, y);
- }
-
- libdcp::Size size () const {
- return libdcp::Size (width, height);
- }
-
- Rect intersection (Rect const & other) const;
-};
-
extern std::string crop_string (Position, libdcp::Size);
extern int dcp_audio_sample_rate (int);
extern std::string colour_lut_index_to_name (int index);
@@ -264,6 +182,7 @@ public:
void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset);
void move (int from, int to, int frames);
+ void accumulate (boost::shared_ptr<AudioBuffers>, int, int);
private:
/** Number of channels */
@@ -276,21 +195,7 @@ private:
float** _data;
};
-class AudioMapping
-{
-public:
- AudioMapping (int);
-
- boost::optional<libdcp::Channel> source_to_dcp (int c) const;
- boost::optional<int> dcp_to_source (libdcp::Channel c) const;
- int dcp_channels () const;
-
-private:
- int _source_channels;
-};
-
-extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
-extern bool still_image_file (std::string);
+extern int64_t video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second);
extern std::pair<std::string, int> cpu_info ();
#endif
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
new file mode 100644
index 000000000..9fb2b9bce
--- /dev/null
+++ b/src/lib/video_content.cc
@@ -0,0 +1,106 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "video_content.h"
+#include "video_decoder.h"
+
+#include "i18n.h"
+
+int const VideoContentProperty::VIDEO_LENGTH = 0;
+int const VideoContentProperty::VIDEO_SIZE = 1;
+int const VideoContentProperty::VIDEO_FRAME_RATE = 2;
+
+using std::string;
+using std::stringstream;
+using std::setprecision;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+VideoContent::VideoContent (boost::filesystem::path f)
+ : Content (f)
+ , _video_length (0)
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const cxml::Node> node)
+ : Content (node)
+{
+ _video_length = node->number_child<ContentVideoFrame> ("VideoLength");
+ _video_size.width = node->number_child<int> ("VideoWidth");
+ _video_size.height = node->number_child<int> ("VideoHeight");
+ _video_frame_rate = node->number_child<float> ("VideoFrameRate");
+}
+
+VideoContent::VideoContent (VideoContent const & o)
+ : Content (o)
+ , _video_length (o._video_length)
+ , _video_size (o._video_size)
+ , _video_frame_rate (o._video_frame_rate)
+{
+
+}
+
+void
+VideoContent::as_xml (xmlpp::Node* node) const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
+ node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
+ node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
+ node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
+}
+
+void
+VideoContent::take_from_video_decoder (shared_ptr<VideoDecoder> d)
+{
+ /* These decoder calls could call other content methods which take a lock on the mutex */
+ libdcp::Size const vs = d->native_size ();
+ float const vfr = d->video_frame_rate ();
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _video_size = vs;
+ _video_frame_rate = vfr;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_SIZE);
+ signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
+}
+
+
+string
+VideoContent::information () const
+{
+ if (video_size().width == 0 || video_size().height == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (
+ _("%1x%2 pixels (%3:1)"),
+ video_size().width,
+ video_size().height,
+ setprecision (3), float (video_size().width) / video_size().height
+ );
+
+ return s.str ();
+}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
new file mode 100644
index 000000000..3d2c4cab2
--- /dev/null
+++ b/src/lib/video_content.h
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_VIDEO_CONTENT_H
+#define DVDOMATIC_VIDEO_CONTENT_H
+
+#include "content.h"
+#include "util.h"
+
+class VideoDecoder;
+
+class VideoContentProperty
+{
+public:
+ static int const VIDEO_LENGTH;
+ static int const VIDEO_SIZE;
+ static int const VIDEO_FRAME_RATE;
+};
+
+class VideoContent : public virtual Content
+{
+public:
+ VideoContent (boost::filesystem::path);
+ VideoContent (boost::shared_ptr<const cxml::Node>);
+ VideoContent (VideoContent const &);
+
+ void as_xml (xmlpp::Node *) const;
+ virtual std::string information () const;
+
+ ContentVideoFrame video_length () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_length;
+ }
+
+ libdcp::Size video_size () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_size;
+ }
+
+ float video_frame_rate () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_frame_rate;
+ }
+
+protected:
+ void take_from_video_decoder (boost::shared_ptr<VideoDecoder>);
+
+ ContentVideoFrame _video_length;
+
+private:
+ libdcp::Size _video_size;
+ float _video_frame_rate;
+};
+
+#endif
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index 891720f6b..fd2b28d7f 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -22,7 +22,6 @@
#include "film.h"
#include "image.h"
#include "log.h"
-#include "options.h"
#include "job.h"
#include "i18n.h"
@@ -30,8 +29,8 @@
using boost::shared_ptr;
using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
+ : Decoder (f)
, _video_frame (0)
, _last_source_time (0)
{
@@ -51,8 +50,13 @@ VideoDecoder::emit_video (shared_ptr<Image> image, double t)
sub = _timed_subtitle->subtitle ();
}
- signal_video (image, false, sub);
- _last_source_time = t;
+ signal_video (image, false, sub, t);
+}
+
+bool
+VideoDecoder::have_last_video () const
+{
+ return _last_image;
}
/** Called by subclasses to repeat the last video frame that we
@@ -60,14 +64,14 @@ VideoDecoder::emit_video (shared_ptr<Image> image, double t)
* we will generate a black frame.
*/
void
-VideoDecoder::repeat_last_video ()
+VideoDecoder::repeat_last_video (double t)
{
if (!_last_image) {
_last_image.reset (new SimpleImage (pixel_format(), native_size(), true));
_last_image->make_black ();
}
- signal_video (_last_image, true, _last_subtitle);
+ signal_video (_last_image, true, _last_subtitle, t);
}
/** Emit our signal to say that some video data is ready.
@@ -76,7 +80,7 @@ VideoDecoder::repeat_last_video ()
* @param sub Subtitle for this frame, or 0.
*/
void
-VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
+VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub, double t)
{
TIMING (N_("Decoder emits %1"), _video_frame);
Video (image, same, sub);
@@ -84,6 +88,7 @@ VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subti
_last_image = image;
_last_subtitle = sub;
+ _last_source_time = t;
}
/** Set up the current subtitle. This will be put onto frames that
@@ -102,21 +107,12 @@ VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
}
}
-/** Set which stream of subtitles we should use from our source.
- * @param s Stream to use.
- */
-void
-VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- _subtitle_stream = s;
-}
-
void
VideoDecoder::set_progress (Job* j) const
{
assert (j);
-
- if (_film->length()) {
- j->set_progress (float (_video_frame) / _film->length().get());
+
+ if (_film->video_length()) {
+ j->set_progress (float (_video_frame) / _film->video_length());
}
}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 283ab5d88..c04874342 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -21,42 +21,33 @@
#define DVDOMATIC_VIDEO_DECODER_H
#include "video_source.h"
-#include "stream.h"
#include "decoder.h"
+class VideoContent;
+
class VideoDecoder : public VideoSource, public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<Film>, DecodeOptions);
+ VideoDecoder (boost::shared_ptr<const Film>);
- /** @return video frames per second, or 0 if unknown */
- virtual float frames_per_second () const = 0;
+ /** @return video frame rate second, or 0 if unknown */
+ virtual float video_frame_rate () const = 0;
/** @return native size in pixels */
virtual libdcp::Size native_size () const = 0;
- /** @return length (in source video frames), according to our content's header */
- virtual SourceFrame length () const = 0;
+ /** @return length according to our content's header */
+ virtual ContentVideoFrame video_length () const = 0;
virtual int time_base_numerator () const = 0;
virtual int time_base_denominator () const = 0;
virtual int sample_aspect_ratio_numerator () const = 0;
virtual int sample_aspect_ratio_denominator () const = 0;
- virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
-
void set_progress (Job *) const;
int video_frame () const {
return _video_frame;
}
- boost::shared_ptr<SubtitleStream> subtitle_stream () const {
- return _subtitle_stream;
- }
-
- std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
- return _subtitle_streams;
- }
-
double last_source_time () const {
return _last_source_time;
}
@@ -67,15 +58,11 @@ protected:
void emit_video (boost::shared_ptr<Image>, double);
void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
- void repeat_last_video ();
-
- /** Subtitle stream to use when decoding */
- boost::shared_ptr<SubtitleStream> _subtitle_stream;
- /** Subtitle streams that this decoder's content has */
- std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
+ bool have_last_video () const;
+ void repeat_last_video (double);
private:
- void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
+ void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
int _video_frame;
double _last_source_time;
diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc
index 56742e2b4..1c4d6466c 100644
--- a/src/lib/video_source.cc
+++ b/src/lib/video_source.cc
@@ -21,10 +21,23 @@
#include "video_sink.h"
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::bind;
+static void
+process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
+{
+ shared_ptr<VideoSink> p = sink.lock ();
+ if (p) {
+ p->process_video (i, same, s);
+ }
+}
+
void
VideoSource::connect_video (shared_ptr<VideoSink> s)
{
- Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
+ /* If we bind, say, a Playlist (as the VideoSink) to a Decoder (which is owned
+ by the Playlist) we create a cycle. Use a weak_ptr to break it.
+ */
+ Video.connect (bind (process_video_proxy, boost::weak_ptr<VideoSink> (s), _1, _2, _3));
}
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
index 893629160..e60e7dfd0 100644
--- a/src/lib/video_source.h
+++ b/src/lib/video_source.h
@@ -32,7 +32,7 @@ class VideoSink;
class Subtitle;
class Image;
-/** @class VideoSink
+/** @class VideoSource
* @param A class that emits video data.
*/
class VideoSource
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index 2d7ee9ba3..7258826ba 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -22,12 +22,16 @@
#include <libdcp/sound_asset.h>
#include <libdcp/picture_frame.h>
#include <libdcp/reel.h>
+#include <libdcp/dcp.h>
#include "writer.h"
#include "compose.hpp"
#include "film.h"
#include "format.h"
#include "log.h"
#include "dcp_video_frame.h"
+#include "dcp_content_type.h"
+#include "player.h"
+#include "audio_mapping.h"
#include "i18n.h"
@@ -74,16 +78,14 @@ Writer::Writer (shared_ptr<Film> f)
_picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
- AudioMapping m (_film->audio_channels ());
-
- if (m.dcp_channels() > 0) {
+ if (_film->audio_channels() > 0) {
_sound_asset.reset (
new libdcp::SoundAsset (
_film->dir (_film->dcp_name()),
N_("audio.mxf"),
_film->dcp_frame_rate (),
- m.dcp_channels (),
- dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
+ _film->audio_mapping().dcp_channels (),
+ dcp_audio_sample_rate (_film->audio_frame_rate())
)
);
diff --git a/src/lib/wscript b/src/lib/wscript
index 8e9d34706..8f0e851e3 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -6,16 +6,18 @@ sources = """
ab_transcoder.cc
analyse_audio_job.cc
audio_analysis.cc
+ audio_content.cc
audio_decoder.cc
+ audio_mapping.cc
audio_source.cc
config.cc
combiner.cc
+ content.cc
cross.cc
dci_metadata.cc
dcp_content_type.cc
dcp_video_frame.cc
decoder.cc
- decoder_factory.cc
delay_line.cc
dolby_cp750.cc
encoder.cc
@@ -23,30 +25,36 @@ sources = """
exceptions.cc
filter_graph.cc
ffmpeg_compatibility.cc
+ ffmpeg_content.cc
ffmpeg_decoder.cc
film.cc
filter.cc
format.cc
gain.cc
image.cc
+ imagemagick_content.cc
imagemagick_decoder.cc
job.cc
job_manager.cc
log.cc
lut.cc
matcher.cc
+ player.cc
+ playlist.cc
scp_dcp_job.cc
scaler.cc
server.cc
+ sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
- stream.cc
subtitle.cc
timer.cc
transcode_job.cc
transcoder.cc
+ types.cc
ui_signaller.cc
util.cc
+ video_content.cc
video_decoder.cc
video_source.cc
writer.cc
@@ -63,7 +71,7 @@ def build(bld):
obj.uselib = """
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
- SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB LZMA
+ SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA
"""
if bld.env.TARGET_WINDOWS:
obj.uselib += ' WINSOCK2'