From: Carl Hetherington Date: Sun, 21 Apr 2013 21:41:52 +0000 (+0100) Subject: Untested merge of master. X-Git-Tag: v2.0.48~1337^2~433 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=47f25009bcbc765e397bcb471dd361a511c99daf;hp=-c Untested merge of master. --- 47f25009bcbc765e397bcb471dd361a511c99daf diff --combined debian/changelog index 0a53eff15,eab1f4b4f..a0b068d55 --- a/debian/changelog +++ b/debian/changelog @@@ -1,298 -1,334 +1,334 @@@ - dcpomatic (0.83-1) UNRELEASED; urgency=low + dvdomatic (0.84-1) UNRELEASED; urgency=low + + * New upstream release. + + -- Carl Hetherington Sun, 21 Apr 2013 17:49:54 +0100 + + dvdomatic (0.84beta5-1) UNRELEASED; urgency=low + + * New upstream release. + + -- Carl Hetherington Sun, 21 Apr 2013 00:06:12 +0100 + + dvdomatic (0.84beta4-1) UNRELEASED; urgency=low + + * New upstream release. + + -- Carl Hetherington Fri, 19 Apr 2013 17:41:58 +0100 + + dvdomatic (0.84beta3-1) UNRELEASED; urgency=low + + * New upstream release. + + -- Carl Hetherington Fri, 19 Apr 2013 11:36:37 +0100 + + dvdomatic (0.84beta2-1) UNRELEASED; urgency=low + + * New upstream release. + + -- Carl Hetherington Fri, 19 Apr 2013 11:12:09 +0100 + + dvdomatic (0.84beta1-1) UNRELEASED; urgency=low + + * New upstream release. + + -- Carl Hetherington Thu, 18 Apr 2013 23:32:17 +0100 + + dvdomatic (0.83-1) UNRELEASED; urgency=low * New upstream release. -- Carl Hetherington 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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. @@@ -300,7 -336,7 +336,7 @@@ -- Carl Hetherington 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. @@@ -309,13 -345,13 +345,13 @@@ -- Carl Hetherington 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 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. @@@ -325,91 -361,91 +361,91 @@@ -- Carl Hetherington 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Sun, 09 Dec 2012 21:38:00 +0000 -dvdomatic (0.59beta1-1) unstable; urgency=low +dcpomatic (0.59beta1-1) unstable; urgency=low * Initial release. diff --combined debian/rules index ba42e5291,a2bf70bd8..7f7da5530 --- a/debian/rules +++ b/debian/rules @@@ -13,11 -13,12 +13,12 @@@ 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 diff --combined src/lib/ab_transcoder.cc index 81aa5a4ba,6eef397c2..c6ccfdc67 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@@ -21,10 -21,13 +21,10 @@@ #include #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" @@@ -46,37 -49,38 +46,30 @@@ using boost::dynamic_pointer_cast * @param e Encoder to use. */ -ABTranscoder::ABTranscoder ( - shared_ptr a, shared_ptr b, DecodeOptions o, Job* j, shared_ptr e) +ABTranscoder::ABTranscoder (shared_ptr a, shared_ptr b, shared_ptr 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 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 @@@ -84,17 -88,23 +77,17 @@@ ABTranscoder::go ( { _encoder->process_begin (); - bool done[3] = { false, false, false }; + bool done[2] = { false, false }; while (1) { - done[0] = _da.video->pass (); - done[1] = _db.video->pass (); - - if (!done[2] && _da.audio && dynamic_pointer_cast (_da.audio) != dynamic_pointer_cast (_da.video)) { - done[2] = _da.audio->pass (); - } else { - done[2] = true; - } + done[0] = _player_a->pass (); + done[1] = _player_b->pass (); if (_job) { - _da.video->set_progress (_job); + _player_a->set_progress (_job); } - if (done[0] && done[1] && done[2]) { + if (done[0] && done[1]) { break; } } diff --combined src/lib/audio_decoder.h index 418fc6da2,cfe94b528..c393e95f1 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@@ -21,21 -21,38 +21,21 @@@ * @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, DecodeOptions); - - virtual void set_audio_stream (boost::shared_ptr); - - /** @return Audio stream that we are using */ - boost::shared_ptr audio_stream () const { - return _audio_stream; - } - - /** @return All available audio streams */ - std::vector > audio_streams () const { - return _audio_streams; - } - -protected: - /** Audio stream that we are using */ - boost::shared_ptr _audio_stream; - /** All available audio streams */ - std::vector > _audio_streams; + AudioDecoder (boost::shared_ptr); }; #endif diff --combined src/lib/audio_sink.h index 085491657,f34b24f88..ba4b772c8 --- a/src/lib/audio_sink.h +++ b/src/lib/audio_sink.h @@@ -17,8 -17,8 +17,8 @@@ */ -#ifndef DVDOMATIC_AUDIO_SINK_H -#define DVDOMATIC_AUDIO_SINK_H +#ifndef DCPOMATIC_AUDIO_SINK_H +#define DCPOMATIC_AUDIO_SINK_H class AudioSink { @@@ -27,4 -27,11 +27,11 @@@ public virtual void process_audio (boost::shared_ptr) = 0; }; + class TimedAudioSink + { + public: + /** Call with some audio data */ + virtual void process_audio (boost::shared_ptr, double t) = 0; + }; + #endif diff --combined src/lib/audio_source.cc index 99b59759d,bca3562cf..3dd3027ab --- a/src/lib/audio_source.cc +++ b/src/lib/audio_source.cc @@@ -21,20 -21,16 +21,26 @@@ #include "audio_sink.h" using boost::shared_ptr; +using boost::weak_ptr; using boost::bind; +static void +process_audio_proxy (weak_ptr sink, shared_ptr audio) +{ + shared_ptr p = sink.lock (); + if (p) { + p->process_audio (audio); + } +} + void AudioSource::connect_audio (shared_ptr s) { - Audio.connect (bind (&AudioSink::process_audio, s, _1)); + Audio.connect (bind (process_audio_proxy, weak_ptr (s), _1)); } + + void + TimedAudioSource::connect_audio (shared_ptr s) + { + Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2)); + } diff --combined src/lib/audio_source.h index ee5c606dc,3dc998cca..dd2480716 --- a/src/lib/audio_source.h +++ b/src/lib/audio_source.h @@@ -21,13 -21,14 +21,14 @@@ * @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 class AudioBuffers; class AudioSink; + class TimedAudioSink; /** A class that emits audio data */ class AudioSource @@@ -39,4 -40,15 +40,15 @@@ public void connect_audio (boost::shared_ptr); }; + + /** A class that emits audio data with timestamps */ + class TimedAudioSource + { + public: + /** Emitted when some audio data is ready */ + boost::signals2::signal, double)> Audio; + + void connect_audio (boost::shared_ptr); + }; + #endif diff --combined src/lib/decoder.h index 72b866ffe,2bc462c33..20e32bfbf --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@@ -21,8 -21,8 +21,8 @@@ * @brief Parent class for decoders of content. */ -#ifndef DVDOMATIC_DECODER_H -#define DVDOMATIC_DECODER_H +#ifndef DCPOMATIC_DECODER_H +#define DCPOMATIC_DECODER_H #include #include @@@ -30,9 -30,11 +30,9 @@@ #include #include #include "util.h" -#include "stream.h" #include "video_source.h" #include "audio_source.h" #include "film.h" -#include "options.h" class Image; class Log; @@@ -51,18 -53,26 +51,22 @@@ class FilterGraph class Decoder { public: - Decoder (boost::shared_ptr, DecodeOptions); + Decoder (boost::shared_ptr); 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 OutputChanged; protected: - /** our Film */ - boost::shared_ptr _film; - /** our decode options */ - DecodeOptions _opt; + boost::shared_ptr _film; private: virtual void film_changed (Film::Property) {} - + boost::signals2::scoped_connection _film_connection; }; diff --combined src/lib/ffmpeg_content.cc index 577dbd14d,000000000..719c4cb53 mode 100644,000000..100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@@ -1,282 -1,0 +1,282 @@@ +/* + Copyright (C) 2013 Carl Hetherington + + 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 +#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 node) + : Content (node) + , VideoContent (node) + , AudioContent (node) +{ + list > c = node->node_children ("SubtitleStream"); + for (list >::const_iterator i = c.begin(); i != c.end(); ++i) { + _subtitle_streams.push_back (FFmpegSubtitleStream (*i)); + if ((*i)->optional_number_child ("Selected")) { + _subtitle_stream = _subtitle_streams.back (); + } + } + + c = node->node_children ("AudioStream"); + for (list >::const_iterator i = c.begin(); i != c.end(); ++i) { + _audio_streams.push_back (FFmpegAudioStream (*i)); + if ((*i)->optional_number_child ("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::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::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, shared_ptr job, bool quick) +{ + job->set_progress_unknown (); + + Content::examine (film, job, quick); + - shared_ptr decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false, true)); ++ shared_ptr 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 node) +{ + name = node->string_child ("Name"); + id = node->number_child ("Id"); + frame_rate = node->number_child ("FrameRate"); + channels = node->number_child ("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 (id)); + root->add_child("FrameRate")->add_child_text (lexical_cast (frame_rate)); + root->add_child("Channels")->add_child_text (lexical_cast (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 node) +{ + name = node->string_child ("Name"); + id = node->number_child ("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 (id)); +} + +shared_ptr +FFmpegContent::clone () const +{ + return shared_ptr (new FFmpegContent (*this)); +} diff --combined src/lib/ffmpeg_decoder.cc index eac1d91ae,8e09810cb..82c7cafd1 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@@ -41,6 -41,7 +41,6 @@@ extern "C" #include "transcoder.h" #include "job.h" #include "filter.h" -#include "options.h" #include "exceptions.h" #include "image.h" #include "util.h" @@@ -61,13 -62,10 +61,13 @@@ using boost::optional using boost::dynamic_pointer_cast; using libdcp::Size; -FFmpegDecoder::FFmpegDecoder (shared_ptr f, DecodeOptions o) - : Decoder (f, o) - , VideoDecoder (f, o) - , AudioDecoder (f, o) +boost::mutex FFmpegDecoder::_mutex; + - FFmpegDecoder::FFmpegDecoder (shared_ptr f, shared_ptr c, bool video, bool audio, bool subtitles, bool video_sync) ++FFmpegDecoder::FFmpegDecoder (shared_ptr f, shared_ptr c, bool video, bool audio, bool subtitles) + : Decoder (f) + , VideoDecoder (f) + , AudioDecoder (f) + , _ffmpeg_content (c) , _format_context (0) , _video_stream (-1) , _frame (0) @@@ -77,29 -75,19 +77,24 @@@ , _audio_codec (0) , _subtitle_codec_context (0) , _subtitle_codec (0) + , _decode_video (video) + , _decode_audio (audio) + , _decode_subtitles (subtitles) - , _video_sync (video_sync) { setup_general (); setup_video (); setup_audio (); setup_subtitle (); - - if (!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); } @@@ -118,15 -106,15 +113,15 @@@ FFmpegDecoder::setup_general ( { av_register_all (); - if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) { - throw OpenFileError (_film->content_path ()); + if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) { + throw OpenFileError (_ffmpeg_content->file().string ()); } if (avformat_find_stream_info (_format_context, 0) < 0) { throw DecodeError (_("could not find stream information")); } - /* Find video, audio and subtitle streams and choose the first of each */ + /* Find video, audio and subtitle streams */ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { AVStream* s = _format_context->streams[i]; @@@ -143,11 -131,17 +138,11 @@@ } _audio_streams.push_back ( - shared_ptr ( - 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 ( - new SubtitleStream (stream_name (s), i) - ) - ); + _subtitle_streams.push_back (FFmpegSubtitleStream (stream_name (s), i)); } } @@@ -164,8 -158,6 +159,8 @@@ 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); @@@ -181,13 -173,14 +176,13 @@@ void FFmpegDecoder::setup_audio () { - if (!_audio_stream) { + boost::mutex::scoped_lock lm (_mutex); + + if (!_ffmpeg_content->audio_stream ()) { return; } - shared_ptr ffa = dynamic_pointer_cast (_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) { @@@ -202,13 -195,11 +197,13 @@@ 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) { @@@ -233,32 -224,34 +228,32 @@@ FFmpegDecoder::pass ( 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 ffa = dynamic_pointer_cast (_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); @@@ -268,16 -261,12 +263,12 @@@ _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; @@@ -309,16 -298,19 +300,16 @@@ shared_ptr FFmpegDecoder::deinterleave_audio (uint8_t** data, int size) { - assert (_film->audio_channels()); + assert (_ffmpeg_content->audio_channels()); assert (bytes_per_audio_sample()); - shared_ptr ffa = dynamic_pointer_cast (_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 audio (new AudioBuffers (ffa->channels(), frames)); + int const frames = total_samples / _ffmpeg_content->audio_channels(); + shared_ptr audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames)); switch (audio_sample_format()) { case AV_SAMPLE_FMT_S16: @@@ -330,7 -322,7 +321,7 @@@ audio->data(channel)[sample] = float(*p++) / (1 << 15); ++channel; - if (channel == _film->audio_channels()) { + if (channel == _ffmpeg_content->audio_channels()) { channel = 0; ++sample; } @@@ -341,7 -333,7 +332,7 @@@ case AV_SAMPLE_FMT_S16P: { int16_t** p = reinterpret_cast (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(p[i][j]) / (1 << 15); } @@@ -358,7 -350,7 +349,7 @@@ audio->data(channel)[sample] = static_cast(*p++) / (1 << 31); ++channel; - if (channel == _film->audio_channels()) { + if (channel == _ffmpeg_content->audio_channels()) { channel = 0; ++sample; } @@@ -375,7 -367,7 +366,7 @@@ audio->data(channel)[sample] = *p++; ++channel; - if (channel == _film->audio_channels()) { + if (channel == _ffmpeg_content->audio_channels()) { channel = 0; ++sample; } @@@ -386,7 -378,7 +377,7 @@@ case AV_SAMPLE_FMT_FLTP: { float** p = reinterpret_cast (data); - for (int i = 0; i < _film->audio_channels(); ++i) { + for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) { memcpy (audio->data(i), p[i], frames * sizeof(float)); } } @@@ -400,7 -392,7 +391,7 @@@ } float -FFmpegDecoder::frames_per_second () const +FFmpegDecoder::video_frame_rate () const { AVStream* s = _format_context->streams[_video_stream]; @@@ -461,18 -453,20 +452,20 @@@ strin 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()) { @@@ -488,100 -482,172 +481,95 @@@ FFmpegDecoder::bytes_per_audio_sample ( return av_get_bytes_per_sample (audio_sample_format ()); } -void -FFmpegDecoder::set_audio_stream (shared_ptr s) -{ - AudioDecoder::set_audio_stream (s); - setup_audio (); -} - -void -FFmpegDecoder::set_subtitle_stream (shared_ptr 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 graph; list >::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 > images = graph->process (frame); + list > images = graph->process (_frame); for (list >::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::create (string t, optional v) -{ - if (!v) { - /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ - return shared_ptr (new FFmpegAudioStream (t, v)); - } - - stringstream s (t); - string type; - s >> type; - if (type != N_("ffmpeg")) { - return shared_ptr (); - } - - return shared_ptr (new FFmpegAudioStream (t, v)); -} - -FFmpegAudioStream::FFmpegAudioStream (string t, optional 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) { @@@ -592,6 -658,7 +580,6 @@@ boost::mutex::scoped_lock lm (_filter_graphs_mutex); _filter_graphs.clear (); } - OutputChanged (); break; default: @@@ -600,21 -667,18 +588,15 @@@ } /** @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 ffa = dynamic_pointer_cast (_audio_stream); - assert (ffa); - /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4 several times. */ @@@ -625,53 -689,21 +607,21 @@@ 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 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; } diff --combined src/lib/ffmpeg_decoder.h index f6a53874a,0c89b973d..174cc3995 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@@ -36,7 -36,6 +36,7 @@@ extern "C" #include "video_decoder.h" #include "audio_decoder.h" #include "film.h" +#include "ffmpeg_content.h" struct AVFilterGraph; struct AVCodecContext; @@@ -51,47 -50,70 +51,48 @@@ class Options class Image; class Log; -class FFmpegAudioStream : public AudioStream -{ -public: - FFmpegAudioStream (std::string n, int i, int s, int64_t c) - : AudioStream (s, c) - , _name (n) - , _id (i) - {} - - std::string to_string () const; - - std::string name () const { - return _name; - } - - int id () const { - return _id; - } - - static boost::shared_ptr create (std::string t, boost::optional v); - -private: - friend class stream_test; - - FFmpegAudioStream (std::string t, boost::optional 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, boost::shared_ptr, bool video, bool audio, bool subtitles, bool video_sync); - FFmpegDecoder (boost::shared_ptr, DecodeOptions); ++ FFmpegDecoder (boost::shared_ptr, boost::shared_ptr, 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); - void set_subtitle_stream (boost::shared_ptr); + std::vector subtitle_streams () const { + return _subtitle_streams; + } + + std::vector 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 (); @@@ -107,8 -129,6 +108,8 @@@ std::string stream_name (AVStream* s) const; + boost::shared_ptr _ffmpeg_content; + AVFormatContext* _format_context; int _video_stream; @@@ -123,23 -143,6 +124,19 @@@ AVPacket _packet; - boost::optional _first_video; - boost::optional _first_audio; - std::list > _filter_graphs; boost::mutex _filter_graphs_mutex; + + std::vector _subtitle_streams; + std::vector _audio_streams; + + bool _decode_video; + bool _decode_audio; + bool _decode_subtitles; - bool _video_sync; + + /* It would appear (though not completely verified) that one must have + a mutex around calls to avcodec_open* and avcodec_close... and here + it is. + */ + static boost::mutex _mutex; }; diff --combined src/lib/film.cc index c8aee7a0a,b0785df34..361031fc8 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@@ -29,31 -29,29 +29,31 @@@ #include #include #include +#include +#include #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" @@@ -69,8 -67,6 +69,8 @@@ using std::setfill 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; @@@ -90,18 -86,19 +90,18 @@@ int const Film::state_version = 4 */ Film::Film (string d, bool must_exist) - : _use_dci_name (true) - , _trust_content_header (true) + : _playlist (new Playlist) + , _use_dci_name (true) + , _trust_content_headers (true) , _dcp_content_type (0) - , _format (0) + , _format (Format::from_id ("185")) , _scaler (Scaler::from_id ("bicubic")) , _trim_start (0) , _trim_end (0) , _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) @@@ -109,11 -106,10 +109,11 @@@ , _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) @@@ -143,8 -139,12 +143,10 @@@ } } - _sndfile_stream = SndfileStream::create (); - if (must_exist) { read_metadata (); + } else { + write_metadata (); } _log.reset (new FileLog (file ("log"))); @@@ -154,11 -154,11 +156,11 @@@ Film::Film (Film const & o : boost::enable_shared_from_this (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) @@@ -167,38 -167,50 +169,39 @@@ , _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 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 @@@ -206,7 -218,7 +209,7 @@@ << "_" << j2k_bandwidth() << "_" << boost::lexical_cast (colour_lut()); - if (dcp_ab()) { + if (ab()) { pair fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; } @@@ -270,7 -282,7 +273,7 @@@ Film::audio_analysis_path () cons { boost::filesystem::path p; p /= "analysis"; - p /= content_digest(); + p /= _playlist->audio_digest(); return file (p.string ()); } @@@ -284,7 -296,7 +287,7 @@@ Film::make_dcp ( 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]; @@@ -292,18 -304,18 +295,18 @@@ 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."); @@@ -329,16 -341,19 +332,16 @@@ throw MissingSettingError (_("name")); } - DecodeOptions od; - od.decode_subtitles = with_subtitles (); - shared_ptr r; - if (dcp_ab()) { - r = JobManager::instance()->add (shared_ptr (new ABTranscodeJob (shared_from_this(), od))); + if (ab()) { + r = JobManager::instance()->add (shared_ptr (new ABTranscodeJob (shared_from_this()))); } else { - r = JobManager::instance()->add (shared_ptr (new TranscodeJob (shared_from_this(), od))); + r = JobManager::instance()->add (shared_ptr (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 () { @@@ -351,12 -366,17 +354,12 @@@ 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 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 j (new ExamineContentJob (shared_from_this(), c, trust_content_headers ())); + JobManager::instance()->add (j); } void @@@ -371,6 -391,12 +374,6 @@@ Film::analyse_audio_finished ( _analyse_audio_job.reset (); } -void -Film::examine_content_finished () -{ - _examine_content_job.reset (); -} - /** Start a job to send our DCP to the configured TMS */ void Film::send_dcp_to_tms () @@@ -402,66 -428,86 +405,67 @@@ Film::encoded_frames () cons 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 (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::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::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 >::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 (_crop.left)); + root->add_child("RightCrop")->add_child_text (boost::lexical_cast (_crop.right)); + root->add_child("TopCrop")->add_child_text (boost::lexical_cast (_crop.top)); + root->add_child("BottomCrop")->add_child_text (boost::lexical_cast (_crop.bottom)); - for (vector >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { - f << "subtitle_stream " << (*i)->to_string () << endl; + for (vector::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 (_trim_start)); + root->add_child("TrimEnd")->add_child_text (boost::lexical_cast (_trim_end)); + root->add_child("AB")->add_child_text (_ab ? "1" : "0"); + root->add_child("AudioGain")->add_child_text (boost::lexical_cast (_audio_gain)); + root->add_child("AudioDelay")->add_child_text (boost::lexical_cast (_audio_delay)); + root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); + root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast (_subtitle_offset)); + root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast (_subtitle_scale)); + root->add_child("ColourLUT")->add_child_text (boost::lexical_cast (_colour_lut)); + root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast (_j2k_bandwidth)); + _dci_metadata.as_xml (root->add_child ("DCIMetadata")); + root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast (_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; } @@@ -471,90 -517,168 +475,91 @@@ voi Film::read_metadata () { boost::mutex::scoped_lock lm (_state_mutex); + LocaleGuard lg; - _external_audio.clear (); - _content_audio_streams.clear (); - _subtitle_streams.clear (); - - boost::optional version; - - /* Backward compatibility things */ - boost::optional audio_sample_rate; - boost::optional audio_stream_index; - boost::optional 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 kv = read_key_value (f); - /* We need version before anything else */ - multimap::iterator v = kv.find ("version"); - if (v != kv.end ()) { - version = atoi (v->second.c_str()); - } + cxml::File f (file ("metadata.xml"), "Metadata"); - for (multimap::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 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 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 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 >::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 ("LeftCrop"); + _crop.right = f.number_child ("RightCrop"); + _crop.top = f.number_child ("TopCrop"); + _crop.bottom = f.number_child ("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 > c = f.node_children ("Filter"); + for (list >::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 ("TrimStart"); + _trim_end = f.number_child ("TrimEnd"); + _ab = f.bool_child ("AB"); + _audio_gain = f.number_child ("AudioGain"); + _audio_delay = f.number_child ("AudioDelay"); + _with_subtitles = f.bool_child ("WithSubtitles"); + _subtitle_offset = f.number_child ("SubtitleOffset"); + _subtitle_scale = f.number_child ("SubtitleScale"); + _colour_lut = f.number_child ("ColourLUT"); + _j2k_bandwidth = f.number_child ("J2KBandwidth"); + _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); + _dcp_frame_rate = f.number_child ("DCPFrameRate"); + _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); + + list > c = f.node_children ("Content"); + for (list >::iterator i = c.begin(); i != c.end(); ++i) { + + string const type = (*i)->string_child ("Type"); + boost::shared_ptr 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 @@@ -601,18 -725,47 +606,18 @@@ Film::file (string f) cons 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 @@@ -621,12 -774,18 +626,12 @@@ */ 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 @@@ -673,7 -832,7 +678,7 @@@ } } - switch (audio_channels()) { + switch (audio_channels ()) { case 1: d << "_10"; break; @@@ -752,21 -911,110 +757,21 @@@ Film::set_use_dci_name (bool u } void -Film::set_content (string c) -{ - string check = directory (); - - boost::filesystem::path slash ("/"); - string platform_slash = slash.make_preferred().string (); - - if (!ends_with (check, platform_slash)) { - check += platform_slash; - } - - if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) { - c = c.substr (_directory.length() + 1); - } - - string old_content; - - { - boost::mutex::scoped_lock lm (_state_mutex); - if (c == _content) { - return; - } - - old_content = _content; - _content = c; - } - - /* Reset streams here in case the new content doesn't have one or the other */ - _content_audio_stream = shared_ptr (); - _subtitle_stream = shared_ptr (); - - /* 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); + } } } @@@ -908,13 -1156,50 +913,13 @@@ Film::set_trim_type (TrimType t } void -Film::set_dcp_ab (bool a) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_ab = a; - } - signal_changed (DCP_AB); -} - -void -Film::set_content_audio_stream (shared_ptr s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_audio_stream = s; - } - signal_changed (CONTENT_AUDIO_STREAM); -} - -void -Film::set_external_audio (vector a) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _external_audio = a; - } - - shared_ptr 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 @@@ -937,6 -1222,26 +942,6 @@@ Film::set_audio_delay (int d 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 s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_stream = s; - } - signal_changed (SUBTITLE_STREAM); -} - void Film::set_with_subtitles (bool w) { @@@ -1009,306 -1314,184 +1014,306 @@@ Film::set_dcp_frame_rate (int f } void -Film::set_size (libdcp::Size s) +Film::signal_changed (Property p) { { boost::mutex::scoped_lock lm (_state_mutex); - _size = s; + _dirty = true; + } + + switch (p) { + case Film::CONTENT: + _playlist->setup (content ()); + set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ())); + set_audio_mapping (_playlist->default_audio_mapping ()); + break; + default: + break; + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (Changed), p)); } - signal_changed (SIZE); } void -Film::set_length (SourceFrame l) +Film::set_dci_date_today () { - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = l; - } - signal_changed (LENGTH); + _dci_date = boost::gregorian::day_clock::local_day (); } -void -Film::unset_length () +string +Film::info_path (int f) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = boost::none; + boost::filesystem::path p; + p /= info_dir (); + + stringstream s; + s.width (8); + s << setfill('0') << f << ".md5"; + + p /= s.str(); + + /* info_dir() will already have added any initial bit of the path, + so don't call file() on this. + */ + return p.string (); +} + +string +Film::j2c_path (int f, bool t) const +{ + boost::filesystem::path p; + p /= "j2c"; + p /= video_state_identifier (); + + stringstream s; + s.width (8); + s << setfill('0') << f << ".j2c"; + + if (t) { + s << ".tmp"; } - signal_changed (LENGTH); + + p /= s.str(); + return file (p.string ()); } -void -Film::set_content_digest (string d) +/** Make an educated guess as to whether we have a complete DCP + * or not. + * @return true if we do. + */ + +bool +Film::have_dcp () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_digest = d; + try { + libdcp::DCP dcp (dir (dcp_name())); + dcp.read (); + } catch (...) { + return false; } - _dirty = true; + + return true; +} + +shared_ptr +Film::player () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return shared_ptr (new Player (shared_from_this (), _playlist)); } void -Film::set_content_audio_streams (vector > s) +Film::add_content (shared_ptr 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 > s) +Film::remove_content (shared_ptr 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 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 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 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 -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 +Film::ffmpeg () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + + for (ContentList::const_iterator i = _content.begin (); i != _content.end(); ++i) { + shared_ptr f = boost::dynamic_pointer_cast (*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 (); } -string -Film::j2c_path (int f, bool t) const +vector +Film::ffmpeg_subtitle_streams () const { - boost::filesystem::path p; - p /= "j2c"; - p /= video_state_identifier (); + shared_ptr f = ffmpeg (); + if (f) { + return f->subtitle_streams (); + } - stringstream s; - s.width (8); - s << setfill('0') << f << ".j2c"; + return vector (); +} - if (t) { - s << ".tmp"; +boost::optional +Film::ffmpeg_subtitle_stream () const +{ + shared_ptr 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 +Film::ffmpeg_audio_streams () const +{ + shared_ptr f = ffmpeg (); + if (f) { + return f->audio_streams (); + } -bool -Film::have_dcp () const + return vector (); +} + +boost::optional +Film::ffmpeg_audio_stream () const { - try { - libdcp::DCP dcp (dir (dcp_name())); - dcp.read (); - } catch (...) { - return false; + shared_ptr 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 f = ffmpeg (); + if (f) { + f->set_subtitle_stream (s); } +} - vector const e = external_audio (); - for (vector::const_iterator i = e.begin(); i != e.end(); ++i) { - if (!i->empty ()) { - return true; - } +void +Film::set_ffmpeg_audio_stream (FFmpegAudioStream s) +{ + shared_ptr 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 c, int p) +{ + if (p == VideoContentProperty::VIDEO_FRAME_RATE) { + set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ())); + } else if (p == AudioContentProperty::AUDIO_CHANNELS) { + set_audio_mapping (_playlist->default_audio_mapping ()); + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); + } +} diff --combined src/lib/format.cc index 3d18edb3e,8c3d0d8ad..a83d53ebd --- a/src/lib/format.cc +++ b/src/lib/format.cc @@@ -29,7 -29,6 +29,7 @@@ #include #include "format.h" #include "film.h" +#include "playlist.h" #include "i18n.h" @@@ -51,7 -50,7 +51,7 @@@ FixedFormat::name () cons s << _nickname << N_(" ("); } - s << setprecision(3) << (_ratio / 100.0) << N_(":1"); + s << setprecision(3) << _ratio << N_(":1"); if (!_nickname.empty ()) { s << N_(")"); @@@ -73,51 -72,51 +73,51 @@@ Format::setup_formats ( { /// 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 ( @@@ -182,12 -181,12 +182,12 @@@ Format::all ( 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) { @@@ -200,7 -199,7 +200,7 @@@ int Format::dcp_padding (shared_ptr 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) { @@@ -211,7 -210,7 +211,7 @@@ } float - Format::container_ratio_as_float () const + Format::container_ratio () const { return static_cast (_dcp_size.width) / _dcp_size.height; } @@@ -222,16 -221,10 +222,10 @@@ VariableFormat::VariableFormat (libdcp: } - int - VariableFormat::ratio_as_integer (shared_ptr f) const - { - return rint (ratio_as_float (f) * 100); - } - float - VariableFormat::ratio_as_float (shared_ptr f) const + VariableFormat::ratio (shared_ptr 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; } diff --combined src/lib/format.h index 7958ca534,e95306232..f240c89fc --- a/src/lib/format.h +++ b/src/lib/format.h @@@ -38,18 -38,10 +38,10 @@@ public , _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 f) const = 0; - - /** @return the ratio as a floating point number */ - virtual float ratio_as_float (boost::shared_ptr 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 f) const; + int dcp_padding (boost::shared_ptr) const; /** @return size in pixels of the images that we should * put in a DCP for this ratio. This size will not correspond @@@ -84,6 -76,9 +76,9 @@@ static void setup_formats (); protected: + /** @return the ratio */ + virtual float ratio (boost::shared_ptr 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. @@@ -107,22 -102,17 +102,17 @@@ private 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 { + float ratio (boost::shared_ptr) const { return _ratio; } - float ratio_as_float (boost::shared_ptr) 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 @@@ -130,8 -120,7 +120,7 @@@ public: VariableFormat (libdcp::Size, std::string, std::string, std::string); - int ratio_as_integer (boost::shared_ptr f) const; - float ratio_as_float (boost::shared_ptr f) const; + float ratio (boost::shared_ptr f) const; std::string name () const; }; diff --combined src/lib/imagemagick_decoder.cc index 7049b7d6e,5ce22c296..3888347ca --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@@ -20,7 -20,6 +20,7 @@@ #include #include #include +#include "imagemagick_content.h" #include "imagemagick_decoder.h" #include "image.h" #include "film.h" @@@ -32,53 -31,64 +32,53 @@@ using std::cout using boost::shared_ptr; using libdcp::Size; -ImageMagickDecoder::ImageMagickDecoder ( - boost::shared_ptr f, DecodeOptions o) - : Decoder (f, o) - , VideoDecoder (f, o) +ImageMagickDecoder::ImageMagickDecoder (shared_ptr f, shared_ptr 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 (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); @@@ -90,11 -100,11 +90,10 @@@ 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; } @@@ -105,16 -115,44 +104,16 @@@ ImageMagickDecoder::pixel_format () con 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; } diff --combined src/lib/imagemagick_decoder.h index 40a89bb15,80a08f81f..e7c9dee9a --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@@ -23,24 -23,37 +23,24 @@@ namespace Magick class Image; } +class ImageMagickContent; + class ImageMagickDecoder : public VideoDecoder { public: - ImageMagickDecoder (boost::shared_ptr, DecodeOptions); - - float frames_per_second () const; + ImageMagickDecoder (boost::shared_ptr, boost::shared_ptr); - 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 { @@@ -62,6 -75,10 +62,7 @@@ } private: - void film_changed (Film::Property); - - std::list _files; - std::list::iterator _iter; - + boost::shared_ptr _imagemagick_content; + boost::shared_ptr _image; + ContentVideoFrame _position; }; diff --combined src/lib/matcher.cc index 77ed9b651,34ddc86d6..edbb084de --- a/src/lib/matcher.cc +++ b/src/lib/matcher.cc @@@ -24,35 -24,97 +24,97 @@@ #include "i18n.h" using std::min; + using std::cout; + using std::list; using boost::shared_ptr; Matcher::Matcher (shared_ptr 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 i, bool same, shared_ptr s) -Matcher::process_video (boost::shared_ptr image, bool same, boost::shared_ptr sub, double t) ++Matcher::process_video (shared_ptr image, bool same, boost::shared_ptr 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 b) -Matcher::process_audio (boost::shared_ptr b, double t) ++Matcher::process_audio (shared_ptr 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 @@@ -62,39 -124,58 +124,58 @@@ Matcher::process_end ( /* 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::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 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()); + ++_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. @@@ -103,7 -184,6 +184,6 @@@ shared_ptr 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); @@@ -113,3 -193,16 +193,16 @@@ } } } + + 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; + } + diff --combined src/lib/player.cc index c77059b0a,000000000..635b67cad mode 100644,000000..100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@@ -1,272 -1,0 +1,283 @@@ +/* + Copyright (C) 2013 Carl Hetherington + + 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 f, shared_ptr p) + : _film (f) + , _playlist (p) + , _video (true) + , _audio (true) + , _subtitles (true) + , _have_valid_decoders (false) - , _video_sync (true) +{ + _playlist->Changed.connect (bind (&Player::playlist_changed, this)); + _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2)); +} + +void +Player::disable_video () +{ + _video = false; +} + +void +Player::disable_audio () +{ + _audio = false; +} + +void +Player::disable_subtitles () +{ + _subtitles = false; +} + +bool +Player::pass () +{ + if (!_have_valid_decoders) { + setup_decoders (); + _have_valid_decoders = true; + } + + bool done = true; + + if (_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 >::iterator i = _sndfile_decoders.begin(); i != _sndfile_decoders.end(); ++i) { + if (!(*i)->pass ()) { + done = false; + } + } + - Audio (_audio_buffers); ++ Audio (_audio_buffers, _audio_time.get()); + _audio_buffers.reset (); ++ _audio_time = boost::none; + } + + return done; +} + +void +Player::set_progress (shared_ptr 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 >::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 - Player::process_video (shared_ptr i, bool same, shared_ptr s) ++Player::process_video (shared_ptr i, bool same, shared_ptr s, double t) +{ - Video (i, same, s); ++ /* XXX: this time will need mangling to add on the offset of the start of the content */ ++ Video (i, same, s, t); +} + +void - Player::process_audio (weak_ptr c, shared_ptr b) ++Player::process_audio (weak_ptr c, shared_ptr 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 dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i)); + for (list::iterator j = dcp.begin(); j != dcp.end(); ++j) { + _audio_buffers->accumulate (b, i, static_cast (*j)); + } + } + + if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { + /* We can just emit this audio now as it will all be here */ - Audio (_audio_buffers); ++ 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 > vc = _playlist->video (); + for (list >::iterator i = vc.begin(); i != vc.end(); ++i) { + + shared_ptr d; + + /* XXX: into content? */ + + shared_ptr fc = dynamic_pointer_cast (*i); + if (fc) { + shared_ptr fd ( + new FFmpegDecoder ( + _film, fc, _video, + _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG, - _subtitles, - _video_sync ++ _subtitles + ) + ); + + if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { - fd->Audio.connect (bind (&Player::process_audio, this, fc, _1)); ++ fd->Audio.connect (bind (&Player::process_audio, this, fc, _1, _2)); + } + + d = fd; + } + + shared_ptr ic = dynamic_pointer_cast (*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 > sc = _playlist->sndfile (); + for (list >::iterator i = sc.begin(); i != sc.end(); ++i) { + shared_ptr d (new SndfileDecoder (_film, *i)); + _sndfile_decoders.push_back (d); - d->Audio.connect (bind (&Player::process_audio, this, *i, _1)); ++ d->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2)); + } + } +} + - void - Player::disable_video_sync () - { - _video_sync = false; - } - +double +Player::last_video_time () const +{ + double t = 0; + for (list >::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 w, int p) +{ + shared_ptr c = w.lock (); + if (!c) { + return; + } + + if (p == VideoContentProperty::VIDEO_LENGTH) { + if (dynamic_pointer_cast (c)) { + /* FFmpeg content length changes are serious; we need new decoders */ + _have_valid_decoders = false; + } + } +} + +void +Player::playlist_changed () +{ + _have_valid_decoders = false; +} diff --combined src/lib/player.h index 9a55b8599,000000000..539fae67a mode 100644,000000..100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@@ -1,78 -1,0 +1,78 @@@ +/* + Copyright (C) 2013 Carl Hetherington + + 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 +#include +#include +#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; + - class Player : public VideoSource, public AudioSource, public VideoSink, public boost::enable_shared_from_this ++class Player : public TimedVideoSource, public TimedAudioSource, public TimedVideoSink, public boost::enable_shared_from_this +{ +public: + Player (boost::shared_ptr, boost::shared_ptr); + + void disable_video (); + void disable_audio (); + void disable_subtitles (); - void disable_video_sync (); + + bool pass (); + void set_progress (boost::shared_ptr); + bool seek (double); ++ void seek_back (); ++ void seek_forward (); + + double last_video_time () const; + +private: - void process_video (boost::shared_ptr i, bool same, boost::shared_ptr s); - void process_audio (boost::weak_ptr, boost::shared_ptr); ++ void process_video (boost::shared_ptr i, bool same, boost::shared_ptr s, double); ++ void process_audio (boost::weak_ptr, boost::shared_ptr, double); + void setup_decoders (); + void playlist_changed (); + void content_changed (boost::weak_ptr, int); + + boost::shared_ptr _film; + boost::shared_ptr _playlist; + + bool _video; + bool _audio; + bool _subtitles; + + bool _have_valid_decoders; + std::list > _video_decoders; + std::list >::iterator _video_decoder; + std::list > _sndfile_decoders; + + boost::shared_ptr _audio_buffers; - - bool _video_sync; ++ boost::optional _audio_time; +}; + +#endif diff --combined src/lib/processor.h index 5dbafab7f,603239f8f..7b7735faa --- a/src/lib/processor.h +++ b/src/lib/processor.h @@@ -21,8 -21,8 +21,8 @@@ * @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" @@@ -67,6 -67,15 +67,15 @@@ public {} }; + class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink + { + public: + TimedAudioVideoProcessor (boost::shared_ptr log) + : Processor (log) + {} + }; + + /** @class AudioProcessor * @brief A processor which handles just audio data. */ @@@ -95,4 -104,12 +104,12 @@@ public {} }; + class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink + { + public: + TimedVideoProcessor (boost::shared_ptr log) + : Processor (log) + {} + }; + #endif diff --combined src/lib/sndfile_decoder.cc index c7311112a,af59c049c..4db45f1d4 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@@ -19,7 -19,6 +19,7 @@@ #include #include +#include "sndfile_content.h" #include "sndfile_decoder.h" #include "film.h" #include "exceptions.h" @@@ -28,62 -27,163 +28,64 @@@ 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 f, DecodeOptions o) - : Decoder (f, o) - , AudioDecoder (f, o) +SndfileDecoder::SndfileDecoder (shared_ptr f, shared_ptr c) + : Decoder (f) + , AudioDecoder (f) + , _sndfile_content (c) { - sf_count_t frames; - vector sf = open_files (frames); - close_files (sf); -} - -vector -SndfileDecoder::open_files (sf_count_t & frames) -{ - vector 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 (); - } - - bool first = true; - frames = 0; - - vector 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 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 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 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 const & sndfiles) -{ - for (size_t i = 0; i < sndfiles.size(); ++i) { - sf_close (sndfiles[i]); - } -} - -shared_ptr -SndfileStream::create () -{ - return shared_ptr (new SndfileStream); -} - -shared_ptr -SndfileStream::create (string t, optional v) -{ - if (!v) { - /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ - return shared_ptr (); - } - - stringstream s (t); - string type; - s >> type; - if (type != N_("external")) { - return shared_ptr (); - } - - return shared_ptr (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 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 v) +int +SndfileDecoder::audio_channels () const { - assert (v); - - stringstream s (t); - string type; - s >> type >> _sample_rate >> _channel_layout; + return _info.channels; } -SndfileStream::SndfileStream () +ContentAudioFrame +SndfileDecoder::audio_length () const { - + return _info.frames; } -string -SndfileStream::to_string () const +int +SndfileDecoder::audio_frame_rate () const { - return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout); + return _info.samplerate; } diff --combined src/lib/sndfile_decoder.h index 2900afea0,e16eab673..b999a66d1 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@@ -20,27 -20,35 +20,28 @@@ #include #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 create (); - static boost::shared_ptr create (std::string t, boost::optional v); - -private: - friend class stream_test; - - SndfileStream (); - SndfileStream (std::string t, boost::optional v); -}; +class SndfileContent; class SndfileDecoder : public AudioDecoder { public: - SndfileDecoder (boost::shared_ptr, DecodeOptions); + SndfileDecoder (boost::shared_ptr, boost::shared_ptr); + ~SndfileDecoder (); bool pass (); + int audio_channels () const; + ContentAudioFrame audio_length () const; + int audio_frame_rate () const; + private: - std::vector open_files (sf_count_t &); - void close_files (std::vector const &); + SNDFILE* open_file (sf_count_t &); + void close_file (SNDFILE*); + + boost::shared_ptr _sndfile_content; + SNDFILE* _sndfile; + SF_INFO _info; ++ ContentAudioFrame _done; + ContentAudioFrame _remaining; }; diff --combined src/lib/transcoder.cc index 2a8ce5044,23fb5b788..ea3f27ad8 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@@ -28,13 -28,14 +28,13 @@@ #include #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; @@@ -42,70 -43,72 +42,57 @@@ using boost::dynamic_pointer_cast /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. - * @param o Decode options. * @param j Job that we are running under, or 0. * @param e Encoder to use. */ -Transcoder::Transcoder (shared_ptr f, DecodeOptions o, Job* j, shared_ptr e) +Transcoder::Transcoder (shared_ptr f, shared_ptr 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 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 (_decoders.audio) != dynamic_pointer_cast (_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 (); +} diff --combined src/lib/util.cc index ad08c6ab4,e43b598ab..56932720c --- a/src/lib/util.cc +++ b/src/lib/util.cc @@@ -27,7 -27,7 +27,7 @@@ #include #include #include -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX #include #include #endif @@@ -63,26 -63,8 +63,26 @@@ extern "C" #include "i18n.h" -using namespace std; -using namespace boost; +using std::string; +using std::stringstream; +using std::setfill; +using std::ostream; +using std::endl; +using std::vector; +using std::hex; +using std::setw; +using std::ifstream; +using std::ios; +using std::min; +using std::max; +using std::list; +using std::multimap; +using std::istream; +using std::numeric_limits; +using std::pair; +using boost::shared_ptr; +using boost::thread; +using boost::lexical_cast; using libdcp::Size; thread::id ui_thread; @@@ -148,7 -130,7 +148,7 @@@ seconds_to_approximate_hms (int s return ap.str (); } -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX /** @param l Mangled C++ identifier. * @return Demangled version. */ @@@ -247,11 -229,11 +247,11 @@@ seconds (struct timeval t 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 (); @@@ -261,10 -243,10 +261,10 @@@ 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 () { @@@ -279,9 -261,9 +279,9 @@@ #endif void -dvdomatic_setup_i18n (string lang) +dcpomatic_setup_i18n (string lang) { -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX lang += ".UTF8"; #endif @@@ -297,15 -279,15 +297,15 @@@ } 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 } @@@ -366,11 -348,11 +366,11 @@@ md5_digest (void const * data, int size * @return MD5 digest of file's contents. */ string -md5_digest (string file) +md5_digest (boost::filesystem::path file) { - ifstream f (file.c_str(), ios::binary); + ifstream f (file.string().c_str(), ios::binary); if (!f.good ()) { - throw OpenFileError (file); + throw OpenFileError (file.string()); } f.seekg (0, ios::end); @@@ -495,6 -477,16 +495,6 @@@ dcp_audio_sample_rate (int fs return 96000; } -bool operator== (Crop const & a, Crop const & b) -{ - return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom); -} - -bool operator!= (Crop const & a, Crop const & b) -{ - return !(a == b); -} - /** @param index Colour LUT index. * @return Human-readable name. */ @@@ -517,16 -509,16 +517,16 @@@ Socket::Socket (int timeout , _socket (_io_service) , _timeout (timeout) { - _deadline.expires_at (posix_time::pos_infin); + _deadline.expires_at (boost::posix_time::pos_infin); check (); } void Socket::check () { - if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) { + if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) { _socket.close (); - _deadline.expires_at (posix_time::pos_infin); + _deadline.expires_at (boost::posix_time::pos_infin); } _deadline.async_wait (boost::bind (&Socket::check, this)); @@@ -536,14 -528,14 +536,14 @@@ * @param endpoint End-point to connect to. */ void -Socket::connect (asio::ip::basic_resolver_entry const & endpoint) +Socket::connect (boost::asio::ip::basic_resolver_entry 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")); @@@ -557,14 -549,14 +557,14 @@@ 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 ()); @@@ -585,14 -577,14 +585,14 @@@ Socket::write (uint32_t v void Socket::read (uint8_t* data, int size) { - _deadline.expires_from_now (posix_time::seconds (_timeout)); - system::error_code ec = asio::error::would_block; + _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); + boost::system::error_code ec = boost::asio::error::would_block; - asio::async_read (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1); + boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1); do { _io_service.run_one (); - } while (ec == asio::error::would_block); + } while (ec == boost::asio::error::would_block); if (ec) { throw NetworkError (ec.message ()); @@@ -869,39 -861,37 +869,39 @@@ AudioBuffers::move (int from, int to, i } } +/** Add data from from `from', `from_channel' to our channel `to_channel' */ +void +AudioBuffers::accumulate (shared_ptr 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 cpu_info () @@@ -909,7 -899,7 +909,7 @@@ pair info; info.second = 0; -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX ifstream f (N_("/proc/cpuinfo")); while (f.good ()) { string l; @@@ -948,6 -938,58 +948,6 @@@ audio_channel_name (int c return channels[c]; } -AudioMapping::AudioMapping (int c) - : _source_channels (c) -{ - -} - -optional -AudioMapping::source_to_dcp (int c) const -{ - if (c >= _source_channels) { - return optional (); - } - - if (_source_channels == 1) { - /* mono sources to centre */ - return libdcp::CENTRE; - } - - return static_cast (c); -} - -optional -AudioMapping::dcp_to_source (libdcp::Channel c) const -{ - if (_source_channels == 1) { - if (c == libdcp::CENTRE) { - return 0; - } else { - return optional (); - } - } - - if (static_cast (c) >= _source_channels) { - return optional (); - } - - return static_cast (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) @@@ -976,3 -1018,22 +976,22 @@@ } } } + + 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); + } diff --combined src/lib/util.h index 065801a88,31d0fc967..02cc742aa --- a/src/lib/util.h +++ b/src/lib/util.h @@@ -22,8 -22,8 +22,8 @@@ * @brief Some utility functions and classes. */ -#ifndef DVDOMATIC_UTIL_H -#define DVDOMATIC_UTIL_H +#ifndef DCPOMATIC_UTIL_H +#define DCPOMATIC_UTIL_H #include #include @@@ -37,9 -37,8 +37,9 @@@ extern "C" #include } #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(...) @@@ -55,17 -54,19 +55,17 @@@ extern std::string seconds_to_approxima 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 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); @@@ -103,6 -104,87 +103,6 @@@ 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); @@@ -117,7 -199,7 +117,7 @@@ extern std::string get_optional_string /** @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. @@@ -182,7 -264,6 +182,7 @@@ public void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset); void move (int from, int to, int frames); + void accumulate (boost::shared_ptr, int, int); private: /** Number of channels */ @@@ -195,8 -276,33 +195,19 @@@ float** _data; }; -class AudioMapping -{ -public: - AudioMapping (int); - - boost::optional source_to_dcp (int c) const; - boost::optional 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 cpu_info (); + class LocaleGuard + { + public: + LocaleGuard (); + ~LocaleGuard (); + + private: + char* _old; + }; + + #endif diff --combined src/lib/video_decoder.cc index 99d711693,16a076698..fd8238441 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@@ -22,17 -22,19 +22,18 @@@ #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 f, DecodeOptions o) - : Decoder (f, o) +VideoDecoder::VideoDecoder (shared_ptr f) + : Decoder (f) , _video_frame (0) - , _last_source_time (0) + , _last_content_time (0) { } @@@ -43,52 -45,17 +44,18 @@@ * @param t Time of the frame within the source, in seconds. */ void - VideoDecoder::emit_video (shared_ptr image, double t) + VideoDecoder::emit_video (shared_ptr image, bool same, double t) { shared_ptr 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, bool same, shared_ptr 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 @@@ -107,12 -74,21 +74,12 @@@ VideoDecoder::emit_subtitle (shared_ptr } } -/** Set which stream of subtitles we should use from our source. - * @param s Stream to use. - */ -void -VideoDecoder::set_subtitle_stream (shared_ptr s) -{ - _subtitle_stream = s; -} - void VideoDecoder::set_progress (Job* j) const { assert (j); - - if (_film->length()) { - j->set_progress (float (_video_frame) / _film->length().get()); + + if (_film->video_length()) { + j->set_progress (float (_video_frame) / _film->video_length()); } } diff --combined src/lib/video_decoder.h index 23817c055,6e4fd48c0..0b05b2f71 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@@ -17,60 -17,67 +17,53 @@@ */ -#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 VideoContent; + - class VideoDecoder : public VideoSource, public virtual Decoder + class VideoDecoder : public TimedVideoSource, public virtual Decoder { public: - VideoDecoder (boost::shared_ptr, DecodeOptions); + VideoDecoder (boost::shared_ptr); - /** @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); - void set_progress (Job *) const; int video_frame () const { return _video_frame; } - boost::shared_ptr subtitle_stream () const { - return _subtitle_stream; - } - - std::vector > 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, double); + void emit_video (boost::shared_ptr, bool, double); void emit_subtitle (boost::shared_ptr); - bool have_last_video () const; - void repeat_last_video (double); - /** Subtitle stream to use when decoding */ - boost::shared_ptr _subtitle_stream; - /** Subtitle streams that this decoder's content has */ - std::vector > _subtitle_streams; - private: - void signal_video (boost::shared_ptr, bool, boost::shared_ptr, double); - int _video_frame; - double _last_source_time; + double _last_content_time; boost::shared_ptr _timed_subtitle; - - boost::shared_ptr _last_image; - boost::shared_ptr _last_subtitle; }; #endif diff --combined src/lib/video_sink.h index c68651005,32c7f3b38..167d3980e --- a/src/lib/video_sink.h +++ b/src/lib/video_sink.h @@@ -17,8 -17,8 +17,8 @@@ */ -#ifndef DVDOMATIC_VIDEO_SINK_H -#define DVDOMATIC_VIDEO_SINK_H +#ifndef DCPOMATIC_VIDEO_SINK_H +#define DCPOMATIC_VIDEO_SINK_H #include #include "util.h" @@@ -37,4 -37,16 +37,16 @@@ public virtual void process_video (boost::shared_ptr i, bool same, boost::shared_ptr 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 i, bool same, boost::shared_ptr s, double t) = 0; + }; + #endif diff --combined src/lib/video_source.cc index 1c4d6466c,af6f941fd..ccb76f020 --- a/src/lib/video_source.cc +++ b/src/lib/video_source.cc @@@ -21,23 -21,16 +21,29 @@@ #include "video_sink.h" using boost::shared_ptr; +using boost::weak_ptr; using boost::bind; +static void +process_video_proxy (weak_ptr sink, shared_ptr i, bool same, shared_ptr s) +{ + shared_ptr p = sink.lock (); + if (p) { + p->process_video (i, same, s); + } +} + void VideoSource::connect_video (shared_ptr 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 (s), _1, _2, _3)); } + + void + TimedVideoSource::connect_video (shared_ptr s) + { + Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4)); + } diff --combined src/lib/video_source.h index e7c9805ef,705b0023a..d2aa045a7 --- a/src/lib/video_source.h +++ b/src/lib/video_source.h @@@ -21,19 -21,20 +21,20 @@@ * @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 #include #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 { @@@ -49,4 -50,22 +50,22 @@@ public void connect_video (boost::shared_ptr); }; + /** @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, bool, boost::shared_ptr, double)> Video; + + void connect_video (boost::shared_ptr); + }; + #endif diff --combined src/wx/film_viewer.cc index cba19c07c,8508ec2a2..688be3bd0 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@@ -28,16 -28,13 +28,16 @@@ #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" @@@ -48,14 -45,16 +48,18 @@@ using std::max 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 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) @@@ -71,11 -70,23 +75,23 @@@ _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); @@@ -83,6 -94,8 +99,8 @@@ _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); @@@ -100,24 -113,44 +118,22 @@@ FilmViewer::film_changed (Film::Propert 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; @@@ -130,7 -163,7 +146,7 @@@ FilmViewer::set_film (shared_ptr if (_film == f) { return; } - + _film = f; _raw_frame.reset (); @@@ -142,29 -175,20 +158,28 @@@ return; } + _player = f->player (); + _player->disable_audio (); - _player->disable_video_sync (); + /* 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)); ++ _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; } @@@ -176,7 -200,7 +191,7 @@@ void FilmViewer::timer (wxTimerEvent &) { - if (!_film || !_decoders.video) { + if (!_player) { return; } @@@ -185,8 -209,8 +200,8 @@@ 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); } @@@ -242,11 -266,11 +257,11 @@@ FilmViewer::paint_panel (wxPaintEvent & 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; } @@@ -284,7 -308,7 +299,7 @@@ FilmViewer::raw_to_display ( return; } - boost::shared_ptr input = _raw_frame; + shared_ptr input = _raw_frame; pair const s = Filter::ffmpeg_strings (_film->filters()); if (!s.second.empty ()) { @@@ -300,7 -324,7 +315,7 @@@ 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, @@@ -319,14 -343,14 +334,14 @@@ void FilmViewer::calculate_sizes () { - if (!_film) { + if (!_film || !_player) { return; } Format const * format = _film->format (); float const panel_ratio = static_cast (_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 */ @@@ -369,14 -393,14 +384,14 @@@ FilmViewer::check_play_state ( } 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, bool, shared_ptr sub) + FilmViewer::process_video (shared_ptr image, bool, shared_ptr sub, double t) { _raw_frame = image; _raw_sub = sub; @@@ -384,26 -408,36 +399,39 @@@ 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. */ @@@ -438,12 -472,28 +466,38 @@@ FilmViewer::active_jobs_changed (bool a _play_button->Enable (!a); } +void +FilmViewer::film_content_changed (weak_ptr, int p) +{ + if (p == VideoContentProperty::VIDEO_LENGTH) { + /* Force an update to our frame */ + wxScrollEvent ev; + slider_moved (ev); + } +} ++ + void + FilmViewer::back_clicked (wxCommandEvent &) + { - if (!_decoders.video) { ++ if (!_player) { + return; + } + - _decoders.video->seek_back (); ++ _player->seek_back (); + get_frame (); + _panel->Refresh (); + _panel->Update (); + } + + void + FilmViewer::forward_clicked (wxCommandEvent &) + { - if (!_decoders.video) { ++ if (!_player) { + return; + } + - _decoders.video->seek_forward (); ++ _player->seek_forward (); + get_frame (); + _panel->Refresh (); + _panel->Update (); + } diff --combined src/wx/film_viewer.h index c81c65acd,a78c772a4..814a095af --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@@ -23,6 -23,7 +23,6 @@@ #include #include "lib/film.h" -#include "lib/decoder_factory.h" class wxToggleButton; class FFmpegPlayer; @@@ -32,25 -33,6 +32,25 @@@ class Subtitle /** @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 { @@@ -61,30 -43,35 +61,36 @@@ public private: void film_changed (Film::Property); + void film_content_changed (boost::weak_ptr, 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, bool, boost::shared_ptr); + void process_video (boost::shared_ptr, bool, boost::shared_ptr, 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; + boost::shared_ptr _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 _raw_frame; boost::shared_ptr _raw_sub; boost::shared_ptr _display_frame; diff --combined test/test.cc index a2c0de250,b0b2cef7d..46fadd570 --- a/test/test.cc +++ b/test/test.cc @@@ -28,7 -28,6 +28,6 @@@ #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" @@@ -40,10 -39,9 +39,10 @@@ #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 using std::string; @@@ -91,7 -89,7 +90,7 @@@ new_test_film (string name 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); @@@ -193,7 -191,7 +192,7 @@@ BOOST_AUTO_TEST_CASE (film_metadata_tes 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); @@@ -206,7 -204,7 +205,7 @@@ 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; @@@ -228,25 -226,86 +227,27 @@@ 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 (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 (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 (1)); - BOOST_CHECK_EQUAL (s.id(), 5); - BOOST_CHECK_EQUAL (s.name(), "a b c"); - - shared_ptr ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional (1)); - shared_ptr cff = dynamic_pointer_cast (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 fe = audio_stream_factory ("external 44100 1", boost::optional (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 ()), 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 ()), 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 (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"; @@@ -270,101 -329,6 +271,6 @@@ public void do_log (string) {} }; - void - do_positive_delay_line_test (int delay_length, int data_length) - { - shared_ptr log (new NullLog); - - DelayLine d (log, 6, delay_length); - shared_ptr 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 log (new NullLog); - - DelayLine d (log, 6, delay_length); - shared_ptr 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"); @@@ -373,6 -337,17 +279,6 @@@ BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError); } -BOOST_AUTO_TEST_CASE (paths_test) -{ - shared_ptr 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 frame, ServerDescription* description, shared_ptr locally_encoded) { @@@ -442,7 -417,7 +348,7 @@@ BOOST_AUTO_TEST_CASE (client_server_tes new thread (boost::bind (&Server::run, server, 2)); /* Let the server get itself ready */ - dvdomatic_sleep (1); + dcpomatic_sleep (1); ServerDescription description ("localhost", 2); @@@ -464,14 -439,14 +370,14 @@@ BOOST_AUTO_TEST_CASE (make_dcp_test { shared_ptr 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); @@@ -494,15 -469,15 +400,15 @@@ BOOST_AUTO_TEST_CASE (make_dcp_with_ran { shared_ptr 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); @@@ -656,44 -631,44 +562,44 @@@ BOOST_AUTO_TEST_CASE (audio_sampling_ra Config::instance()->set_allowed_dcp_frame_rates (afr); shared_ptr 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 (new FFmpegAudioStream ("a", 42, 48000, 0))); +// f->set_content_audio_stream (shared_ptr (new FFmpegAudioStream ("a", 42, 48000, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); - f->set_content_audio_stream (shared_ptr (new FFmpegAudioStream ("a", 42, 44100, 0))); +// f->set_content_audio_stream (shared_ptr (new FFmpegAudioStream ("a", 42, 44100, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); - f->set_content_audio_stream (shared_ptr (new FFmpegAudioStream ("a", 42, 80000, 0))); +// f->set_content_audio_stream (shared_ptr (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 (new FFmpegAudioStream ("a", 42, 48000, 0))); +// f->set_content_audio_stream (shared_ptr (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 (new FFmpegAudioStream ("a", 42, 48000, 0))); +// f->set_content_audio_stream (shared_ptr (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 (new FFmpegAudioStream ("a", 42, 48000, 0))); +// f->set_content_audio_stream (shared_ptr (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 (new FFmpegAudioStream ("a", 42, 44100, 0))); +// f->set_content_audio_stream (shared_ptr (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 (new FFmpegAudioStream ("a", 42, 16000, 0))); +// f->set_content_audio_stream (shared_ptr (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. */ @@@ -739,10 -714,10 +645,10 @@@ BOOST_AUTO_TEST_CASE (job_manager_test shared_ptr 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); } diff --combined wscript index f784d760d,a9a057367..5288cab14 --- a/wscript +++ b/wscript @@@ -2,8 -2,8 +2,8 @@@ import subproces import os import sys -APPNAME = 'dvdomatic' +APPNAME = 'dcpomatic' - VERSION = '0.84pre' + VERSION = '0.85pre' def options(opt): opt.load('compiler_cxx') @@@ -25,7 -25,7 +25,7 @@@ def configure(conf) '-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: @@@ -35,9 -35,9 +35,9 @@@ 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') @@@ -50,13 -50,12 +50,13 @@@ 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) @@@ -73,9 -72,6 +73,9 @@@ 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 @@@ -228,16 -224,16 +228,16 @@@ def build(bld) 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) @@@ -259,8 -255,8 +259,8 @@@ def create_version_cc(version) 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)