- dcpomatic (0.83-1) UNRELEASED; urgency=low
+ dvdomatic (0.84-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan> Sun, 21 Apr 2013 17:49:54 +0100
+
+ dvdomatic (0.84beta5-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan> Sun, 21 Apr 2013 00:06:12 +0100
+
+ dvdomatic (0.84beta4-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan> Fri, 19 Apr 2013 17:41:58 +0100
+
+ dvdomatic (0.84beta3-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan> Fri, 19 Apr 2013 11:36:37 +0100
+
+ dvdomatic (0.84beta2-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan> Fri, 19 Apr 2013 11:12:09 +0100
+
+ dvdomatic (0.84beta1-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Carl Hetherington <carl@houllier.lan> Thu, 18 Apr 2013 23:32:17 +0100
+
+ dvdomatic (0.83-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 10 Apr 2013 12:48:25 +0100
-dvdomatic (0.82-1) UNRELEASED; urgency=low
+dcpomatic (0.82-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 23:43:35 +0100
-dvdomatic (0.82beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.82beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 21:48:56 +0100
-dvdomatic (0.81-1) UNRELEASED; urgency=low
+dcpomatic (0.81-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 19:48:04 +0100
-dvdomatic (0.81beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.81beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 09 Apr 2013 15:37:32 +0100
-dvdomatic (0.80-1) UNRELEASED; urgency=low
+dcpomatic (0.80-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 23:48:12 +0100
-dvdomatic (0.80beta4-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta4-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 23:08:49 +0100
-dvdomatic (0.80beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta3-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 22:44:29 +0100
-dvdomatic (0.80beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta2-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 22:19:34 +0100
-dvdomatic (0.80beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.80beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 07 Apr 2013 18:21:33 +0100
-dvdomatic (0.79-1) UNRELEASED; urgency=low
+dcpomatic (0.79-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Mon, 01 Apr 2013 22:37:03 +0100
-dvdomatic (0.78-1) UNRELEASED; urgency=low
+dcpomatic (0.78-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 31 Mar 2013 02:43:03 +0100
-dvdomatic (0.78beta16-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta16-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 28 Mar 2013 16:28:05 +0000
-dvdomatic (0.78beta15-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta15-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 28 Mar 2013 14:25:56 +0000
-dvdomatic (0.78beta14-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta14-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 28 Mar 2013 10:38:07 +0000
-dvdomatic (0.78beta13-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta13-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 27 Mar 2013 12:26:55 +0000
-dvdomatic (0.78beta12-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta12-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 21:13:54 +0000
-dvdomatic (0.78beta11-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta11-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 17:34:49 +0000
-dvdomatic (0.78beta10-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta10-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 11:35:15 +0000
-dvdomatic (0.78beta9-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta9-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 10:36:05 +0000
-dvdomatic (0.78beta8-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta8-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 00:59:36 +0000
-dvdomatic (0.78beta7-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta7-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 26 Mar 2013 00:19:21 +0000
-dvdomatic (0.78beta6-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta6-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Mon, 25 Mar 2013 00:08:10 +0000
-dvdomatic (0.78beta5-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta5-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 21 Mar 2013 16:32:21 +0000
-dvdomatic (0.78beta4-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta4-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 20 Mar 2013 15:01:10 +0000
-dvdomatic (0.78beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta3-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 20 Mar 2013 10:49:17 +0000
-dvdomatic (0.78beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta2-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 19 Mar 2013 21:35:50 +0000
-dvdomatic (0.78beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.78beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 19 Mar 2013 20:50:54 +0000
-dvdomatic (0.77-1) UNRELEASED; urgency=low
+dcpomatic (0.77-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 14 Mar 2013 17:12:03 +0000
-dvdomatic (0.77beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.77beta2-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 14 Mar 2013 15:50:43 +0000
-dvdomatic (0.77beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.77beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 14 Mar 2013 15:14:01 +0000
-dvdomatic (0.76-1) UNRELEASED; urgency=low
+dcpomatic (0.76-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 05 Mar 2013 13:30:28 +0000
-dvdomatic (0.76beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.76beta3-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 05 Mar 2013 12:47:20 +0000
-dvdomatic (0.76beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.76beta2-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Fri, 01 Mar 2013 18:32:16 +0000
-dvdomatic (0.76beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.76beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Fri, 01 Mar 2013 17:36:55 +0000
-dvdomatic (0.75-1) UNRELEASED; urgency=low
+dcpomatic (0.75-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 27 Feb 2013 11:03:07 +0000
-dvdomatic (0.75beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.75beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 27 Feb 2013 08:20:42 +0000
-dvdomatic (0.74-1) UNRELEASED; urgency=low
+dcpomatic (0.74-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sat, 23 Feb 2013 22:57:20 +0000
-dvdomatic (0.74beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.74beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sat, 23 Feb 2013 21:44:22 +0000
-dvdomatic (0.73-1) UNRELEASED; urgency=low
+dcpomatic (0.73-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 21 Feb 2013 00:43:40 +0000
-dvdomatic (0.73beta9-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta9-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 20 Feb 2013 23:40:24 +0000
-dvdomatic (0.73beta8-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta8-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Mon, 18 Feb 2013 22:35:51 +0000
-dvdomatic (0.73beta7-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta7-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Mon, 18 Feb 2013 20:38:51 +0000
-dvdomatic (0.73beta6-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta6-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 17 Feb 2013 23:05:56 +0000
-dvdomatic (0.73beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta3-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 17 Feb 2013 23:05:05 +0000
-dvdomatic (0.73beta2-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta2-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sat, 16 Feb 2013 22:42:32 +0000
-dvdomatic (0.73beta1-1) UNRELEASED; urgency=low
+dcpomatic (0.73beta1-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sat, 16 Feb 2013 21:19:24 +0000
-dvdomatic (0.72-1) UNRELEASED; urgency=low
+dcpomatic (0.72-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 24 Jan 2013 15:31:57 +0000
-dvdomatic (0.71-1) UNRELEASED; urgency=low
+dcpomatic (0.71-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 24 Jan 2013 11:36:04 +0000
-dvdomatic (0.70-1) UNRELEASED; urgency=low
+dcpomatic (0.70-1) UNRELEASED; urgency=low
* New upstream release.
* New upstream release.
-- Carl Hetherington <cth@carlh.net> Sat, 12 Jan 2013 23:07:15 +0000
-dvdomatic (0.70beta3-1) UNRELEASED; urgency=low
+dcpomatic (0.70beta3-1) UNRELEASED; urgency=low
* New upstream release.
* New upstream release.
-- Carl Hetherington <cth@carlh.net> Sun, 06 Jan 2013 23:44:24 +0000
-dvdomatic (0.68-1) UNRELEASED; urgency=low
+dcpomatic (0.68-1) UNRELEASED; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 23 Dec 2012 01:43:44 +0000
-dvdomatic (0.68beta10-1) UNRELEASED; urgency=low
+dcpomatic (0.68beta10-1) UNRELEASED; urgency=low
* New upstream release.
* New upstream release.
-- Carl Hetherington <cth@carlh.net> Sat, 22 Dec 2012 13:27:27 +0000
-dvdomatic (0.68beta5-1) unstable; urgency=low
+dcpomatic (0.68beta5-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 20 Dec 2012 07:53:46 +0000
-dvdomatic (0.68beta4-1) unstable; urgency=low
+dcpomatic (0.68beta4-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 20 Dec 2012 07:48:45 +0000
-dvdomatic (0.68beta3-1) unstable; urgency=low
+dcpomatic (0.68beta3-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 20 Dec 2012 00:35:45 +0000
-dvdomatic (0.68beta2-1) unstable; urgency=low
+dcpomatic (0.68beta2-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 19 Dec 2012 11:22:58 +0000
-dvdomatic (0.68beta1-1) unstable; urgency=low
+dcpomatic (0.68beta1-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Wed, 19 Dec 2012 10:11:13 +0000
-dvdomatic (0.67-1) unstable; urgency=low
+dcpomatic (0.67-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 18 Dec 2012 23:49:27 +0000
-dvdomatic (0.66-1) unstable; urgency=low
+dcpomatic (0.66-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 18 Dec 2012 11:29:04 +0000
-dvdomatic (0.65-1) unstable; urgency=low
+dcpomatic (0.65-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 18 Dec 2012 09:24:56 +0000
-dvdomatic (0.64-1) unstable; urgency=low
+dcpomatic (0.64-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Thu, 13 Dec 2012 21:52:09 +0000
-dvdomatic (0.63pre-1) unstable; urgency=low
+dcpomatic (0.63pre-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 11 Dec 2012 23:15:52 +0000
-dvdomatic (0.60-1) unstable; urgency=low
+dcpomatic (0.60-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Tue, 11 Dec 2012 22:46:04 +0000
-dvdomatic (0.59-1) unstable; urgency=low
+dcpomatic (0.59-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Mon, 10 Dec 2012 20:58:19 +0000
-dvdomatic (0.59beta5-1) unstable; urgency=low
+dcpomatic (0.59beta5-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 09 Dec 2012 23:51:55 +0000
-dvdomatic (0.59beta4-1) unstable; urgency=low
+dcpomatic (0.59beta4-1) unstable; urgency=low
* New upstream release.
-- Carl Hetherington <carl@houllier.lan> Sun, 09 Dec 2012 21:38:00 +0000
-dvdomatic (0.59beta1-1) unstable; urgency=low
+dcpomatic (0.59beta1-1) unstable; urgency=low
* Initial release.
dh $@
override_dh_auto_configure:
- ./waf --nocache configure --prefix=/usr --static
+ LINKFLAGS=$(CDIST_LINKFLAGS) CXXFLAGS="$(CXXFLAGS) $(CDIST_CXXFLAGS)" PKG_CONFIG_PATH=$(CDIST_PKG_CONFIG_PATH) \
+ ./waf --nocache configure --prefix=/usr --static
override_dh_auto_build:
./waf --nocache build
override_dh_auto_install:
- ./waf --nocache install --destdir=debian/dvdomatic
+ ./waf --nocache install --destdir=debian/dcpomatic
#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"
* @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()))
{
- 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()));
- }
- _da = decoder_factory (_film_a, o);
- _db = decoder_factory (_film_b, o);
-
- 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(), _film_a->audio_delay() / 1000.0f));
++ _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_delay() * _film_a->audio_frame_rate() / 1000));
+ _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
- _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));
- /* 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, _4));
- _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
++ _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4));
++ _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
- if (_matcher) {
- _combiner->connect_video (_matcher);
- _matcher->connect_video (_encoder);
- } else {
- _combiner->connect_video (_encoder);
- }
+ _combiner->connect_video (_delay_line);
+ _delay_line->connect_video (_matcher);
+ _matcher->connect_video (_encoder);
- if (_matcher && _delay_line) {
- _player_a->connect_audio (_delay_line);
- _delay_line->connect_audio (_matcher);
- _matcher->connect_audio (_gain);
- _gain->connect_audio (_encoder);
- }
- _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);
}
void
{
_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;
}
}
* @brief Parent class for audio decoders.
*/
-#ifndef DVDOMATIC_AUDIO_DECODER_H
-#define DVDOMATIC_AUDIO_DECODER_H
+#ifndef DCPOMATIC_AUDIO_DECODER_H
+#define DCPOMATIC_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
+ class AudioDecoder : public TimedAudioSource, 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
*/
-#ifndef DVDOMATIC_AUDIO_SINK_H
-#define DVDOMATIC_AUDIO_SINK_H
+#ifndef DCPOMATIC_AUDIO_SINK_H
+#define DCPOMATIC_AUDIO_SINK_H
class AudioSink
{
virtual void process_audio (boost::shared_ptr<AudioBuffers>) = 0;
};
+ class TimedAudioSink
+ {
+ public:
+ /** Call with some audio data */
+ virtual void process_audio (boost::shared_ptr<AudioBuffers>, double t) = 0;
+ };
+
#endif
#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));
}
+
+ void
+ TimedAudioSource::connect_audio (shared_ptr<TimedAudioSink> s)
+ {
+ Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2));
+ }
* @brief Parent class for classes which emit audio data.
*/
-#ifndef DVDOMATIC_AUDIO_SOURCE_H
-#define DVDOMATIC_AUDIO_SOURCE_H
+#ifndef DCPOMATIC_AUDIO_SOURCE_H
+#define DCPOMATIC_AUDIO_SOURCE_H
#include <boost/signals2.hpp>
class AudioBuffers;
class AudioSink;
+ class TimedAudioSink;
/** A class that emits audio data */
class AudioSource
void connect_audio (boost::shared_ptr<AudioSink>);
};
+
+ /** A class that emits audio data with timestamps */
+ class TimedAudioSource
+ {
+ public:
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>, double)> Audio;
+
+ void connect_audio (boost::shared_ptr<TimedAudioSink>);
+ };
+
#endif
* @brief Parent class for decoders of content.
*/
-#ifndef DVDOMATIC_DECODER_H
-#define DVDOMATIC_DECODER_H
+#ifndef DCPOMATIC_DECODER_H
+#define DCPOMATIC_DECODER_H
#include <vector>
#include <string>
#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;
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 ();
+ virtual void seek_back () {}
+ virtual void seek_forward () {}
+
+ 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;
};
--- /dev/null
- shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false, true));
+/*
+ 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 std::cout;
+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));
+
+ 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;
+}
+
+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");
+ channels = node->number_child<int64_t> ("Channels");
+}
+
+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("Channels")->add_child_text (lexical_cast<string> (channels));
+}
+
+/** 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));
+}
#include "transcoder.h"
#include "job.h"
#include "filter.h"
-#include "options.h"
#include "exceptions.h"
#include "image.h"
#include "util.h"
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)
++FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles)
+ : Decoder (f)
+ , VideoDecoder (f)
+ , AudioDecoder (f)
+ , _ffmpeg_content (c)
, _format_context (0)
, _video_stream (-1)
, _frame (0)
, _audio_codec (0)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
- , _video_sync (video_sync)
+ , _decode_video (video)
+ , _decode_audio (audio)
+ , _decode_subtitles (subtitles)
{
setup_general ();
setup_video ();
setup_audio ();
setup_subtitle ();
-
- 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);
}
{
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];
}
_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->channels)
);
} 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));
}
}
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);
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) {
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) {
av_strerror (r, buf, sizeof(buf));
_film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
}
-
+
/* Get any remaining frames */
_packet.data = 0;
_packet.size = 0;
-
+
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
-
+
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);
+ filter_and_emit_video ();
}
}
-
- if (_audio_stream && _opt.decode_audio) {
+
+ if (_ffmpeg_content->audio_stream() && _decode_audio) {
decode_audio_packet ();
}
-
+
return true;
}
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);
_film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
}
- if (_video_sync) {
- out_with_sync ();
- } else {
- filter_and_emit_video (_frame);
- }
+ filter_and_emit_video ();
}
- } 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 (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles && _first_video) {
- } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) {
++ } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles) {
int got_subtitle;
AVSubtitle sub;
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:
audio->data(channel)[sample] = float(*p++) / (1 << 15);
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
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);
}
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;
}
audio->data(channel)[sample] = *p++;
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
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));
}
}
}
float
-FFmpegDecoder::frames_per_second () const
+FFmpegDecoder::video_frame_rate () const
{
AVStream* s = _format_context->streams[_video_stream];
FFmpegDecoder::stream_name (AVStream* s) const
{
stringstream n;
-
- AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
- if (lang) {
- n << lang->value;
- }
-
- AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
- if (title) {
- if (!n.str().empty()) {
- n << N_(" ");
+
+ if (s->metadata) {
+ AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
+ if (lang) {
+ n << lang->value;
+ }
+
+ AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
+ if (title) {
+ if (!n.str().empty()) {
+ n << N_(" ");
+ }
+ n << title->value;
}
- n << title->value;
}
if (n.str().empty()) {
return av_get_bytes_per_sample (audio_sample_format ());
}
-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)
+ FFmpegDecoder::filter_and_emit_video ()
{
boost::mutex::scoped_lock lm (_filter_graphs_mutex);
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (frame->width, frame->height), (AVPixelFormat) frame->format)) {
+ while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
++i;
}
if (i == _filter_graphs.end ()) {
- graph.reset (new FilterGraph (_film, this, libdcp::Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+ graph.reset (new FilterGraph (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
_filter_graphs.push_back (graph);
- _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), frame->width, frame->height, frame->format));
+ _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
} else {
graph = *i;
}
- list<shared_ptr<Image> > images = graph->process (frame);
+ list<shared_ptr<Image> > images = graph->process (_frame);
for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
- emit_video (*i, frame_time ());
+ int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+ if (bet != AV_NOPTS_VALUE) {
+ emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base));
+ } else {
+ _film->log()->log ("Dropping frame without PTS");
+ }
}
}
bool
FFmpegDecoder::seek (double p)
{
- /* This use of AVSEEK_FLAG_BACKWARD is a bit of a hack; without it, if we ask for a seek to the same place as last time
- (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
- staying in the same place.
- */
- bool const backwards = (p == last_content_time());
-
+ return do_seek (p, false, false);
+ }
+
-bool
-FFmpegDecoder::seek_to_last ()
-{
- /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
- (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
- staying in the same place.
- */
- return do_seek (last_source_time(), true, false);
-}
-
+ void
+ FFmpegDecoder::seek_back ()
+ {
- do_seek (last_source_time() - 2.5 / frames_per_second (), true, true);
++ do_seek (last_content_time() - 2.5 / video_frame_rate(), true, true);
+ }
+
+ void
+ FFmpegDecoder::seek_forward ()
+ {
- do_seek (last_source_time() - 0.5 / frames_per_second(), true, true);
++ do_seek (last_content_time() - 0.5 / video_frame_rate(), true, true);
+ }
+
+ bool
+ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
+ {
int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
-
+
avcodec_flush_buffers (_video_codec_context);
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
-
- return r < 0;
- }
- void
- FFmpegDecoder::out_with_sync ()
- {
- /* Where we are in the output, in seconds */
- 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)
- * av_frame_get_best_effort_timestamp(_frame);
-
- _film->log()->log (
- String::compose (N_("Source video frame ready; source at %1, output at %2"), source_pts_seconds, out_pts_seconds),
- Log::VERBOSE
- );
-
- if (!_first_video) {
- _first_video = source_pts_seconds;
- }
-
- /* 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 / 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 (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, video_frame_rate()
- )
- );
+ if (accurate) {
+ while (1) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ return true;
+ }
+
+ avcodec_get_frame_defaults (_frame);
+
+ if (_packet.stream_index == _video_stream) {
+ int finished = 0;
+ int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+ if (bet > vt) {
+ break;
+ }
+ }
+ }
+
+ av_free_packet (&_packet);
}
}
-
- if (delta > -one_frame) {
- /* Process this frame */
- filter_and_emit_video (_frame);
- } else {
- /* Otherwise we are omitting a frame to keep things right */
- _film->log()->log (String::compose (N_("Frame removed at %1s"), out_pts_seconds));
- }
+
+ 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::film_changed (Film::Property p)
{
boost::mutex::scoped_lock lm (_filter_graphs_mutex);
_filter_graphs.clear ();
}
- OutputChanged ();
break;
default:
}
/** @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
- FFmpegDecoder::frame_time () const
- {
- return av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base);
- }
-
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.
*/
int frame_finished;
int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, ©_packet);
- if (decode_result >= 0 && frame_finished) {
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
-
- /* We only decode audio if we've had our first video packet through, and if it
- was before this packet. Until then audio is thrown away.
- */
+ if (decode_result >= 0) {
+ if (frame_finished) {
- if ((_first_video && _first_video.get() <= source_pts_seconds) || !_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
- first video frame. Push some silence to make up any gap between our first
- video frame and our first audio.
- */
-
- /* frames of silence that we must push */
- 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, _ffmpeg_content->audio_channels(), bytes_per_audio_sample()
- )
- );
-
- if (s) {
- shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), s));
- audio->make_silent ();
- Audio (audio);
- }
- }
+ /* Where we are in the source, in seconds */
+ double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame);
int const data_size = av_samples_get_buffer_size (
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));
+ Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
}
- }
-
- if (decode_result >= 0) {
+
copy_packet.data += decode_result;
copy_packet.size -= decode_result;
}
#include "video_decoder.h"
#include "audio_decoder.h"
#include "film.h"
+#include "ffmpeg_content.h"
struct AVFilterGraph;
struct AVCodecContext;
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<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles, bool video_sync);
- FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions);
++ FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles);
~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 ();
+ void seek_forward ();
+ void seek_back ();
+ bool pass ();
private:
- bool pass ();
- bool do_seek (double p, bool, bool);
+ /* No copy construction */
+ FFmpegDecoder (FFmpegDecoder const &);
+ FFmpegDecoder& operator= (FFmpegDecoder const &);
+
PixelFormat pixel_format () const;
AVSampleFormat audio_sample_format () const;
int bytes_per_audio_sample () const;
++ bool do_seek (double, bool, bool);
- void out_with_sync ();
- void filter_and_emit_video (AVFrame *);
- double frame_time () const;
+ void filter_and_emit_video ();
void setup_general ();
void setup_video ();
std::string stream_name (AVStream* s) const;
+ boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
AVFormatContext* _format_context;
int _video_stream;
AVPacket _packet;
- boost::optional<double> _first_video;
- boost::optional<double> _first_audio;
-
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
boost::mutex _filter_graphs_mutex;
- bool _video_sync;
+
+ std::vector<FFmpegSubtitleStream> _subtitle_streams;
+ std::vector<FFmpegAudioStream> _audio_streams;
+
+ bool _decode_video;
+ bool _decode_audio;
+ bool _decode_subtitles;
+
+ /* 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;
};
#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"
using std::min;
using std::make_pair;
using std::endl;
+using std::cout;
+using std::list;
using boost::shared_ptr;
using boost::lexical_cast;
using boost::to_upper_copy;
*/
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)
, _trim_type (CPL)
- , _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)
, _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)
}
}
- _sndfile_stream = SndfileStream::create ();
-
if (must_exist) {
read_metadata ();
+ } else {
+ write_metadata ();
}
_log.reset (new FileLog (file ("log")));
: 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)
, _trim_start (o._trim_start)
, _trim_end (o._trim_end)
, _trim_type (o._trim_type)
- , _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
Film::video_state_identifier () const
{
assert (format ());
+ LocaleGuard lg;
pair<string, string> f = Filter::ffmpeg_strings (filters());
stringstream s;
s << format()->id()
- << "_" << content_digest()
+ << "_" << _playlist->video_digest()
<< "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
<< "_" << _dcp_frame_rate
<< "_" << f.first << "_" << f.second
<< "_" << 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;
}
{
boost::filesystem::path p;
p /= "analysis";
- p /= content_digest();
+ p /= _playlist->audio_digest();
return file (p.string ());
}
throw BadSettingError (_("name"), _("cannot contain slashes"));
}
- log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
+ log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
{
char buffer[128];
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
- log()->log ("DVD-o-matic built in debug mode.");
+#ifdef DCPOMATIC_DEBUG
+ log()->log ("DCP-o-matic built in debug mode.");
#else
- log()->log ("DVD-o-matic built in optimised mode.");
+ log()->log ("DCP-o-matic built in optimised mode.");
#endif
#ifdef LIBDCP_DEBUG
log()->log ("libdcp built in debug mode.");
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 ()
{
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
_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 ()
void
Film::write_metadata () const
{
+ ContentList the_content = content ();
+
boost::mutex::scoped_lock lm (_state_mutex);
+ LocaleGuard lg;
boost::filesystem::create_directories (directory());
- string const m = file ("metadata");
- ofstream f (m.c_str ());
- if (!f.good ()) {
- throw CreateFileError (m);
- }
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("Metadata");
- f << "version " << state_version << 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");
- /* 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;
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;
+ root->add_child("Format")->add_child_text (_format->id ());
}
- f << "scaler " << _scaler->id () << endl;
- f << "trim_start " << _trim_start << endl;
- f << "trim_end " << _trim_end << endl;
+
switch (_trim_type) {
case CPL:
- f << "trim_type cpl\n";
+ root->add_child("TrimType")->add_child_text ("CPL");
break;
case ENCODE:
- f << "trim_type encode\n";
- break;
- }
- 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("TrimType")->add_child_text ("Encode");
}
- 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;
}
Film::read_metadata ()
{
boost::mutex::scoped_lock lm (_state_mutex);
+ LocaleGuard lg;
- _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 DCP-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());
+ {
+ optional<string> c = f.optional_string_child ("DCPContentType");
+ if (c) {
+ _dcp_content_type = DCPContentType::from_dci_name (c.get ());
}
+ }
- /* 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 == "trim_type") {
- if (v == "cpl") {
- _trim_type = CPL;
- } else if (v == "encode") {
- _trim_type = ENCODE;
- }
- } 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 ("Format");
+ if (c) {
+ _format = Format::from_id (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 ("TrimType");
+ if (!c || c.get() == "CPL") {
+ _trim_type = CPL;
+ } else if (c && c.get() == "Encode") {
+ _trim_type = ENCODE;
}
}
- 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
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
*/
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
}
}
- switch (audio_channels()) {
+ switch (audio_channels ()) {
case 1:
d << "_10";
break;
}
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);
+ }
}
}
}
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)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _external_audio = 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)
+Film::set_ab (bool a)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _use_content_audio = e;
+ _ab = a;
}
-
- signal_changed (USE_CONTENT_AUDIO);
+ signal_changed (AB);
}
void
signal_changed (AUDIO_DELAY);
}
-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)
{
}
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);
}
- 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);
}
+
+ 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 s->channels ();
+ return _playlist->audio_channels ();
}
-void
-Film::set_dci_date_today ()
+int
+Film::audio_frame_rate () const
{
- _dci_date = boost::gregorian::day_clock::local_day ();
+ return _playlist->audio_frame_rate ();
}
-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));
+ }
+}
#include <iostream>
#include "format.h"
#include "film.h"
+#include "playlist.h"
#include "i18n.h"
s << _nickname << N_(" (");
}
- s << setprecision(3) << (_ratio / 100.0) << N_(":1");
+ s << setprecision(3) << _ratio << N_(":1");
if (!_nickname.empty ()) {
s << N_(")");
{
/// TRANSLATORS: these are film picture aspect ratios; "Academy" means 1.37, "Flat" 1.85 and "Scope" 2.39.
_formats.push_back (
- new FixedFormat (119, libdcp::Size (1285, 1080), N_("119"), _("1.19"), N_("F")
+ new FixedFormat (1.19, libdcp::Size (1285, 1080), N_("119"), _("1.19"), N_("F")
));
_formats.push_back (
- new FixedFormat (133, libdcp::Size (1436, 1080), N_("133"), _("1.33"), N_("F")
+ new FixedFormat (4.0 / 3.0, libdcp::Size (1436, 1080), N_("133"), _("4:3"), N_("F")
));
_formats.push_back (
- new FixedFormat (138, libdcp::Size (1485, 1080), N_("138"), _("1.375"), N_("F")
+ new FixedFormat (1.38, libdcp::Size (1485, 1080), N_("138"), _("1.375"), N_("F")
));
_formats.push_back (
- new FixedFormat (133, libdcp::Size (1998, 1080), N_("133-in-flat"), _("4:3 within Flat"), N_("F")
+ new FixedFormat (4.0 / 3.0, libdcp::Size (1998, 1080), N_("133-in-flat"), _("4:3 within Flat"), N_("F")
));
_formats.push_back (
- new FixedFormat (137, libdcp::Size (1480, 1080), N_("137"), _("Academy"), N_("F")
+ new FixedFormat (1.37, libdcp::Size (1480, 1080), N_("137"), _("Academy"), N_("F")
));
_formats.push_back (
- new FixedFormat (166, libdcp::Size (1793, 1080), N_("166"), _("1.66"), N_("F")
+ new FixedFormat (1.66, libdcp::Size (1793, 1080), N_("166"), _("1.66"), N_("F")
));
_formats.push_back (
- new FixedFormat (166, libdcp::Size (1998, 1080), N_("166-in-flat"), _("1.66 within Flat"), N_("F")
+ new FixedFormat (1.66, libdcp::Size (1998, 1080), N_("166-in-flat"), _("1.66 within Flat"), N_("F")
));
_formats.push_back (
- new FixedFormat (178, libdcp::Size (1998, 1080), N_("178-in-flat"), _("16:9 within Flat"), N_("F")
+ new FixedFormat (1.78, libdcp::Size (1998, 1080), N_("178-in-flat"), _("16:9 within Flat"), N_("F")
));
_formats.push_back (
- new FixedFormat (178, libdcp::Size (1920, 1080), N_("178"), _("16:9"), N_("F")
+ new FixedFormat (1.78, libdcp::Size (1920, 1080), N_("178"), _("16:9"), N_("F")
));
_formats.push_back (
- new FixedFormat (185, libdcp::Size (1998, 1080), N_("185"), _("Flat"), N_("F")
+ new FixedFormat (1.85, libdcp::Size (1998, 1080), N_("185"), _("Flat"), N_("F")
));
_formats.push_back (
- new FixedFormat (178, libdcp::Size (2048, 858), N_("178-in-scope"), _("16:9 within Scope"), N_("S")
+ new FixedFormat (1.78, libdcp::Size (2048, 858), N_("178-in-scope"), _("16:9 within Scope"), N_("S")
));
_formats.push_back (
- new FixedFormat (239, libdcp::Size (2048, 858), N_("239"), _("Scope"), N_("S")
+ new FixedFormat (2.39, libdcp::Size (2048, 858), N_("239"), _("Scope"), N_("S")
));
_formats.push_back (
return _formats;
}
- /** @param r Ratio multiplied by 100 (e.g. 185)
+ /** @param r Ratio
* @param dcp Size (in pixels) of the images that we should put in a DCP.
* @param id ID (e.g. 185)
* @param n Nick name (e.g. Flat)
*/
- FixedFormat::FixedFormat (int r, libdcp::Size dcp, string id, string n, string d)
+ FixedFormat::FixedFormat (float r, libdcp::Size dcp, string id, string n, string d)
: Format (dcp, id, n, d)
, _ratio (r)
{
int
Format::dcp_padding (shared_ptr<const Film> f) const
{
- int p = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_float(f))) / 2.0);
+ int p = rint ((_dcp_size.width - (_dcp_size.height * ratio(f))) / 2.0);
/* This comes out -ve for Scope; bodge it */
if (p < 0) {
}
float
- Format::container_ratio_as_float () const
+ Format::container_ratio () const
{
return static_cast<float> (_dcp_size.width) / _dcp_size.height;
}
}
- int
- VariableFormat::ratio_as_integer (shared_ptr<const Film> f) const
- {
- return rint (ratio_as_float (f) * 100);
- }
-
float
- VariableFormat::ratio_as_float (shared_ptr<const Film> f) const
+ VariableFormat::ratio (shared_ptr<const Film> f) const
{
- libdcp::Size const c = f->cropped_size (f->size ());
+ libdcp::Size const c = f->cropped_size (f->video_size ());
return float (c.width) / c.height;
}
, _dci_name (d)
{}
- /** @return the aspect ratio multiplied by 100
- * (e.g. 239 for Cinemascope 2.39:1)
- */
- virtual int ratio_as_integer (boost::shared_ptr<const Film> f) const = 0;
-
- /** @return the ratio as a floating point number */
- virtual float ratio_as_float (boost::shared_ptr<const Film> f) const = 0;
-
- /** @return the ratio of the container (including any padding) as a floating point number */
- float container_ratio_as_float () const;
+ /** @return the ratio of the container (including any padding) */
+ float container_ratio () 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
static void setup_formats ();
protected:
+ /** @return the ratio */
+ virtual float ratio (boost::shared_ptr<const Film> f) const = 0;
+
/** libdcp::Size in pixels of the images that we should
* put in a DCP for this ratio. This size will not correspond
* to the ratio when we are doing things like 16:9 in a Flat frame.
class FixedFormat : public Format
{
public:
- FixedFormat (int, libdcp::Size, std::string, std::string, std::string);
+ FixedFormat (float, libdcp::Size, std::string, std::string, std::string);
- int ratio_as_integer (boost::shared_ptr<const Film>) const {
+ float ratio (boost::shared_ptr<const Film>) const {
return _ratio;
}
- float ratio_as_float (boost::shared_ptr<const Film>) const {
- return _ratio / 100.0;
- }
-
std::string name () const;
private:
- /** Ratio expressed as the actual ratio multiplied by 100 */
- int _ratio;
+ float _ratio;
};
class VariableFormat : public Format
public:
VariableFormat (libdcp::Size, std::string, std::string, std::string);
- int ratio_as_integer (boost::shared_ptr<const Film> f) const;
- float ratio_as_float (boost::shared_ptr<const Film> f) const;
+ float ratio (boost::shared_ptr<const Film> f) const;
std::string name () const;
};
#include <iostream>
#include <boost/filesystem.hpp>
#include <Magick++.h>
+#include "imagemagick_content.h"
#include "imagemagick_decoder.h"
#include "image.h"
#include "film.h"
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;
+ }
- if (have_last_video ()) {
- repeat_last_video (double (_position) / 24);
- emit_video (_image, true, double (video_frame()) / frames_per_second());
++ if (_image) {
++ emit_video (_image, true, 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));
++ _image.reset (new SimpleImage (PIX_FMT_RGB24, size, false));
using namespace MagickCore;
-- uint8_t* p = image->data()[0];
++ uint8_t* p = _image->data()[0];
for (int y = 0; y < size.height; ++y) {
for (int x = 0; x < size.width; ++x) {
Magick::Color c = magick_image->pixelColor (x, y);
delete magick_image;
- image = image->crop (_film->crop(), true);
-
- emit_video (image, double (_position) / 24);
- _image = image->crop (_film->crop(), true);
-
- emit_video (_image, false, double (video_frame()) / frames_per_second());
++ _image = _image->crop (_film->crop(), true);
++ emit_video (_image, false, double (_position) / 24);
- ++_iter;
+ ++_position;
return false;
}
return PIX_FMT_RGB24;
}
-bool
-ImageMagickDecoder::seek_to_last ()
-{
- if (_iter == _files.end()) {
- _iter = _files.begin();
- } else {
- --_iter;
- }
-
- return false;
-}
-
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;
}
-}
-float
-ImageMagickDecoder::frames_per_second () const
-{
- return _film->source_frame_rate ();
+ _position = f;
+ return false;
}
class Image;
}
+class ImageMagickContent;
+
class ImageMagickDecoder : public VideoDecoder
{
public:
- ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
- float frames_per_second () const;
+ ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
- libdcp::Size native_size () const;
-
- SourceFrame length () const {
- /* We don't know */
- return 0;
+ float video_frame_rate () const {
+ return 24;
}
- int audio_channels () const {
- return 0;
- }
-
- int audio_sample_rate () const {
- return 0;
- }
-
- int64_t audio_channel_layout () const {
- return 0;
- }
+ libdcp::Size native_size () const;
+ ContentVideoFrame video_length () const;
bool seek (double);
- bool seek_to_last ();
+ bool pass ();
protected:
- bool pass ();
PixelFormat pixel_format () const;
int time_base_numerator () const {
}
private:
- void film_changed (Film::Property);
-
- std::list<std::string> _files;
- std::list<std::string>::iterator _iter;
-
+ boost::shared_ptr<const ImageMagickContent> _imagemagick_content;
+ boost::shared_ptr<Image> _image;
+ ContentVideoFrame _position;
};
#include "i18n.h"
using std::min;
+ using std::cout;
+ using std::list;
using boost::shared_ptr;
Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
- : AudioVideoProcessor (log)
+ : Processor (log)
, _sample_rate (sample_rate)
, _frames_per_second (frames_per_second)
, _video_frames (0)
, _audio_frames (0)
+ , _had_first_video (false)
+ , _had_first_audio (false)
{
}
void
- Matcher::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
-Matcher::process_video (boost::shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
++Matcher::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
{
- Video (i, same, s);
- _video_frames++;
+ _pixel_format = image->pixel_format ();
+ _size = image->size ();
- _pixel_format = i->pixel_format ();
- _size = i->size ();
+ _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size()));
+
+ if (!_first_input) {
+ _first_input = t;
+ }
+
+ bool const this_is_first_video = !_had_first_video;
+ _had_first_video = true;
+
+ if (this_is_first_video && _had_first_audio) {
+ /* First video since we got audio */
+ fix_start (t);
+ }
+
+ /* Video before audio is fine, since we can make up an arbitrary difference
+ with audio samples (contrasting with video which is quantised to frames)
+ */
+
+ /* Difference between where this video is and where it should be */
+ double const delta = t - _first_input.get() - _video_frames / _frames_per_second;
+ double const one_frame = 1 / _frames_per_second;
+
+ if (delta > one_frame) {
+ /* Insert frames to make up the difference */
+ int const extra = rint (delta / one_frame);
+ for (int i = 0; i < extra; ++i) {
+ repeat_last_video ();
+ _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second));
+ }
+ }
+
+ if (delta > -one_frame) {
+ Video (image, same, sub);
+ ++_video_frames;
+ } else {
+ /* We are omitting a frame to keep things right */
+ _log->log (String::compose ("Frame removed at %1s", t));
+ }
+
+ _last_image = image;
+ _last_subtitle = sub;
}
void
- Matcher::process_audio (shared_ptr<AudioBuffers> b)
-Matcher::process_audio (boost::shared_ptr<AudioBuffers> b, double t)
++Matcher::process_audio (shared_ptr<AudioBuffers> b, double t)
{
- Audio (b);
- _audio_frames += b->frames ();
-
_channels = b->channels ();
+
+ _log->log (String::compose ("Matcher audio @ %1 [video=%2, audio=%3, pending_audio=%4]", t, _video_frames, _audio_frames, _pending_audio.size()));
+
+ if (!_first_input) {
+ _first_input = t;
+ }
+
+ bool const this_is_first_audio = _had_first_audio;
+ _had_first_audio = true;
+
+ if (!_had_first_video) {
+ /* No video yet; we must postpone these data until we have some */
+ _pending_audio.push_back (AudioRecord (b, t));
+ } else if (this_is_first_audio && !_had_first_video) {
+ /* First audio since we got video */
+ _pending_audio.push_back (AudioRecord (b, t));
+ fix_start (_first_input.get ());
+ } else {
+ /* Normal running. We assume audio time stamps are consecutive */
+ Audio (b);
+ _audio_frames += b->frames ();
+ }
}
void
/* We won't do anything */
return;
}
+
+ _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames",
+ _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames));
- int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
-
- _log->log (
- String::compose (
- N_("Matching processor has seen %1 video frames (which equals %2 audio frames) and %3 audio frames"),
- _video_frames,
- video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second),
- _audio_frames
- )
- );
+ match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second));
+ }
+
+ void
+ Matcher::fix_start (double first_video)
+ {
+ assert (!_pending_audio.empty ());
+
+ _log->log (String::compose ("Fixing start; video at %1, audio at %2", first_video, _pending_audio.front().time));
+
+ match (first_video - _pending_audio.front().time);
+
+ for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
+ process_audio (i->audio, i->time);
+ }
- if (audio_short_by_frames < 0) {
-
- _log->log (String::compose (N_("%1 too many audio frames"), -audio_short_by_frames));
-
- /* We have seen more audio than video. Emit enough black video frames so that we reverse this */
- int const black_video_frames = ceil (-audio_short_by_frames * _frames_per_second / _sample_rate);
+ _pending_audio.clear ();
+ }
+
+ void
+ Matcher::match (double extra_video_needed)
+ {
+ _log->log (String::compose ("Match %1", extra_video_needed));
+
+ if (extra_video_needed > 0) {
+
+ /* Emit black video frames */
+ int const black_video_frames = ceil (extra_video_needed * _frames_per_second);
+
_log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames));
shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
black->make_black ();
for (int i = 0; i < black_video_frames; ++i) {
Video (black, i != 0, shared_ptr<Subtitle>());
+ ++_video_frames;
}
-
- /* Now recompute our check value */
- audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
+
+ extra_video_needed -= black_video_frames / _frames_per_second;
}
-
- if (audio_short_by_frames > 0) {
- _log->log (String::compose (N_("Emitted %1 too few audio frames"), audio_short_by_frames));
+
+ if (extra_video_needed < 0) {
+
+ /* Emit silence */
+
+ int64_t to_do = -extra_video_needed * _sample_rate;
+ _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do));
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
b->make_silent ();
- int64_t to_do = audio_short_by_frames;
while (to_do > 0) {
int64_t const this_time = min (to_do, block);
b->set_frames (this_time);
}
}
}
+
+ void
+ Matcher::repeat_last_video ()
+ {
+ if (!_last_image) {
+ _last_image.reset (new SimpleImage (_pixel_format.get(), _size.get(), true));
+ _last_image->make_black ();
+ }
+
+ Video (_last_image, true, _last_subtitle);
+ ++_video_frames;
+ }
+
--- /dev/null
- , _video_sync (true)
+/*
+ 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 "ffmpeg_content.h"
+#include "imagemagick_decoder.h"
+#include "imagemagick_content.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)
- Audio (_audio_buffers);
+{
+ _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 (_video_decoder != _video_decoders.end ()) {
+ if ((*_video_decoder)->pass ()) {
+ _video_decoder++;
+ }
+
+ if (_video_decoder != _video_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;
+ }
+ }
+
- Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
++ Audio (_audio_buffers, _audio_time.get());
+ _audio_buffers.reset ();
++ _audio_time = boost::none;
+ }
+
+ return done;
+}
+
+void
+Player::set_progress (shared_ptr<Job> job)
+{
+ /* Assume progress can be divined from how far through the video we are */
+
+ if (_video_decoder == _video_decoders.end() || !_playlist->video_length()) {
+ return;
+ }
+
+ ContentVideoFrame p = 0;
+ list<shared_ptr<VideoDecoder> >::iterator i = _video_decoders.begin ();
+ while (i != _video_decoders.end() && i != _video_decoder) {
+ p += (*i)->video_length ();
+ }
+
+ job->set_progress (float ((*_video_decoder)->video_frame ()) / _playlist->video_length ());
+}
+
+void
- Video (i, same, s);
++Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s, double t)
+{
- Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b)
++ /* XXX: this time will need mangling to add on the offset of the start of the content */
++ Video (i, same, s, t);
+}
+
+void
- Audio (_audio_buffers);
++Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b, double t)
+{
++ /* XXX: this time will need mangling to add on the offset of the start of the content */
+ AudioMapping mapping = _film->audio_mapping ();
+ if (!_audio_buffers) {
+ _audio_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ()));
+ _audio_buffers->make_silent ();
++ _audio_time = t;
+ }
+
+ 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) {
+ _audio_buffers->accumulate (b, i, static_cast<int> (*j));
+ }
+ }
+
+ if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
+ /* We can just emit this audio now as it will all be here */
- _subtitles,
- _video_sync
++ Audio (_audio_buffers, t);
+ _audio_buffers.reset ();
++ _audio_time = boost::none;
+ }
+}
+
+/** @return true on error */
+bool
+Player::seek (double t)
+{
+ if (!_have_valid_decoders) {
+ setup_decoders ();
+ _have_valid_decoders = true;
+ }
+
+ /* Find the decoder that contains this position */
+ _video_decoder = _video_decoders.begin ();
+ while (_video_decoder != _video_decoders.end ()) {
+ double const this_length = double ((*_video_decoder)->video_length()) / _film->video_frame_rate ();
+ if (t < this_length) {
+ break;
+ }
+ t -= this_length;
+ ++_video_decoder;
+ }
+
+ if (_video_decoder != _video_decoders.end()) {
+ (*_video_decoder)->seek (t);
+ } else {
+ return true;
+ }
+
+ /* XXX: don't seek audio because we don't need to... */
+
+ return false;
+}
+
++
++void
++Player::seek_back ()
++{
++ /* XXX */
++}
++
++void
++Player::seek_forward ()
++{
++ /* XXX */
++}
++
++
+void
+Player::setup_decoders ()
+{
+ _video_decoders.clear ();
+ _video_decoder = _video_decoders.end ();
+ _sndfile_decoders.clear ();
+
+ if (_video) {
+ list<shared_ptr<const VideoContent> > vc = _playlist->video ();
+ for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) {
+
+ shared_ptr<VideoDecoder> d;
+
+ /* XXX: into content? */
+
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+ if (fc) {
+ shared_ptr<FFmpegDecoder> fd (
+ new FFmpegDecoder (
+ _film, fc, _video,
+ _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
- fd->Audio.connect (bind (&Player::process_audio, this, fc, _1));
++ _subtitles
+ )
+ );
+
+ if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
- d->Audio.connect (bind (&Player::process_audio, this, *i, _1));
++ fd->Audio.connect (bind (&Player::process_audio, this, fc, _1, _2));
+ }
+
+ d = fd;
+ }
+
+ shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
+ if (ic) {
+ d.reset (new ImageMagickDecoder (_film, ic));
+ }
+
+ d->connect_video (shared_from_this ());
+ _video_decoders.push_back (d);
+ }
+
+ _video_decoder = _video_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);
- void
- Player::disable_video_sync ()
- {
- _video_sync = false;
- }
-
++ d->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
+ }
+ }
+}
+
+double
+Player::last_video_time () const
+{
+ double t = 0;
+ for (list<shared_ptr<VideoDecoder> >::const_iterator i = _video_decoders.begin(); i != _video_decoder; ++i) {
+ t += (*i)->video_length() / (*i)->video_frame_rate ();
+ }
+
+ return t + (*_video_decoder)->last_content_time ();
+}
+
+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)) {
+ /* FFmpeg content length changes are serious; we need new decoders */
+ _have_valid_decoders = false;
+ }
+ }
+}
+
+void
+Player::playlist_changed ()
+{
+ _have_valid_decoders = false;
+}
--- /dev/null
- class Player : public VideoSource, public AudioSource, public VideoSink, public boost::enable_shared_from_this<Player>
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_PLAYER_H
+#define DCPOMATIC_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 VideoDecoder;
+class SndfileDecoder;
+class Job;
+class Film;
+class Playlist;
+class AudioContent;
+
- void disable_video_sync ();
++class Player : public TimedVideoSource, public TimedAudioSource, public TimedVideoSink, 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 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>);
+
+ bool pass ();
+ void set_progress (boost::shared_ptr<Job>);
+ bool seek (double);
++ void seek_back ();
++ void seek_forward ();
+
+ double last_video_time () const;
+
+private:
-
- bool _video_sync;
++ void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double);
++ void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<AudioBuffers>, double);
+ 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;
+ std::list<boost::shared_ptr<VideoDecoder> > _video_decoders;
+ std::list<boost::shared_ptr<VideoDecoder> >::iterator _video_decoder;
+ std::list<boost::shared_ptr<SndfileDecoder> > _sndfile_decoders;
+
+ boost::shared_ptr<AudioBuffers> _audio_buffers;
++ boost::optional<double> _audio_time;
+};
+
+#endif
* @brief Parent class for classes which accept and then emit video or audio data.
*/
-#ifndef DVDOMATIC_PROCESSOR_H
-#define DVDOMATIC_PROCESSOR_H
+#ifndef DCPOMATIC_PROCESSOR_H
+#define DCPOMATIC_PROCESSOR_H
#include "video_source.h"
#include "video_sink.h"
{}
};
+ class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink
+ {
+ public:
+ TimedAudioVideoProcessor (boost::shared_ptr<Log> log)
+ : Processor (log)
+ {}
+ };
+
+
/** @class AudioProcessor
* @brief A processor which handles just audio data.
*/
{}
};
+ class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink
+ {
+ public:
+ TimedVideoProcessor (boost::shared_ptr<Log> log)
+ : Processor (log)
+ {}
+ };
+
#endif
#include <iostream>
#include <sndfile.h>
+#include "sndfile_content.h"
#include "sndfile_decoder.h"
#include "film.h"
#include "exceptions.h"
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);
++ _done = 0;
+ _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));
- sf_count_t done = 0;
- 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, double(done) / _audio_stream->sample_rate());
- done += this_time;
- 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> ();
- }
-
- return shared_ptr<SndfileStream> (new SndfileStream (t, v));
+ 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);
++ Audio (audio, double(_done) / audio_frame_rate());
++ _done += this_time;
+ _remaining -= this_time;
+
+ 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;
}
#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 _done;
+ ContentAudioFrame _remaining;
};
#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;
/** 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))
{
- 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()));
- }
- assert (_encoder);
-
- 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(), f->audio_delay() / 1000.0f));
++ _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate()));
++ _delay_line.reset (new DelayLine (f->log(), 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 ());
- _decoders.audio->set_audio_stream (f->audio_stream ());
+ if (!f->with_subtitles ()) {
+ _player->disable_subtitles ();
+ }
- if (_matcher) {
- _player->connect_video (_matcher);
- _matcher->connect_video (_encoder);
- } else {
- _player->connect_video (_encoder);
- }
- _decoders.video->connect_video (_delay_line);
++ _player->connect_video (_delay_line);
+ _delay_line->connect_video (_matcher);
+ _matcher->connect_video (_encoder);
- 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);
- }
- _decoders.audio->connect_audio (_delay_line);
++ _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]) {
- break;
- }
+ while (1) {
+ if (_player->pass ()) {
+ break;
}
-
- } catch (...) {
- _encoder->process_end ();
- throw;
+ _player->set_progress (_job);
}
-
+
- if (_delay_line) {
- _delay_line->process_end ();
- }
- if (_matcher) {
- _matcher->process_end ();
- }
- if (_gain) {
- _gain->process_end ();
- }
+ _delay_line->process_end ();
+ _matcher->process_end ();
+ _gain->process_end ();
_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 ();
+}
#include <iostream>
#include <fstream>
#include <climits>
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
#endif
#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;
return ap.str ();
}
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
/** @param l Mangled C++ identifier.
* @return Demangled version.
*/
return t.tv_sec + (double (t.tv_usec) / 1e6);
}
-/** Call the required functions to set up DVD-o-matic's static arrays, etc.
+/** Call the required functions to set up DCP-o-matic's static arrays, etc.
* Must be called from the UI thread, if there is one.
*/
void
-dvdomatic_setup ()
+dcpomatic_setup ()
{
avfilter_register_all ();
Filter::setup_filters ();
SoundProcessor::setup_sound_processors ();
- ui_thread = this_thread::get_id ();
+ ui_thread = boost::this_thread::get_id ();
}
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
boost::filesystem::path
mo_path ()
{
#endif
void
-dvdomatic_setup_i18n (string lang)
+dcpomatic_setup_i18n (string lang)
{
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
lang += ".UTF8";
#endif
}
setlocale (LC_ALL, "");
- textdomain ("libdvdomatic");
+ textdomain ("libdcpomatic");
-#ifdef DVDOMATIC_WINDOWS
- bindtextdomain ("libdvdomatic", mo_path().string().c_str());
- bind_textdomain_codeset ("libdvdomatic", "UTF8");
+#ifdef DCPOMATIC_WINDOWS
+ bindtextdomain ("libdcpomatic", mo_path().string().c_str());
+ bind_textdomain_codeset ("libdcpomatic", "UTF8");
#endif
-#ifdef DVDOMATIC_POSIX
- bindtextdomain ("libdvdomatic", POSIX_LOCALE_PREFIX);
+#ifdef DCPOMATIC_POSIX
+ bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
#endif
}
* @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);
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.
*/
, _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));
* @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"));
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 ());
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 ());
}
}
+/** 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 ()
pair<string, int> info;
info.second = 0;
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
ifstream f (N_("/proc/cpuinfo"));
while (f.good ()) {
string l;
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)
}
}
}
+
+ LocaleGuard::LocaleGuard ()
+ : _old (0)
+ {
+ char const * old = setlocale (LC_NUMERIC, 0);
+
+ if (old) {
+ _old = strdup (old);
+ if (strcmp (_old, "POSIX")) {
+ setlocale (LC_NUMERIC, "POSIX");
+ }
+ }
+ }
+
+ LocaleGuard::~LocaleGuard ()
+ {
+ setlocale (LC_NUMERIC, _old);
+ free (_old);
+ }
* @brief Some utility functions and classes.
*/
-#ifndef DVDOMATIC_UTIL_H
-#define DVDOMATIC_UTIL_H
+#ifndef DCPOMATIC_UTIL_H
+#define DCPOMATIC_UTIL_H
#include <string>
#include <vector>
#include <libavfilter/avfilter.h>
}
#include "compose.hpp"
+#include "types.h"
-#ifdef DVDOMATIC_DEBUG
+#ifdef DCPOMATIC_DEBUG
#define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
#else
#define TIMING(...)
extern void stacktrace (std::ostream &, int);
extern std::string dependency_version_summary ();
extern double seconds (struct timeval);
-extern void dvdomatic_setup ();
-extern void dvdomatic_setup_i18n (std::string);
+extern void dcpomatic_setup ();
+extern void dcpomatic_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);
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
extern boost::filesystem::path mo_path ();
#endif
-typedef int SourceFrame;
-
struct FrameRateConversion
{
FrameRateConversion (float, int);
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);
/** @class Socket
* @brief A class to wrap a boost::asio::ip::tcp::socket with some things
- * that are useful for DVD-o-matic.
+ * that are useful for DCP-o-matic.
*
* This class wraps some things that I could not work out how to do with boost;
* most notably, sync read/write calls with timeouts.
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 */
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 ();
+ class LocaleGuard
+ {
+ public:
+ LocaleGuard ();
+ ~LocaleGuard ();
+
+ private:
+ char* _old;
+ };
+
+
#endif
#include "film.h"
#include "image.h"
#include "log.h"
-#include "options.h"
#include "job.h"
#include "i18n.h"
+ using std::cout;
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)
+ , _last_content_time (0)
{
}
* @param t Time of the frame within the source, in seconds.
*/
void
- VideoDecoder::emit_video (shared_ptr<Image> image, double t)
+ VideoDecoder::emit_video (shared_ptr<Image> image, bool same, double t)
{
shared_ptr<Subtitle> sub;
if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
sub = _timed_subtitle->subtitle ();
}
- 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
- * passed to emit_video(). If emit_video hasn't yet been called,
- * we will generate a black frame.
- */
- void
- 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, t);
- }
-
- /** Emit our signal to say that some video data is ready.
- * @param image Video frame.
- * @param same true if `image' is the same as the last one we emitted.
- * @param sub Subtitle for this frame, or 0.
- */
- void
- 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);
+ Video (image, same, sub, t);
++_video_frame;
-
- _last_source_time = t;
+
- _last_image = image;
- _last_subtitle = sub;
+ _last_content_time = t;
}
/** Set up the current subtitle. This will be put onto frames that
}
}
-/** 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());
}
}
*/
-#ifndef DVDOMATIC_VIDEO_DECODER_H
-#define DVDOMATIC_VIDEO_DECODER_H
+#ifndef DCPOMATIC_VIDEO_DECODER_H
+#define DCPOMATIC_VIDEO_DECODER_H
#include "video_source.h"
-#include "stream.h"
#include "decoder.h"
- class VideoDecoder : public VideoSource, public virtual Decoder
+class VideoContent;
+
+ class VideoDecoder : public TimedVideoSource, 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;
+ double last_content_time () const {
+ return _last_content_time;
}
protected:
virtual PixelFormat pixel_format () const = 0;
- void emit_video (boost::shared_ptr<Image>, double);
+ void emit_video (boost::shared_ptr<Image>, bool, double);
void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
- bool have_last_video () const;
- void repeat_last_video (double);
- /** 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;
-
private:
- void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
-
int _video_frame;
- double _last_source_time;
+ double _last_content_time;
boost::shared_ptr<TimedSubtitle> _timed_subtitle;
-
- boost::shared_ptr<Image> _last_image;
- boost::shared_ptr<Subtitle> _last_subtitle;
};
#endif
*/
-#ifndef DVDOMATIC_VIDEO_SINK_H
-#define DVDOMATIC_VIDEO_SINK_H
+#ifndef DCPOMATIC_VIDEO_SINK_H
+#define DCPOMATIC_VIDEO_SINK_H
#include <boost/shared_ptr.hpp>
#include "util.h"
virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0;
};
+ class TimedVideoSink
+ {
+ public:
+ /** Call with a frame of video.
+ * @param i Video frame image.
+ * @param same true if i is the same as last time we were called.
+ * @param s A subtitle that should be on this frame, or 0.
+ * @param t Source timestamp.
+ */
+ virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double t) = 0;
+ };
+
#endif
#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));
}
+
+ void
+ TimedVideoSource::connect_video (shared_ptr<TimedVideoSink> s)
+ {
+ Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4));
+ }
* @brief Parent class for classes which emit video data.
*/
-#ifndef DVDOMATIC_VIDEO_SOURCE_H
-#define DVDOMATIC_VIDEO_SOURCE_H
+#ifndef DCPOMATIC_VIDEO_SOURCE_H
+#define DCPOMATIC_VIDEO_SOURCE_H
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include "util.h"
class VideoSink;
+ class TimedVideoSink;
class Subtitle;
class Image;
/** @class VideoSource
- * @param A class that emits video data.
+ * @param A class that emits video data without timestamps.
*/
class VideoSource
{
void connect_video (boost::shared_ptr<VideoSink>);
};
+ /** @class TimedVideoSource
+ * @param A class that emits video data with timestamps.
+ */
+ class TimedVideoSource
+ {
+ public:
+
+ /** Emitted when a video frame is ready.
+ * First parameter is the video image.
+ * Second parameter is true if the image is the same as the last one that was emitted.
+ * Third parameter is either 0 or a subtitle that should be on this frame.
+ * Fourth parameter is the source timestamp of this frame.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double)> Video;
+
+ void connect_video (boost::shared_ptr<TimedVideoSink>);
+ };
+
#endif
#include "lib/format.h"
#include "lib/util.h"
#include "lib/job_manager.h"
-#include "lib/options.h"
#include "lib/subtitle.h"
#include "lib/image.h"
#include "lib/scaler.h"
#include "lib/exceptions.h"
#include "lib/examine_content_job.h"
#include "lib/filter.h"
+#include "lib/player.h"
+#include "lib/video_content.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/imagemagick_content.h"
#include "film_viewer.h"
#include "wx_util.h"
#include "video_decoder.h"
using std::cout;
using std::list;
using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::weak_ptr;
using libdcp::Size;
FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
: wxPanel (p)
, _panel (new wxPanel (this))
, _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
+ , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
+ , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
+ , _frame (new wxStaticText (this, wxID_ANY, wxT("")))
+ , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
, _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
, _display_frame_x (0)
, _got_frame (false)
_v_sizer->Add (_panel, 1, wxEXPAND);
wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
+
+ wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
+ time_sizer->Add (_frame, 0, wxEXPAND);
+ time_sizer->Add (_timecode, 0, wxEXPAND);
+
+ h_sizer->Add (_back_button, 0, wxALL, 2);
+ h_sizer->Add (time_sizer, 0, wxEXPAND);
+ h_sizer->Add (_forward_button, 0, wxALL, 2);
h_sizer->Add (_play_button, 0, wxEXPAND);
h_sizer->Add (_slider, 1, wxEXPAND);
_v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
+ _frame->SetMinSize (wxSize (84, -1));
+ _back_button->SetMinSize (wxSize (32, -1));
+ _forward_button->SetMinSize (wxSize (32, -1));
+
_panel->Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (FilmViewer::paint_panel), 0, this);
_panel->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (FilmViewer::panel_sized), 0, this);
_slider->Connect (wxID_ANY, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
_slider->Connect (wxID_ANY, wxEVT_SCROLL_PAGEDOWN, wxScrollEventHandler (FilmViewer::slider_moved), 0, this);
_play_button->Connect (wxID_ANY, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler (FilmViewer::play_clicked), 0, this);
_timer.Connect (wxID_ANY, wxEVT_TIMER, wxTimerEventHandler (FilmViewer::timer), 0, this);
+ _back_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmViewer::back_clicked), 0, this);
+ _forward_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmViewer::forward_clicked), 0, this);
set_film (f);
update_from_raw ();
break;
case Film::CONTENT:
-- {
- DecodeOptions o;
- o.decode_audio = false;
- o.decode_subtitles = true;
- o.video_sync = false;
-
- try {
- _decoders = decoder_factory (_film, o);
- } catch (StringError& e) {
- error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data()));
- return;
- }
-
- if (_decoders.video == 0) {
- break;
- }
- _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
- _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
- _decoders.video->set_subtitle_stream (_film->subtitle_stream());
calculate_sizes ();
get_frame ();
_panel->Refresh ();
- _slider->Show (_film->content_type() == VIDEO);
- _play_button->Show (_film->content_type() == VIDEO);
_v_sizer->Layout ();
break;
-- }
case Film::WITH_SUBTITLES:
case Film::SUBTITLE_OFFSET:
case Film::SUBTITLE_SCALE:
+ raw_to_display ();
+ _panel->Refresh ();
+ _panel->Update ();
+ break;
case Film::SCALER:
case Film::FILTERS:
- update_from_raw ();
- break;
- case Film::SUBTITLE_STREAM:
- if (_decoders.video) {
- _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
- }
+ case Film::CROP:
+ update_from_decoder ();
break;
default:
break;
if (_film == f) {
return;
}
-
+
_film = f;
_raw_frame.reset ();
return;
}
- _player->disable_video_sync ();
+ _player = f->player ();
+ _player->disable_audio ();
- _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
+ /* Don't disable subtitles here as we may need them, and it's nice to be able to turn them
+ on and off without needing obtain a new Player.
+ */
+
++ _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
+
_film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
+ _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
film_changed (Film::CONTENT);
film_changed (Film::FORMAT);
film_changed (Film::WITH_SUBTITLES);
film_changed (Film::SUBTITLE_OFFSET);
film_changed (Film::SUBTITLE_SCALE);
- film_changed (Film::SUBTITLE_STREAM);
}
void
-FilmViewer::decoder_changed ()
+FilmViewer::update_from_decoder ()
{
- if (_decoders.video == 0 || _decoders.video->seek_to_last ()) {
+ if (!_player || _player->seek (_player->last_video_time ())) {
return;
}
void
FilmViewer::timer (wxTimerEvent &)
{
- if (!_film || !_decoders.video) {
+ if (!_player) {
return;
}
get_frame ();
- if (_film->length()) {
- int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate());
+ if (_film->video_length()) {
+ int const new_slider_position = 4096 * _player->last_video_time() / (_film->video_length() / _film->video_frame_rate());
if (new_slider_position != _slider->GetValue()) {
_slider->SetValue (new_slider_position);
}
void
FilmViewer::slider_moved (wxScrollEvent &)
{
- if (!_film || !_film->length() || !_decoders.video) {
+ if (!_film || !_player) {
return;
}
-
- if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) {
+
+ if (_player->seek (_slider->GetValue() * _film->video_length() / (4096 * _film->video_frame_rate()))) {
return;
}
return;
}
- boost::shared_ptr<Image> input = _raw_frame;
+ shared_ptr<Image> input = _raw_frame;
pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
if (!s.second.empty ()) {
when working out the scale that we are applying.
*/
- Size const cropped_size = _film->cropped_size (_film->size ());
+ Size const cropped_size = _film->cropped_size (_film->video_size ());
Rect tx = subtitle_transformed_area (
float (_film_size.width) / cropped_size.width,
void
FilmViewer::calculate_sizes ()
{
- if (!_film) {
+ if (!_film || !_player) {
return;
}
Format const * format = _film->format ();
float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
- float const film_ratio = format ? format->container_ratio_as_float () : 1.78;
+ float const film_ratio = format ? format->container_ratio () : 1.78;
if (panel_ratio < film_ratio) {
/* panel is less widscreen than the film; clamp width */
}
if (_play_button->GetValue()) {
- _timer.Start (1000 / _film->source_frame_rate());
+ _timer.Start (1000 / _film->video_frame_rate());
} else {
_timer.Stop ();
}
}
void
- FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub)
+ FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub, double t)
{
_raw_frame = image;
_raw_sub = sub;
raw_to_display ();
_got_frame = true;
- double const fps = _decoders.video->frames_per_second ();
+
++ double const fps = _film->video_frame_rate ();
+ _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps))));
+
+ double w = t;
+ int const h = (w / 3600);
+ w -= h * 3600;
+ int const m = (w / 60);
+ w -= m * 60;
+ int const s = floor (w);
+ w -= s;
+ int const f = rint (w * fps);
+ _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f));
}
+/** Get a new _raw_frame from the decoder and then do
+ * raw_to_display ().
+ */
void
FilmViewer::get_frame ()
{
/* Clear our raw frame in case we don't get a new one */
_raw_frame.reset ();
- if (_decoders.video == 0) {
+ if (!_player) {
_display_frame.reset ();
return;
}
-
+
try {
_got_frame = false;
while (!_got_frame) {
- if (_decoders.video->pass ()) {
+ if (_player->pass ()) {
/* We didn't get a frame before the decoder gave up,
so clear our display frame.
*/
_play_button->Enable (!a);
}
- if (!_decoders.video) {
+void
+FilmViewer::film_content_changed (weak_ptr<Content>, int p)
+{
+ if (p == VideoContentProperty::VIDEO_LENGTH) {
+ /* Force an update to our frame */
+ wxScrollEvent ev;
+ slider_moved (ev);
+ }
+}
++
+ void
+ FilmViewer::back_clicked (wxCommandEvent &)
+ {
- _decoders.video->seek_back ();
++ if (!_player) {
+ return;
+ }
+
- if (!_decoders.video) {
++ _player->seek_back ();
+ get_frame ();
+ _panel->Refresh ();
+ _panel->Update ();
+ }
+
+ void
+ FilmViewer::forward_clicked (wxCommandEvent &)
+ {
- _decoders.video->seek_forward ();
++ if (!_player) {
+ return;
+ }
+
++ _player->seek_forward ();
+ get_frame ();
+ _panel->Refresh ();
+ _panel->Update ();
+ }
#include <wx/wx.h>
#include "lib/film.h"
-#include "lib/decoder_factory.h"
class wxToggleButton;
class FFmpegPlayer;
/** @class FilmViewer
* @brief A wx widget to view a preview of a Film.
+ *
+ * The film takes the following path through the viewer:
+ *
+ * 1. get_frame() asks our _player to decode some data. If it does, process_video()
+ * will be called.
+ *
+ * 2. process_video() takes the image and subtitle from the decoder (_raw_frame and _raw_sub)
+ * and calls raw_to_display().
+ *
+ * 3. raw_to_display() copies _raw_frame to _display_frame, processing it and scaling it.
+ *
+ * 4. calling _panel->Refresh() and _panel->Update() results in paint_panel() being called;
+ * this creates frame_bitmap from _display_frame and blits it to the display. It also
+ * blits the subtitle, if required.
+ *
+ * update_from_decoder() asks the player to re-emit its current frame on the next pass(), and then
+ * starts from step #1.
+ *
+ * update_from_raw() starts at step #3, then calls _panel->Refresh and _panel->Update.
*/
class FilmViewer : public wxPanel
{
private:
void film_changed (Film::Property);
+ void film_content_changed (boost::weak_ptr<Content>, int);
void paint_panel (wxPaintEvent &);
void panel_sized (wxSizeEvent &);
void slider_moved (wxScrollEvent &);
void play_clicked (wxCommandEvent &);
void timer (wxTimerEvent &);
- void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
+ void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
void calculate_sizes ();
void check_play_state ();
void update_from_raw ();
- void decoder_changed ();
+ void update_from_decoder ();
void raw_to_display ();
void get_frame ();
void active_jobs_changed (bool);
+ void back_clicked (wxCommandEvent &);
+ void forward_clicked (wxCommandEvent &);
boost::shared_ptr<Film> _film;
+ boost::shared_ptr<Player> _player;
wxSizer* _v_sizer;
wxPanel* _panel;
wxSlider* _slider;
+ wxButton* _back_button;
+ wxButton* _forward_button;
+ wxStaticText* _frame;
+ wxStaticText* _timecode;
wxToggleButton* _play_button;
wxTimer _timer;
- Decoders _decoders;
boost::shared_ptr<Image> _raw_frame;
boost::shared_ptr<Subtitle> _raw_sub;
boost::shared_ptr<Image> _display_frame;
#include "job_manager.h"
#include "util.h"
#include "exceptions.h"
- #include "delay_line.h"
#include "image.h"
#include "log.h"
#include "dcp_video_frame.h"
#include "scaler.h"
#include "ffmpeg_decoder.h"
#include "sndfile_decoder.h"
+#include "dcp_content_type.h"
#include "trimmer.h"
#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_MODULE dvdomatic_test
+#define BOOST_TEST_MODULE dcpomatic_test
#include <boost/test/unit_test.hpp>
using std::string;
BOOST_AUTO_TEST_CASE (make_black_test)
{
/* This needs to happen in the first test */
- dvdomatic_setup ();
+ dcpomatic_setup ();
libdcp::Size in_size (512, 512);
libdcp::Size out_size (1024, 1024);
BOOST_CHECK (f->filters ().empty());
f->set_name ("fred");
- BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
+// BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
f->set_format (Format::from_nickname ("Flat"));
f->set_left_crop (1);
f->set_filters (f_filters);
f->set_trim_start (42);
f->set_trim_end (99);
- f->set_dcp_ab (true);
+ f->set_ab (true);
f->write_metadata ();
stringstream s;
BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
BOOST_CHECK_EQUAL (g->trim_start(), 42);
BOOST_CHECK_EQUAL (g->trim_end(), 99);
- BOOST_CHECK_EQUAL (g->dcp_ab(), true);
+ BOOST_CHECK_EQUAL (g->ab(), true);
g->write_metadata ();
BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
}
-BOOST_AUTO_TEST_CASE (stream_test)
-{
- FFmpegAudioStream a ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
- BOOST_CHECK_EQUAL (a.id(), 4);
- BOOST_CHECK_EQUAL (a.sample_rate(), 44100);
- BOOST_CHECK_EQUAL (a.channel_layout(), 1);
- BOOST_CHECK_EQUAL (a.name(), "hello there world");
- BOOST_CHECK_EQUAL (a.to_string(), "ffmpeg 4 44100 1 hello there world");
-
- SndfileStream e ("external 44100 1", boost::optional<int> (1));
- BOOST_CHECK_EQUAL (e.sample_rate(), 44100);
- BOOST_CHECK_EQUAL (e.channel_layout(), 1);
- BOOST_CHECK_EQUAL (e.to_string(), "external 44100 1");
-
- SubtitleStream s ("5 a b c", boost::optional<int> (1));
- BOOST_CHECK_EQUAL (s.id(), 5);
- BOOST_CHECK_EQUAL (s.name(), "a b c");
-
- shared_ptr<AudioStream> ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
- shared_ptr<FFmpegAudioStream> cff = dynamic_pointer_cast<FFmpegAudioStream> (ff);
- BOOST_CHECK (cff);
- BOOST_CHECK_EQUAL (cff->id(), 4);
- BOOST_CHECK_EQUAL (cff->sample_rate(), 44100);
- BOOST_CHECK_EQUAL (cff->channel_layout(), 1);
- BOOST_CHECK_EQUAL (cff->name(), "hello there world");
- BOOST_CHECK_EQUAL (cff->to_string(), "ffmpeg 4 44100 1 hello there world");
-
- shared_ptr<AudioStream> fe = audio_stream_factory ("external 44100 1", boost::optional<int> (1));
- BOOST_CHECK_EQUAL (fe->sample_rate(), 44100);
- BOOST_CHECK_EQUAL (fe->channel_layout(), 1);
- BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1");
-}
-
BOOST_AUTO_TEST_CASE (format_test)
{
Format::setup_formats ();
Format const * f = Format::from_nickname ("Flat");
BOOST_CHECK (f);
- // BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
+ BOOST_CHECK_EQUAL (f->dcp_size().width, 1998);
+ BOOST_CHECK_EQUAL (f->dcp_size().height, 1080);
f = Format::from_nickname ("Scope");
BOOST_CHECK (f);
- // BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
+ BOOST_CHECK_EQUAL (f->dcp_size().width, 2048);
+ BOOST_CHECK_EQUAL (f->dcp_size().height, 858);
}
-/* Test VariableFormat-based scaling of content */
-BOOST_AUTO_TEST_CASE (scaling_test)
-{
- shared_ptr<Film> film (new Film (test_film_dir ("scaling_test").string(), false));
-
- /* 4:3 ratio */
- film->set_size (libdcp::Size (320, 240));
-
- /* This format should preserve aspect ratio of the source */
- Format const * format = Format::from_id ("var-185");
-
- /* We should have enough padding that the result is 4:3,
- which would be 1440 pixels.
- */
- BOOST_CHECK_EQUAL (format->dcp_padding (film), (1998 - 1440) / 2);
-
- /* This crops it to 1.291666667 */
- film->set_left_crop (5);
- film->set_right_crop (5);
-
- /* We should now have enough padding that the result is 1.29166667,
- which would be 1395 pixels.
- */
- BOOST_CHECK_EQUAL (format->dcp_padding (film), rint ((1998 - 1395) / 2.0));
-}
-
BOOST_AUTO_TEST_CASE (util_test)
{
string t = "Hello this is a string \"with quotes\" and indeed without them";
void do_log (string) {}
};
- void
- do_positive_delay_line_test (int delay_length, int data_length)
- {
- shared_ptr<NullLog> log (new NullLog);
-
- DelayLine d (log, 6, delay_length);
- shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length));
-
- int in = 0;
- int out = 0;
- int returned = 0;
- int zeros = 0;
-
- for (int i = 0; i < 64; ++i) {
- for (int j = 0; j < data_length; ++j) {
- for (int c = 0; c < 6; ++c ) {
- data->data(c)[j] = in;
- ++in;
- }
- }
-
- /* This only works because the delay line modifies the parameter */
- d.process_audio (data);
- returned += data->frames ();
-
- for (int j = 0; j < data->frames(); ++j) {
- if (zeros < delay_length) {
- for (int c = 0; c < 6; ++c) {
- BOOST_CHECK_EQUAL (data->data(c)[j], 0);
- }
- ++zeros;
- } else {
- for (int c = 0; c < 6; ++c) {
- BOOST_CHECK_EQUAL (data->data(c)[j], out);
- ++out;
- }
- }
- }
- }
-
- BOOST_CHECK_EQUAL (returned, 64 * data_length);
- }
-
- void
- do_negative_delay_line_test (int delay_length, int data_length)
- {
- shared_ptr<NullLog> log (new NullLog);
-
- DelayLine d (log, 6, delay_length);
- shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length));
-
- int in = 0;
- int out = -delay_length * 6;
- int returned = 0;
-
- for (int i = 0; i < 256; ++i) {
- data->set_frames (data_length);
- for (int j = 0; j < data_length; ++j) {
- for (int c = 0; c < 6; ++c) {
- data->data(c)[j] = in;
- ++in;
- }
- }
-
- /* This only works because the delay line modifies the parameter */
- d.process_audio (data);
- returned += data->frames ();
-
- for (int j = 0; j < data->frames(); ++j) {
- for (int c = 0; c < 6; ++c) {
- BOOST_CHECK_EQUAL (data->data(c)[j], out);
- ++out;
- }
- }
- }
-
- returned += -delay_length;
- BOOST_CHECK_EQUAL (returned, 256 * data_length);
- }
-
- BOOST_AUTO_TEST_CASE (delay_line_test)
- {
- do_positive_delay_line_test (64, 128);
- do_positive_delay_line_test (128, 64);
- do_positive_delay_line_test (3, 512);
- do_positive_delay_line_test (512, 3);
-
- do_positive_delay_line_test (0, 64);
-
- do_negative_delay_line_test (-64, 128);
- do_negative_delay_line_test (-128, 64);
- do_negative_delay_line_test (-3, 512);
- do_negative_delay_line_test (-512, 3);
- }
-
BOOST_AUTO_TEST_CASE (md5_digest_test)
{
string const t = md5_digest ("test/md5.test");
BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
}
-BOOST_AUTO_TEST_CASE (paths_test)
-{
- shared_ptr<Film> f = new_test_film ("paths_test");
- f->set_directory ("build/test/a/b/c/d/e");
-
- f->_content = "/foo/bar/baz";
- BOOST_CHECK_EQUAL (f->content_path(), "/foo/bar/baz");
- f->_content = "foo/bar/baz";
- BOOST_CHECK_EQUAL (f->content_path(), "build/test/a/b/c/d/e/foo/bar/baz");
-}
-
void
do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
{
new thread (boost::bind (&Server::run, server, 2));
/* Let the server get itself ready */
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
ServerDescription description ("localhost", 2);
{
shared_ptr<Film> film = new_test_film ("make_dcp_test");
film->set_name ("test_film2");
- film->set_content ("../../../test/test.mp4");
+// film->set_content ("../../../test/test.mp4");
film->set_format (Format::from_nickname ("Flat"));
film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
film->make_dcp ();
film->write_metadata ();
while (JobManager::instance()->work_to_do ()) {
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
}
BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
{
shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test");
film->set_name ("test_film3");
- film->set_content ("../../../test/test.mp4");
- film->examine_content ();
+// film->set_content ("../../../test/test.mp4");
+// film->examine_content ();
film->set_format (Format::from_nickname ("Flat"));
film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
film->set_trim_end (42);
film->make_dcp ();
while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
}
BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
Config::instance()->set_allowed_dcp_frame_rates (afr);
shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");
- f->set_source_frame_rate (24);
+// f->set_source_frame_rate (24);
f->set_dcp_frame_rate (24);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000);
- f->set_source_frame_rate (23.976);
+// f->set_source_frame_rate (23.976);
f->set_dcp_frame_rate (best_dcp_frame_rate (23.976));
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
- f->set_source_frame_rate (29.97);
+// f->set_source_frame_rate (29.97);
f->set_dcp_frame_rate (best_dcp_frame_rate (29.97));
BOOST_CHECK_EQUAL (f->dcp_frame_rate (), 30);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
- f->set_source_frame_rate (25);
+// f->set_source_frame_rate (25);
f->set_dcp_frame_rate (24);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
- f->set_source_frame_rate (25);
+// f->set_source_frame_rate (25);
f->set_dcp_frame_rate (24);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
/* Check some out-there conversions (not the best) */
- f->set_source_frame_rate (14.99);
+// f->set_source_frame_rate (14.99);
f->set_dcp_frame_rate (25);
- f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
+// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
/* The FrameRateConversion within target_audio_sample_rate should choose to double-up
the 14.99 fps video to 30 and then run it slow at 25.
*/
shared_ptr<TestJob> a (new TestJob (f));
JobManager::instance()->add (a);
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
BOOST_CHECK_EQUAL (a->running (), true);
a->set_finished_ok ();
- dvdomatic_sleep (2);
+ dcpomatic_sleep (2);
BOOST_CHECK_EQUAL (a->finished_ok(), true);
}
import os
import sys
-APPNAME = 'dvdomatic'
+APPNAME = 'dcpomatic'
- VERSION = '0.84pre'
+ VERSION = '0.85pre'
def options(opt):
opt.load('compiler_cxx')
'-Wall', '-Wno-attributes', '-Wextra'])
if conf.options.target_windows:
- conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
+ conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
wxrc = os.popen('wx-config --rescomp').read().split()[1:]
conf.env.append_value('WINRCFLAGS', wxrc)
if conf.options.enable_debug:
boost_lib_suffix = '-mt'
boost_thread = 'boost_thread_win32-mt'
else:
- conf.env.append_value('CXXFLAGS', '-DDVDOMATIC_POSIX')
+ conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_POSIX')
conf.env.append_value('CXXFLAGS', '-DPOSIX_LOCALE_PREFIX="%s/share/locale"' % conf.env['PREFIX'])
- conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dvdomatic"' % conf.env['PREFIX'])
+ conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dcpomatic"' % conf.env['PREFIX'])
boost_lib_suffix = ''
boost_thread = 'boost_thread'
conf.env.append_value('LINKFLAGS', '-pthread')
conf.env.VERSION = VERSION
if conf.options.enable_debug:
- conf.env.append_value('CXXFLAGS', ['-g', '-DDVDOMATIC_DEBUG'])
+ conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG'])
else:
conf.env.append_value('CXXFLAGS', '-O2')
if not conf.options.static:
- conf.check_cfg(package = 'libdcp', atleast_version = '0.41', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+ conf.check_cfg(package = 'libdcp', atleast_version = '0.45', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+ conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
conf.env.HAVE_DCP = 1
conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2']
+ conf.env.HAVE_CXML = 1
+ conf.env.STLIB_CXML = ['cxml']
+ conf.check_cfg(package = 'libxml++-2.6', args = '--cflags --libs', uselib_store = 'XML++', mandatory = True)
conf.env.HAVE_AVFORMAT = 1
conf.env.STLIB_AVFORMAT = ['avformat']
conf.env.HAVE_AVFILTER = 1
d = { 'PREFIX' : '${PREFIX' }
obj = bld(features = 'subst')
- obj.source = 'dvdomatic.desktop.in'
- obj.target = 'dvdomatic.desktop'
+ obj.source = 'dcpomatic.desktop.in'
+ obj.target = 'dcpomatic.desktop'
obj.dict = d
- bld.install_files('${PREFIX}/share/applications', 'dvdomatic.desktop')
+ bld.install_files('${PREFIX}/share/applications', 'dcpomatic.desktop')
for r in ['22x22', '32x32', '48x48', '64x64', '128x128']:
- bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dvdomatic.png' % r)
+ bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dcpomatic.png' % r)
if not bld.env.TARGET_WINDOWS:
- bld.install_files('${PREFIX}/share/dvdomatic', 'icons/taskbar_icon.png')
+ bld.install_files('${PREFIX}/share/dcpomatic', 'icons/taskbar_icon.png')
bld.add_post_fun(post)
try:
text = '#include "version.h"\n'
- text += 'char const * dvdomatic_git_commit = \"%s\";\n' % commit
- text += 'char const * dvdomatic_version = \"%s\";\n' % version
+ text += 'char const * dcpomatic_git_commit = \"%s\";\n' % commit
+ text += 'char const * dcpomatic_version = \"%s\";\n' % version
print('Writing version information to src/lib/version.cc')
o = open('src/lib/version.cc', 'w')
o.write(text)