summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/ab_transcoder.cc88
-rw-r--r--src/lib/ab_transcoder.h20
-rw-r--r--src/lib/audio_decoder.cc36
-rw-r--r--src/lib/audio_decoder.h58
-rw-r--r--src/lib/audio_sink.h30
-rw-r--r--src/lib/audio_source.cc30
-rw-r--r--src/lib/audio_source.h42
-rw-r--r--src/lib/combiner.cc67
-rw-r--r--src/lib/combiner.h42
-rw-r--r--src/lib/config.h1
-rw-r--r--src/lib/decoder.cc259
-rw-r--r--src/lib/decoder.h82
-rw-r--r--src/lib/decoder_factory.cc25
-rw-r--r--src/lib/decoder_factory.h8
-rw-r--r--src/lib/delay_line.cc32
-rw-r--r--src/lib/delay_line.h10
-rw-r--r--src/lib/encoder.cc29
-rw-r--r--src/lib/encoder.h29
-rw-r--r--src/lib/examine_content_job.cc18
-rw-r--r--src/lib/examine_content_job.h5
-rw-r--r--src/lib/external_audio_decoder.cc185
-rw-r--r--src/lib/external_audio_decoder.h54
-rw-r--r--src/lib/ffmpeg_decoder.cc296
-rw-r--r--src/lib/ffmpeg_decoder.h64
-rw-r--r--src/lib/film.cc331
-rw-r--r--src/lib/film.h94
-rw-r--r--src/lib/filter_graph.cc42
-rw-r--r--src/lib/filter_graph.h19
-rw-r--r--src/lib/gain.cc45
-rw-r--r--src/lib/gain.h31
-rw-r--r--src/lib/image.cc80
-rw-r--r--src/lib/image.h32
-rw-r--r--src/lib/imagemagick_decoder.cc58
-rw-r--r--src/lib/imagemagick_decoder.h19
-rw-r--r--src/lib/imagemagick_encoder.cc16
-rw-r--r--src/lib/imagemagick_encoder.h7
-rw-r--r--src/lib/j2k_still_encoder.cc6
-rw-r--r--src/lib/j2k_still_encoder.h7
-rw-r--r--src/lib/j2k_wav_encoder.cc83
-rw-r--r--src/lib/j2k_wav_encoder.h6
-rw-r--r--src/lib/job.cc2
-rw-r--r--src/lib/make_dcp_job.cc2
-rw-r--r--src/lib/matcher.cc113
-rw-r--r--src/lib/matcher.h39
-rw-r--r--src/lib/processor.h96
-rw-r--r--src/lib/stream.cc70
-rw-r--r--src/lib/stream.h104
-rw-r--r--src/lib/tiff_decoder.cc220
-rw-r--r--src/lib/tiff_decoder.h71
-rw-r--r--src/lib/transcode_job.cc3
-rw-r--r--src/lib/transcoder.cc73
-rw-r--r--src/lib/transcoder.h21
-rw-r--r--src/lib/util.cc101
-rw-r--r--src/lib/util.h38
-rw-r--r--src/lib/video_decoder.cc98
-rw-r--r--src/lib/video_decoder.h82
-rw-r--r--src/lib/video_sink.h39
-rw-r--r--src/lib/video_source.cc30
-rw-r--r--src/lib/video_source.h51
-rw-r--r--src/lib/wscript9
-rw-r--r--src/tools/dvdomatic.cc6
-rw-r--r--src/tools/test.cc19
-rw-r--r--src/wx/film_editor.cc554
-rw-r--r--src/wx/film_editor.h27
-rw-r--r--src/wx/wx_util.cc43
-rw-r--r--src/wx/wx_util.h4
66 files changed, 2800 insertions, 1501 deletions
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc
index c40c0916e..537cb4dd7 100644
--- a/src/lib/ab_transcoder.cc
+++ b/src/lib/ab_transcoder.cc
@@ -21,12 +21,17 @@
#include <boost/shared_ptr.hpp>
#include "ab_transcoder.h"
#include "film.h"
-#include "decoder.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 "matcher.h"
+#include "delay_line.h"
+#include "gain.h"
+#include "combiner.h"
/** @file src/ab_transcoder.cc
* @brief A transcoder which uses one Film for the left half of the screen, and a different one
@@ -50,71 +55,66 @@ ABTranscoder::ABTranscoder (
, _opt (o)
, _job (j)
, _encoder (e)
- , _last_frame (0)
{
_da = decoder_factory (_film_a, o, j);
_db = decoder_factory (_film_b, o, j);
- _da->Video.connect (bind (&ABTranscoder::process_video, this, _1, _2, _3, 0));
- _db->Video.connect (bind (&ABTranscoder::process_video, this, _1, _2, _3, 1));
- _da->Audio.connect (bind (&Encoder::process_audio, e, _1, _2));
-}
+ if (_film_a->audio_stream()) {
+ shared_ptr<AudioStream> st = _film_a->audio_stream();
+ _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->frames_per_second()));
+ _delay_line.reset (new DelayLine (_film_a->log(), st->channels(), _film_a->audio_delay() * st->sample_rate() / 1000));
+ _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
+ }
-ABTranscoder::~ABTranscoder ()
-{
+ /* Set up the decoder to use the film's set streams */
+ _da.first->set_subtitle_stream (_film_a->subtitle_stream ());
+ _db.first->set_subtitle_stream (_film_a->subtitle_stream ());
+ _da.second->set_audio_stream (_film_a->audio_stream ());
-}
+ _da.first->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2));
+ _db.first->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2));
-void
-ABTranscoder::process_video (shared_ptr<Image> yuv, SourceFrame frame, shared_ptr<Subtitle> sub, int index)
-{
- if (index == 0) {
- /* Keep this image around until we get the other half */
- _image = yuv;
+ if (_matcher) {
+ _combiner->connect_video (_matcher);
+ _matcher->connect_video (_encoder);
} else {
- /* Copy the right half of yuv into _image */
- for (int i = 0; i < yuv->components(); ++i) {
- int const line_size = yuv->line_size()[i];
- int const half_line_size = line_size / 2;
- int const stride = yuv->stride()[i];
-
- uint8_t* p = _image->data()[i];
- uint8_t* q = yuv->data()[i];
-
- for (int j = 0; j < yuv->lines (i); ++j) {
- memcpy (p + half_line_size, q + half_line_size, half_line_size);
- p += stride;
- q += stride;
- }
- }
-
- /* And pass it to the encoder */
- _encoder->process_video (_image, frame, sub);
- _image.reset ();
+ _combiner->connect_video (_encoder);
}
- _last_frame = frame;
+ if (_matcher && _delay_line) {
+ _da.second->connect_audio (_delay_line);
+ _delay_line->connect_audio (_matcher);
+ _matcher->connect_audio (_gain);
+ _gain->connect_audio (_encoder);
+ }
}
-
void
ABTranscoder::go ()
{
- _encoder->process_begin (_da->audio_channel_layout());
- _da->process_begin ();
- _db->process_begin ();
+ _encoder->process_begin ();
while (1) {
- bool const a = _da->pass ();
- bool const b = _db->pass ();
+ bool const va = _da.first->pass ();
+ bool const vb = _db.first->pass ();
+ bool const a = _da.first->pass ();
+
+ _da.first->set_progress ();
- if (a && b) {
+ if (va && vb && a) {
break;
}
}
+ if (_delay_line) {
+ _delay_line->process_end ();
+ }
+ if (_matcher) {
+ _matcher->process_end ();
+ }
+ if (_gain) {
+ _gain->process_end ();
+ }
_encoder->process_end ();
- _da->process_end ();
- _db->process_end ();
}
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
index a136fd270..9b57e4f73 100644
--- a/src/lib/ab_transcoder.h
+++ b/src/lib/ab_transcoder.h
@@ -28,12 +28,17 @@
class Job;
class Encoder;
-class Decoder;
+class VideoDecoder;
+class AudioDecoder;
class Options;
class Image;
class Log;
class Subtitle;
class Film;
+class Matcher;
+class DelayLine;
+class Gain;
+class Combiner;
/** @class ABTranscoder
* @brief A transcoder which uses one Film for the left half of the screen, and a different one
@@ -50,20 +55,19 @@ public:
boost::shared_ptr<Encoder> e
);
- ~ABTranscoder ();
-
void go ();
private:
- void process_video (boost::shared_ptr<Image>, SourceFrame, boost::shared_ptr<Subtitle>, int);
-
boost::shared_ptr<Film> _film_a;
boost::shared_ptr<Film> _film_b;
boost::shared_ptr<const Options> _opt;
Job* _job;
boost::shared_ptr<Encoder> _encoder;
- boost::shared_ptr<Decoder> _da;
- boost::shared_ptr<Decoder> _db;
- SourceFrame _last_frame;
+ std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _da;
+ std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _db;
+ boost::shared_ptr<Combiner> _combiner;
+ boost::shared_ptr<Matcher> _matcher;
+ boost::shared_ptr<DelayLine> _delay_line;
+ boost::shared_ptr<Gain> _gain;
boost::shared_ptr<Image> _image;
};
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
new file mode 100644
index 000000000..70f0effd9
--- /dev/null
+++ b/src/lib/audio_decoder.cc
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_decoder.h"
+#include "stream.h"
+
+using boost::optional;
+using boost::shared_ptr;
+
+AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+ : Decoder (f, o, j)
+{
+
+}
+
+void
+AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+{
+ _audio_stream = s;
+}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
new file mode 100644
index 000000000..1570fe3b0
--- /dev/null
+++ b/src/lib/audio_decoder.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/lib/audio_decoder.h
+ * @brief Parent class for audio decoders.
+ */
+
+#ifndef DVDOMATIC_AUDIO_DECODER_H
+#define DVDOMATIC_AUDIO_DECODER_H
+
+#include "audio_source.h"
+#include "stream.h"
+#include "decoder.h"
+
+/** @class AudioDecoder.
+ * @brief Parent class for audio decoders.
+ */
+class AudioDecoder : public AudioSource, public virtual Decoder
+{
+public:
+ AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+
+ virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
+
+ /** @return Audio stream that we are using */
+ boost::shared_ptr<AudioStream> audio_stream () const {
+ return _audio_stream;
+ }
+
+ /** @return All available audio streams */
+ std::vector<boost::shared_ptr<AudioStream> > audio_streams () const {
+ return _audio_streams;
+ }
+
+protected:
+ /** Audio stream that we are using */
+ boost::shared_ptr<AudioStream> _audio_stream;
+ /** All available audio streams */
+ std::vector<boost::shared_ptr<AudioStream> > _audio_streams;
+};
+
+#endif
diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h
new file mode 100644
index 000000000..11d578a60
--- /dev/null
+++ b/src/lib/audio_sink.h
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_SINK_H
+#define DVDOMATIC_AUDIO_SINK_H
+
+class AudioSink
+{
+public:
+ /** Call with some audio data */
+ virtual void process_audio (boost::shared_ptr<AudioBuffers>) = 0;
+};
+
+#endif
diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc
new file mode 100644
index 000000000..53b0dda15
--- /dev/null
+++ b/src/lib/audio_source.cc
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_source.h"
+#include "audio_sink.h"
+
+using boost::shared_ptr;
+using boost::bind;
+
+void
+AudioSource::connect_audio (shared_ptr<AudioSink> s)
+{
+ Audio.connect (bind (&AudioSink::process_audio, s, _1));
+}
diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h
new file mode 100644
index 000000000..5a1510d3c
--- /dev/null
+++ b/src/lib/audio_source.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/audio_source.h
+ * @brief Parent class for classes which emit audio data.
+ */
+
+#ifndef DVDOMATIC_AUDIO_SOURCE_H
+#define DVDOMATIC_AUDIO_SOURCE_H
+
+#include <boost/signals2.hpp>
+
+class AudioBuffers;
+class AudioSink;
+
+/** A class that emits audio data */
+class AudioSource
+{
+public:
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>)> Audio;
+
+ void connect_audio (boost::shared_ptr<AudioSink>);
+};
+
+#endif
diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc
new file mode 100644
index 000000000..b85dbf288
--- /dev/null
+++ b/src/lib/combiner.cc
@@ -0,0 +1,67 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "combiner.h"
+#include "image.h"
+
+using boost::shared_ptr;
+
+Combiner::Combiner (Log* log)
+ : VideoProcessor (log)
+{
+
+}
+
+/** Process video for the left half of the frame.
+ * @param image Frame image.
+ * @param sub Subtitle (which will be ignored)
+ */
+void
+Combiner::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
+{
+ _image = image;
+}
+
+/** Process video for the right half of the frame.
+ * @param image Frame image.
+ * @param sub Subtitle (which will be put onto the whole frame)
+ */
+void
+Combiner::process_video_b (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
+{
+ /* Copy the right half of this image into our _image */
+ /* XXX: this should probably be in the Image class */
+ for (int i = 0; i < image->components(); ++i) {
+ int const line_size = image->line_size()[i];
+ int const half_line_size = line_size / 2;
+ int const stride = image->stride()[i];
+
+ uint8_t* p = _image->data()[i];
+ uint8_t* q = image->data()[i];
+
+ for (int j = 0; j < image->lines (i); ++j) {
+ memcpy (p + half_line_size, q + half_line_size, half_line_size);
+ p += stride;
+ q += stride;
+ }
+ }
+
+ Video (_image, sub);
+ _image.reset ();
+}
diff --git a/src/lib/combiner.h b/src/lib/combiner.h
new file mode 100644
index 000000000..78c9889b5
--- /dev/null
+++ b/src/lib/combiner.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/lib/combiner.h
+ * @brief Class for combining two video streams.
+ */
+
+#include "processor.h"
+
+/** @class Combiner
+ * @brief A class which can combine two video streams into one, with
+ * one image used for the left half of the screen and the other for
+ * the right.
+ */
+class Combiner : public VideoProcessor
+{
+public:
+ Combiner (Log* log);
+
+ void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s);
+ void process_video_b (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s);
+
+private:
+ /** The image that we are currently working on */
+ boost::shared_ptr<Image> _image;
+};
diff --git a/src/lib/config.h b/src/lib/config.h
index 5113236d6..4575cb54d 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -102,6 +102,7 @@ public:
return _tms_password;
}
+ /** @return The sound processor that we are using */
SoundProcessor const * sound_processor () const {
return _sound_processor;
}
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index c9d8f063a..2bacf58e7 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -43,273 +43,16 @@ using std::min;
using std::pair;
using std::list;
using boost::shared_ptr;
+using boost::optional;
/** @param f Film.
* @param o Options.
* @param j Job that we are running within, or 0
- * @param minimal true to do the bare minimum of work; just run through the content. Useful for acquiring
- * accurate frame counts as quickly as possible. This generates no video or audio output.
*/
Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j)
: _film (f)
, _opt (o)
, _job (j)
- , _video_frame (0)
- , _audio_frame (0)
- , _delay_line (0)
- , _delay_in_frames (0)
{
}
-
-Decoder::~Decoder ()
-{
- delete _delay_line;
-}
-
-/** Start off a decode processing run. This should only be called once on
- * a given Decoder object.
- */
-void
-Decoder::process_begin ()
-{
- _delay_in_frames = _film->audio_delay() * audio_sample_rate() / 1000;
- _delay_line = new DelayLine (audio_channels(), _delay_in_frames);
-}
-
-/** Finish off a decode processing run */
-void
-Decoder::process_end ()
-{
- if (_delay_in_frames < 0 && _opt->decode_audio && audio_channels()) {
- shared_ptr<AudioBuffers> b (new AudioBuffers (audio_channels(), -_delay_in_frames));
- b->make_silent ();
- emit_audio (b);
- }
-
- if (_opt->decode_audio && audio_channels()) {
-
- /* Ensure that our video and audio emissions are the same length */
-
- int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frame, audio_sample_rate(), frames_per_second()) - _audio_frame;
-
- _film->log()->log (
- String::compose (
- "Decoder has emitted %1 video frames (which equals %2 audio frames) and %3 audio frames",
- _video_frame,
- video_frames_to_audio_frames (_video_frame, audio_sample_rate(), frames_per_second()),
- _audio_frame
- )
- );
-
- if (audio_short_by_frames < 0) {
-
- _film->log()->log (String::compose ("Emitted %1 too many audio frames", -audio_short_by_frames));
-
- /* We have emitted 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() / audio_sample_rate());
-
- _film->log()->log (String::compose ("Emitting %1 frames of black video", black_video_frames));
-
- shared_ptr<Image> black (new CompactImage (pixel_format(), native_size()));
- black->make_black ();
- for (int i = 0; i < black_video_frames; ++i) {
- emit_video (black, shared_ptr<Subtitle> ());
- }
-
- /* Now recompute our check value */
- audio_short_by_frames = video_frames_to_audio_frames (_video_frame, audio_sample_rate(), frames_per_second()) - _audio_frame;
- }
-
- if (audio_short_by_frames > 0) {
- _film->log()->log (String::compose ("Emitted %1 too few audio frames", audio_short_by_frames));
- shared_ptr<AudioBuffers> b (new AudioBuffers (audio_channels(), audio_short_by_frames));
- b->make_silent ();
- emit_audio (b);
- }
- }
-}
-
-/** Start decoding */
-void
-Decoder::go ()
-{
- process_begin ();
-
- if (_job && !_film->dcp_length()) {
- _job->set_progress_unknown ();
- }
-
- while (pass () == false) {
- if (_job && _film->dcp_length()) {
- _job->set_progress (float (_video_frame) / _film->length().get());
- }
- }
-
- process_end ();
-}
-
-/** Called by subclasses to tell the world that some audio data is ready
- * @param data Audio data, in Film::audio_sample_format.
- * @param size Number of bytes of data.
- */
-void
-Decoder::process_audio (uint8_t* data, int size)
-{
- /* XXX: could this be removed? */
- if (size == 0) {
- return;
- }
-
- assert (_film->audio_channels());
- assert (bytes_per_audio_sample());
-
- /* Deinterleave and convert to float */
-
- assert ((size % (bytes_per_audio_sample() * audio_channels())) == 0);
-
- int const total_samples = size / bytes_per_audio_sample();
- int const frames = total_samples / _film->audio_channels();
- shared_ptr<AudioBuffers> audio (new AudioBuffers (audio_channels(), frames));
-
- switch (audio_sample_format()) {
- case AV_SAMPLE_FMT_S16:
- {
- int16_t* p = (int16_t *) data;
- int sample = 0;
- int channel = 0;
- for (int i = 0; i < total_samples; ++i) {
- audio->data(channel)[sample] = float(*p++) / (1 << 15);
-
- ++channel;
- if (channel == _film->audio_channels()) {
- channel = 0;
- ++sample;
- }
- }
- }
- break;
-
- case AV_SAMPLE_FMT_S32:
- {
- int32_t* p = (int32_t *) data;
- int sample = 0;
- int channel = 0;
- for (int i = 0; i < total_samples; ++i) {
- audio->data(channel)[sample] = float(*p++) / (1 << 31);
-
- ++channel;
- if (channel == _film->audio_channels()) {
- channel = 0;
- ++sample;
- }
- }
- }
-
- case AV_SAMPLE_FMT_FLTP:
- {
- float* p = reinterpret_cast<float*> (data);
- for (int i = 0; i < _film->audio_channels(); ++i) {
- memcpy (audio->data(i), p, frames * sizeof(float));
- p += frames;
- }
- }
- break;
-
- default:
- assert (false);
- }
-
- /* Maybe apply gain */
- if (_film->audio_gain() != 0) {
- float const linear_gain = pow (10, _film->audio_gain() / 20);
- for (int i = 0; i < _film->audio_channels(); ++i) {
- for (int j = 0; j < frames; ++j) {
- audio->data(i)[j] *= linear_gain;
- }
- }
- }
-
- _delay_line->feed (audio);
- emit_audio (audio);
-}
-
-/** Called by subclasses to tell the world that some video data is ready.
- * We do some post-processing / filtering then emit it for listeners.
- * @param frame to decode; caller manages memory.
- */
-void
-Decoder::process_video (AVFrame* frame)
-{
- shared_ptr<FilterGraph> graph;
-
- list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (Size (frame->width, frame->height), (AVPixelFormat) frame->format)) {
- ++i;
- }
-
- if (i == _filter_graphs.end ()) {
- graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
- _filter_graphs.push_back (graph);
- _film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format));
- } else {
- graph = *i;
- }
-
- list<shared_ptr<Image> > images = graph->process (frame);
-
- for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
- shared_ptr<Subtitle> sub;
- if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) {
- sub = _timed_subtitle->subtitle ();
- }
-
- emit_video (*i, sub);
- }
-}
-
-void
-Decoder::repeat_last_video ()
-{
- if (!_last_image) {
- _last_image.reset (new CompactImage (pixel_format(), native_size()));
- _last_image->make_black ();
- }
-
- emit_video (_last_image, _last_subtitle);
-}
-
-void
-Decoder::emit_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
-{
- TIMING ("Decoder emits %1", _video_frame);
- Video (image, _video_frame, sub);
- ++_video_frame;
-
- _last_image = image;
- _last_subtitle = sub;
-}
-
-void
-Decoder::emit_audio (shared_ptr<AudioBuffers> audio)
-{
- Audio (audio, _audio_frame);
- _audio_frame += audio->frames ();
-}
-
-void
-Decoder::process_subtitle (shared_ptr<TimedSubtitle> s)
-{
- _timed_subtitle = s;
-
- if (_timed_subtitle && _opt->apply_crop) {
- Position const p = _timed_subtitle->subtitle()->position ();
- _timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top));
- }
-}
-
-int
-Decoder::bytes_per_audio_sample () const
-{
- return av_get_bytes_per_sample (audio_sample_format ());
-}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index 9f47bf425..e757e5401 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -31,6 +31,8 @@
#include <boost/signals2.hpp>
#include "util.h"
#include "stream.h"
+#include "video_source.h"
+#include "audio_source.h"
class Job;
class Options;
@@ -45,97 +47,25 @@ class FilterGraph;
/** @class Decoder.
* @brief Parent class for decoders of content.
*
- * These classes can be instructed run through their content
- * (by calling ::go), and they emit signals when video or audio data is ready for something else
- * to process.
+ * These classes can be instructed run through their content (by
+ * calling ::go), and they emit signals when video or audio data is
+ * ready for something else to process.
*/
class Decoder
{
public:
Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
- virtual ~Decoder ();
+ virtual ~Decoder () {}
- /* Methods to query our input video */
-
- /** @return video frames per second, or 0 if unknown */
- virtual float frames_per_second () const = 0;
- /** @return native size in pixels */
- virtual Size native_size () const = 0;
- /** @return number of audio channels */
- virtual int audio_channels () const = 0;
- /** @return audio sampling rate in Hz */
- virtual int audio_sample_rate () const = 0;
- /** @return format of audio samples */
- virtual AVSampleFormat audio_sample_format () const = 0;
- virtual int64_t audio_channel_layout () const = 0;
- virtual bool has_subtitles () 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;
-
- void process_begin ();
virtual bool pass () = 0;
- void process_end ();
- void go ();
-
- SourceFrame video_frame () const {
- return _video_frame;
- }
-
- virtual std::vector<AudioStream> audio_streams () const {
- return std::vector<AudioStream> ();
- }
-
- virtual std::vector<SubtitleStream> subtitle_streams () const {
- return std::vector<SubtitleStream> ();
- }
-
- /** Emitted when a video frame is ready.
- * First parameter is the frame within the source.
- * Second parameter is its index within the content.
- * Third parameter is either 0 or a subtitle that should be on this frame.
- */
- boost::signals2::signal<void (boost::shared_ptr<Image>, SourceFrame, boost::shared_ptr<Subtitle>)> Video;
-
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>, int64_t)> Audio;
protected:
-
- virtual PixelFormat pixel_format () const = 0;
-
- void process_video (AVFrame *);
- void process_audio (uint8_t *, int);
- void process_subtitle (boost::shared_ptr<TimedSubtitle>);
- void repeat_last_video ();
-
- int bytes_per_audio_sample () const;
-
/** our Film */
boost::shared_ptr<Film> _film;
/** our options */
boost::shared_ptr<const Options> _opt;
/** associated Job, or 0 */
Job* _job;
-
-private:
- void emit_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
- void emit_audio (boost::shared_ptr<AudioBuffers>);
-
- SourceFrame _video_frame;
- int64_t _audio_frame;
-
- std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
-
- DelayLine* _delay_line;
- int _delay_in_frames;
-
- boost::shared_ptr<TimedSubtitle> _timed_subtitle;
-
- boost::shared_ptr<Image> _last_image;
- boost::shared_ptr<Subtitle> _last_subtitle;
};
#endif
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
index d4a91d8d9..b2118ef74 100644
--- a/src/lib/decoder_factory.cc
+++ b/src/lib/decoder_factory.cc
@@ -23,26 +23,33 @@
#include <boost/filesystem.hpp>
#include "ffmpeg_decoder.h"
-#include "tiff_decoder.h"
#include "imagemagick_decoder.h"
#include "film.h"
+#include "external_audio_decoder.h"
using std::string;
+using std::pair;
+using std::make_pair;
using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
-shared_ptr<Decoder>
+pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> >
decoder_factory (
shared_ptr<Film> f, shared_ptr<const Options> o, Job* j
)
{
- if (boost::filesystem::is_directory (f->content_path ())) {
- /* Assume a directory contains TIFFs */
- return shared_ptr<Decoder> (new TIFFDecoder (f, o, j));
+ if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
+ /* A single image file, or a directory of them */
+ return make_pair (
+ shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o, j)),
+ shared_ptr<AudioDecoder> ()
+ );
}
- if (f->content_type() == STILL) {
- return shared_ptr<Decoder> (new ImageMagickDecoder (f, o, j));
+ shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o, j));
+ if (f->use_content_audio()) {
+ return make_pair (fd, fd);
}
-
- return shared_ptr<Decoder> (new FFmpegDecoder (f, o, j));
+
+ return make_pair (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j)));
}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
index 765b38816..1f3690611 100644
--- a/src/lib/decoder_factory.h
+++ b/src/lib/decoder_factory.h
@@ -18,15 +18,15 @@
*/
/** @file src/decoder_factory.h
- * @brief A method to create an appropriate decoder for some content.
+ * @brief A method to create appropriate decoders for some content.
*/
-class Decoder;
class Film;
class Options;
class Job;
-class Log;
+class VideoDecoder;
+class AudioDecoder;
-extern boost::shared_ptr<Decoder> decoder_factory (
+extern std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > decoder_factory (
boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *
);
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
index e7cd8dc94..45d8e9d9d 100644
--- a/src/lib/delay_line.cc
+++ b/src/lib/delay_line.cc
@@ -30,28 +30,25 @@ using boost::shared_ptr;
/** @param channels Number of channels of audio.
* @param frames Delay in frames, +ve to move audio later.
*/
-DelayLine::DelayLine (int channels, int frames)
- : _negative_delay_remaining (0)
+DelayLine::DelayLine (Log* log, int channels, int frames)
+ : AudioProcessor (log)
+ , _negative_delay_remaining (0)
+ , _frames (frames)
{
- if (frames > 0) {
+ if (_frames > 0) {
/* We need a buffer to keep some data in */
- _buffers.reset (new AudioBuffers (channels, frames));
+ _buffers.reset (new AudioBuffers (channels, _frames));
_buffers->make_silent ();
- } else if (frames < 0) {
+ } else if (_frames < 0) {
/* We can do -ve delays just by chopping off
the start, so no buffer needed.
*/
- _negative_delay_remaining = -frames;
+ _negative_delay_remaining = -_frames;
}
}
-DelayLine::~DelayLine ()
-{
-
-}
-
void
-DelayLine::feed (shared_ptr<AudioBuffers> data)
+DelayLine::process_audio (shared_ptr<AudioBuffers> data)
{
if (_buffers) {
/* We have some buffers, so we are moving the audio later */
@@ -91,4 +88,15 @@ DelayLine::feed (shared_ptr<AudioBuffers> data)
_negative_delay_remaining -= to_do;
}
}
+
+ Audio (data);
+}
+
+void
+DelayLine::process_end ()
+{
+ if (_frames < 0) {
+ _buffers->make_silent ();
+ Audio (_buffers);
+ }
}
diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h
index e8d9560af..fa2870ae7 100644
--- a/src/lib/delay_line.h
+++ b/src/lib/delay_line.h
@@ -18,19 +18,21 @@
*/
#include <boost/shared_ptr.hpp>
+#include "processor.h"
class AudioBuffers;
/** A delay line for audio */
-class DelayLine
+class DelayLine : public AudioProcessor
{
public:
- DelayLine (int channels, int frames);
- ~DelayLine ();
+ DelayLine (Log* log, int channels, int frames);
- void feed (boost::shared_ptr<AudioBuffers>);
+ void process_audio (boost::shared_ptr<AudioBuffers>);
+ void process_end ();
private:
boost::shared_ptr<AudioBuffers> _buffers;
int _negative_delay_remaining; ///< number of frames of negative delay that remain to emit
+ int _frames;
};
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index b322be04c..17a6726a6 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -37,7 +37,8 @@ Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const Options> o)
: _film (f)
, _opt (o)
, _just_skipped (false)
- , _last_frame (0)
+ , _video_frame (0)
+ , _audio_frame (0)
{
}
@@ -68,23 +69,22 @@ Encoder::skipping () const
return _just_skipped;
}
-/** @return Index of last frame to be successfully encoded */
+/** @return Number of video frames that have been received */
SourceFrame
-Encoder::last_frame () const
+Encoder::video_frame () const
{
boost::mutex::scoped_lock (_history_mutex);
- return _last_frame;
+ return _video_frame;
}
/** Should be called when a frame has been encoded successfully.
* @param n Source frame index.
*/
void
-Encoder::frame_done (SourceFrame n)
+Encoder::frame_done ()
{
boost::mutex::scoped_lock lock (_history_mutex);
_just_skipped = false;
- _last_frame = n;
struct timeval tv;
gettimeofday (&tv, 0);
@@ -105,24 +105,27 @@ Encoder::frame_skipped ()
}
void
-Encoder::process_video (shared_ptr<const Image> i, SourceFrame f, boost::shared_ptr<Subtitle> s)
+Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s)
{
- if (_opt->decode_video_skip != 0 && (f % _opt->decode_video_skip) != 0) {
+ if (_opt->decode_video_skip != 0 && (_video_frame % _opt->decode_video_skip) != 0) {
+ ++_video_frame;
return;
}
if (_opt->video_decode_range) {
pair<SourceFrame, SourceFrame> const r = _opt->video_decode_range.get();
- if (f < r.first || f >= r.second) {
+ if (_video_frame < r.first || _video_frame >= r.second) {
+ ++_video_frame;
return;
}
}
- do_process_video (i, f, s);
+ do_process_video (i, s);
+ ++_video_frame;
}
void
-Encoder::process_audio (shared_ptr<const AudioBuffers> data, int64_t f)
+Encoder::process_audio (shared_ptr<AudioBuffers> data)
{
if (_opt->audio_decode_range) {
@@ -131,7 +134,7 @@ Encoder::process_audio (shared_ptr<const AudioBuffers> data, int64_t f)
/* Range that we are encoding */
pair<int64_t, int64_t> required_range = _opt->audio_decode_range.get();
/* Range of this block of data */
- pair<int64_t, int64_t> this_range (f, f + trimmed->frames());
+ pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames());
if (this_range.second < required_range.first || required_range.second < this_range.first) {
/* No part of this audio is within the required range */
@@ -150,4 +153,6 @@ Encoder::process_audio (shared_ptr<const AudioBuffers> data, int64_t f)
}
do_process_audio (data);
+
+ _audio_frame += data->frames ();
}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index 20ec8fb16..b12bd0d48 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -32,6 +32,8 @@ extern "C" {
#include <libavutil/samplefmt.h>
}
#include "util.h"
+#include "video_sink.h"
+#include "audio_sink.h"
class Options;
class Image;
@@ -49,45 +51,43 @@ class Film;
* some way and write it to disk.
*/
-class Encoder
+class Encoder : public VideoSink, public AudioSink
{
public:
Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o);
virtual ~Encoder () {}
/** Called to indicate that a processing run is about to begin */
- virtual void process_begin (int64_t audio_channel_layout) = 0;
+ virtual void process_begin () {}
/** Call with a frame of video.
* @param i Video frame image.
- * @param f Frame number within the film's source.
* @param s A subtitle that should be on this frame, or 0.
*/
- void process_video (boost::shared_ptr<const Image> i, SourceFrame f, boost::shared_ptr<Subtitle> s);
+ void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s);
/** Call with some audio data */
- void process_audio (boost::shared_ptr<const AudioBuffers>, int64_t);
+ void process_audio (boost::shared_ptr<AudioBuffers>);
/** Called when a processing run has finished */
- virtual void process_end () = 0;
+ virtual void process_end () {}
float current_frames_per_second () const;
bool skipping () const;
- SourceFrame last_frame () const;
+ SourceFrame video_frame () const;
protected:
/** Called with a frame of video.
* @param i Video frame image.
- * @param f Frame number within the film's source.
* @param s A subtitle that should be on this frame, or 0.
*/
- virtual void do_process_video (boost::shared_ptr<const Image> i, SourceFrame f, boost::shared_ptr<Subtitle> s) = 0;
+ virtual void do_process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) = 0;
/** Called with some audio data */
- virtual void do_process_audio (boost::shared_ptr<const AudioBuffers>) = 0;
+ virtual void do_process_audio (boost::shared_ptr<AudioBuffers>) = 0;
- void frame_done (SourceFrame n);
+ void frame_done ();
void frame_skipped ();
/** Film that we are encoding */
@@ -105,8 +105,11 @@ protected:
static int const _history_size;
/** true if the last frame we processed was skipped (because it was already done) */
bool _just_skipped;
- /** Source index of the last frame to be processed */
- SourceFrame _last_frame;
+
+ /** Number of video frames received so far */
+ SourceFrame _video_frame;
+ /** Number of audio frames received so far */
+ int64_t _audio_frame;
};
#endif
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
index 4646fe8fc..8db74801f 100644
--- a/src/lib/examine_content_job.cc
+++ b/src/lib/examine_content_job.cc
@@ -30,9 +30,11 @@
#include "transcoder.h"
#include "log.h"
#include "film.h"
+#include "video_decoder.h"
using std::string;
using std::vector;
+using std::pair;
using boost::shared_ptr;
ExamineContentJob::ExamineContentJob (shared_ptr<Film> f, shared_ptr<Job> req)
@@ -59,17 +61,27 @@ void
ExamineContentJob::run ()
{
/* Decode the content to get an accurate length */
+
+ /* We don't want to use any existing length here, as progress
+ will be messed up.
+ */
+ _film->unset_length ();
shared_ptr<Options> o (new Options ("", "", ""));
o->out_size = Size (512, 512);
o->apply_crop = false;
+ o->decode_audio = false;
descend (0.5);
- _decoder = decoder_factory (_film, o, this);
- _decoder->go ();
+ pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoders = decoder_factory (_film, o, this);
+
+ set_progress_unknown ();
+ while (!decoders.first->pass()) {
+ /* keep going */
+ }
- _film->set_length (_decoder->video_frame());
+ _film->set_length (decoders.first->video_frame());
_film->log()->log (String::compose ("Video length is %1 frames", _film->length()));
diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h
index d87bd0876..2004aca83 100644
--- a/src/lib/examine_content_job.h
+++ b/src/lib/examine_content_job.h
@@ -23,8 +23,6 @@
#include "job.h"
-class Decoder;
-
/** @class ExamineContentJob
* @brief A class to run through content at high speed to find its length.
*/
@@ -36,8 +34,5 @@ public:
std::string name () const;
void run ();
-
-private:
- boost::shared_ptr<Decoder> _decoder;
};
diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc
new file mode 100644
index 000000000..136e00fb2
--- /dev/null
+++ b/src/lib/external_audio_decoder.cc
@@ -0,0 +1,185 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <sndfile.h>
+#include "external_audio_decoder.h"
+#include "film.h"
+#include "exceptions.h"
+
+using std::vector;
+using std::string;
+using std::stringstream;
+using std::min;
+using std::cout;
+using boost::shared_ptr;
+using boost::optional;
+
+ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+ : Decoder (f, o, j)
+ , AudioDecoder (f, o, j)
+{
+ sf_count_t frames;
+ vector<SNDFILE*> sf = open_files (frames);
+ close_files (sf);
+}
+
+vector<SNDFILE*>
+ExternalAudioDecoder::open_files (sf_count_t & frames)
+{
+ vector<string> const files = _film->external_audio ();
+
+ int N = 0;
+ for (size_t i = 0; i < files.size(); ++i) {
+ if (!files[i].empty()) {
+ N = i + 1;
+ }
+ }
+
+ if (N == 0) {
+ return vector<SNDFILE*> ();
+ }
+
+ bool first = true;
+ frames = 0;
+
+ vector<SNDFILE*> sndfiles;
+ for (size_t i = 0; i < (size_t) N; ++i) {
+ if (files[i].empty ()) {
+ sndfiles.push_back (0);
+ } else {
+ SF_INFO info;
+ SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info);
+ if (!s) {
+ throw DecodeError ("could not open external audio file for reading");
+ }
+
+ if (info.channels != 1) {
+ throw DecodeError ("external audio files must be mono");
+ }
+
+ sndfiles.push_back (s);
+
+ if (first) {
+ shared_ptr<ExternalAudioStream> st (
+ new ExternalAudioStream (
+ 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");
+ }
+ }
+ }
+ }
+
+ return sndfiles;
+}
+
+bool
+ExternalAudioDecoder::pass ()
+{
+ sf_count_t frames;
+ vector<SNDFILE*> sndfiles = open_files (frames);
+ if (sndfiles.empty()) {
+ return true;
+ }
+
+ /* Do things in half second blocks as I think there may be limits
+ to what FFmpeg (and in particular the resampler) can cope with.
+ */
+ sf_count_t const block = _audio_stream->sample_rate() / 2;
+
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block));
+ while (frames > 0) {
+ sf_count_t const this_time = min (block, frames);
+ for (size_t i = 0; i < sndfiles.size(); ++i) {
+ if (!sndfiles[i]) {
+ audio->make_silent (i);
+ } else {
+ sf_read_float (sndfiles[i], audio->data(i), block);
+ }
+ }
+
+ audio->set_frames (this_time);
+ Audio (audio);
+ frames -= this_time;
+ }
+
+ close_files (sndfiles);
+
+ return true;
+}
+
+void
+ExternalAudioDecoder::close_files (vector<SNDFILE*> const & sndfiles)
+{
+ for (size_t i = 0; i < sndfiles.size(); ++i) {
+ sf_close (sndfiles[i]);
+ }
+}
+
+shared_ptr<ExternalAudioStream>
+ExternalAudioStream::create ()
+{
+ return shared_ptr<ExternalAudioStream> (new ExternalAudioStream);
+}
+
+shared_ptr<ExternalAudioStream>
+ExternalAudioStream::create (string t, optional<int> v)
+{
+ if (!v) {
+ /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
+ return shared_ptr<ExternalAudioStream> ();
+ }
+
+ stringstream s (t);
+ string type;
+ s >> type;
+ if (type != "external") {
+ return shared_ptr<ExternalAudioStream> ();
+ }
+
+ return shared_ptr<ExternalAudioStream> (new ExternalAudioStream (t, v));
+}
+
+ExternalAudioStream::ExternalAudioStream (string t, optional<int> v)
+{
+ assert (v);
+
+ stringstream s (t);
+ string type;
+ s >> type >> _sample_rate >> _channel_layout;
+}
+
+ExternalAudioStream::ExternalAudioStream ()
+{
+
+}
+
+string
+ExternalAudioStream::to_string () const
+{
+ return String::compose ("external %1 %2", _sample_rate, _channel_layout);
+}
diff --git a/src/lib/external_audio_decoder.h b/src/lib/external_audio_decoder.h
new file mode 100644
index 000000000..45a2a809c
--- /dev/null
+++ b/src/lib/external_audio_decoder.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <sndfile.h>
+#include "decoder.h"
+#include "audio_decoder.h"
+#include "stream.h"
+
+class ExternalAudioStream : public AudioStream
+{
+public:
+ ExternalAudioStream (int sample_rate, int64_t layout)
+ : AudioStream (sample_rate, layout)
+ {}
+
+ std::string to_string () const;
+
+ static boost::shared_ptr<ExternalAudioStream> create ();
+ static boost::shared_ptr<ExternalAudioStream> create (std::string t, boost::optional<int> v);
+
+private:
+ friend class stream_test;
+
+ ExternalAudioStream ();
+ ExternalAudioStream (std::string t, boost::optional<int> v);
+};
+
+class ExternalAudioDecoder : public AudioDecoder
+{
+public:
+ ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+
+ bool pass ();
+
+private:
+ std::vector<SNDFILE*> open_files (sf_count_t &);
+ void close_files (std::vector<SNDFILE*> const &);
+};
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index a2ca739e2..4a6e236c3 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -47,20 +47,24 @@ extern "C" {
#include "util.h"
#include "log.h"
#include "ffmpeg_decoder.h"
+#include "filter_graph.h"
#include "subtitle.h"
using std::cout;
using std::string;
using std::vector;
using std::stringstream;
+using std::list;
using boost::shared_ptr;
+using boost::optional;
+using boost::dynamic_pointer_cast;
FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
: Decoder (f, o, j)
+ , VideoDecoder (f, o, j)
+ , AudioDecoder (f, o, j)
, _format_context (0)
, _video_stream (-1)
- , _audio_stream (-1)
- , _subtitle_stream (-1)
, _frame (0)
, _video_codec_context (0)
, _video_codec (0)
@@ -116,28 +120,20 @@ FFmpegDecoder::setup_general ()
if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
_video_stream = i;
} else if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
- if (_audio_stream == -1) {
- _audio_stream = i;
- }
- _audio_streams.push_back (AudioStream (stream_name (s), i, s->codec->channels));
+ _audio_streams.push_back (
+ shared_ptr<AudioStream> (
+ new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
+ )
+ );
} else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
- if (_subtitle_stream == -1) {
- _subtitle_stream = i;
- }
- _subtitle_streams.push_back (SubtitleStream (stream_name (s), i));
+ _subtitle_streams.push_back (
+ shared_ptr<SubtitleStream> (
+ new SubtitleStream (stream_name (s), i)
+ )
+ );
}
}
- /* Now override audio and subtitle streams with those from the Film, if it has any */
-
- if (_film->audio_stream_index() != -1) {
- _audio_stream = _film->audio_stream().id();
- }
-
- if (_film->subtitle_stream_index() != -1) {
- _subtitle_stream = _film->subtitle_stream().id ();
- }
-
if (_video_stream < 0) {
throw DecodeError ("could not find video stream");
}
@@ -173,11 +169,14 @@ FFmpegDecoder::setup_video ()
void
FFmpegDecoder::setup_audio ()
{
- if (_audio_stream < 0) {
+ if (!_audio_stream) {
return;
}
+
+ shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
+ assert (ffa);
- _audio_codec_context = _format_context->streams[_audio_stream]->codec;
+ _audio_codec_context = _format_context->streams[ffa->id()]->codec;
_audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
if (_audio_codec == 0) {
@@ -193,18 +192,18 @@ FFmpegDecoder::setup_audio ()
*/
if (_audio_codec_context->channel_layout == 0) {
- _audio_codec_context->channel_layout = av_get_default_channel_layout (audio_channels ());
+ _audio_codec_context->channel_layout = av_get_default_channel_layout (ffa->channels());
}
}
void
FFmpegDecoder::setup_subtitle ()
{
- if (_subtitle_stream < 0) {
+ if (!_subtitle_stream) {
return;
}
- _subtitle_codec_context = _format_context->streams[_subtitle_stream]->codec;
+ _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
_subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
if (_subtitle_codec == 0) {
@@ -240,17 +239,17 @@ FFmpegDecoder::pass ()
int frame_finished;
while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- process_video (_frame);
+ filter_and_emit_video (_frame);
}
- if (_audio_stream >= 0 && _opt->decode_audio) {
+ if (_audio_stream && _opt->decode_audio) {
while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
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());
- process_audio (_frame->data[0], data_size);
+ Audio (deinterleave_audio (_frame->data[0], data_size));
}
}
@@ -258,6 +257,8 @@ FFmpegDecoder::pass ()
}
avcodec_get_frame_defaults (_frame);
+
+ shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
if (_packet.stream_index == _video_stream) {
@@ -300,14 +301,14 @@ FFmpegDecoder::pass ()
if (delta > -one_frame) {
/* Process this frame */
- process_video (_frame);
+ filter_and_emit_video (_frame);
} else {
/* Otherwise we are omitting a frame to keep things right */
_film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
}
}
- } else if (_audio_stream >= 0 && _packet.stream_index == _audio_stream && _opt->decode_audio) {
+ } else if (ffa && _packet.stream_index == ffa->id() && _opt->decode_audio) {
int frame_finished;
if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
@@ -331,25 +332,19 @@ FFmpegDecoder::pass ()
*/
/* frames of silence that we must push */
- int const s = rint ((_first_audio.get() - _first_video.get()) * audio_sample_rate ());
+ int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ());
_film->log()->log (
String::compose (
"First video at %1, first audio at %2, pushing %3 frames of silence for %4 channels (%5 bytes per sample)",
- _first_video.get(), _first_audio.get(), s, audio_channels(), bytes_per_audio_sample()
+ _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample()
)
);
if (s) {
- /* hence bytes */
- int const b = s * audio_channels() * bytes_per_audio_sample();
-
- /* XXX: this assumes that it won't be too much, and there are shaky assumptions
- that all sound representations are silent with memset()ed zero data.
- */
- uint8_t silence[b];
- memset (silence, 0, b);
- process_audio (silence, b);
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s));
+ audio->make_silent ();
+ Audio (audio);
}
}
@@ -358,11 +353,11 @@ FFmpegDecoder::pass ()
);
assert (_audio_codec_context->channels == _film->audio_channels());
- process_audio (_frame->data[0], data_size);
+ Audio (deinterleave_audio (_frame->data[0], data_size));
}
}
- } else if (_subtitle_stream >= 0 && _packet.stream_index == _subtitle_stream && _opt->decode_subtitles && _first_video) {
+ } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) {
int got_subtitle;
AVSubtitle sub;
@@ -371,9 +366,9 @@ FFmpegDecoder::pass ()
indicate that the previous subtitle should stop.
*/
if (sub.num_rects > 0) {
- process_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub, _first_video.get())));
+ emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub, _first_video.get())));
} else {
- process_subtitle (shared_ptr<TimedSubtitle> ());
+ emit_subtitle (shared_ptr<TimedSubtitle> ());
}
avsubtitle_free (&sub);
}
@@ -383,36 +378,84 @@ FFmpegDecoder::pass ()
return false;
}
-float
-FFmpegDecoder::frames_per_second () const
+shared_ptr<AudioBuffers>
+FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
{
- AVStream* s = _format_context->streams[_video_stream];
+ assert (_film->audio_channels());
+ assert (bytes_per_audio_sample());
- if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
- return av_q2d (s->avg_frame_rate);
+ shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
+ assert (ffa);
+
+ /* Deinterleave and convert to float */
+
+ assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
+
+ int const total_samples = size / bytes_per_audio_sample();
+ int const frames = total_samples / _film->audio_channels();
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
+
+ switch (audio_sample_format()) {
+ case AV_SAMPLE_FMT_S16:
+ {
+ int16_t* p = (int16_t *) data;
+ int sample = 0;
+ int channel = 0;
+ for (int i = 0; i < total_samples; ++i) {
+ audio->data(channel)[sample] = float(*p++) / (1 << 15);
+
+ ++channel;
+ if (channel == _film->audio_channels()) {
+ channel = 0;
+ ++sample;
+ }
+ }
+ }
+ break;
+
+ case AV_SAMPLE_FMT_S32:
+ {
+ int32_t* p = (int32_t *) data;
+ int sample = 0;
+ int channel = 0;
+ for (int i = 0; i < total_samples; ++i) {
+ audio->data(channel)[sample] = float(*p++) / (1 << 31);
+
+ ++channel;
+ if (channel == _film->audio_channels()) {
+ channel = 0;
+ ++sample;
+ }
+ }
}
- return av_q2d (s->r_frame_rate);
-}
+ case AV_SAMPLE_FMT_FLTP:
+ {
+ float* p = reinterpret_cast<float*> (data);
+ for (int i = 0; i < _film->audio_channels(); ++i) {
+ memcpy (audio->data(i), p, frames * sizeof(float));
+ p += frames;
+ }
+ }
+ break;
-int
-FFmpegDecoder::audio_channels () const
-{
- if (_audio_codec_context == 0) {
- return 0;
+ default:
+ assert (false);
}
- return _audio_codec_context->channels;
+ return audio;
}
-int
-FFmpegDecoder::audio_sample_rate () const
+float
+FFmpegDecoder::frames_per_second () const
{
- if (_audio_codec_context == 0) {
- return 0;
+ AVStream* s = _format_context->streams[_video_stream];
+
+ if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
+ return av_q2d (s->avg_frame_rate);
}
-
- return _audio_codec_context->sample_rate;
+
+ return av_q2d (s->r_frame_rate);
}
AVSampleFormat
@@ -425,16 +468,6 @@ FFmpegDecoder::audio_sample_format () const
return _audio_codec_context->sample_fmt;
}
-int64_t
-FFmpegDecoder::audio_channel_layout () const
-{
- if (_audio_codec_context == 0) {
- return 0;
- }
-
- return _audio_codec_context->channel_layout;
-}
-
Size
FFmpegDecoder::native_size () const
{
@@ -471,24 +504,6 @@ FFmpegDecoder::sample_aspect_ratio_denominator () const
return _video_codec_context->sample_aspect_ratio.den;
}
-bool
-FFmpegDecoder::has_subtitles () const
-{
- return (_subtitle_stream != -1);
-}
-
-vector<AudioStream>
-FFmpegDecoder::audio_streams () const
-{
- return _audio_streams;
-}
-
-vector<SubtitleStream>
-FFmpegDecoder::subtitle_streams () const
-{
- return _subtitle_streams;
-}
-
string
FFmpegDecoder::stream_name (AVStream* s) const
{
@@ -514,3 +529,100 @@ FFmpegDecoder::stream_name (AVStream* s) const
return n.str ();
}
+int
+FFmpegDecoder::bytes_per_audio_sample () const
+{
+ return av_get_bytes_per_sample (audio_sample_format ());
+}
+
+void
+FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+{
+ AudioDecoder::set_audio_stream (s);
+ setup_audio ();
+}
+
+void
+FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
+{
+ VideoDecoder::set_subtitle_stream (s);
+ setup_subtitle ();
+}
+
+void
+FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
+{
+ shared_ptr<FilterGraph> graph;
+
+ list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
+ while (i != _filter_graphs.end() && !(*i)->can_process (Size (frame->width, frame->height), (AVPixelFormat) frame->format)) {
+ ++i;
+ }
+
+ if (i == _filter_graphs.end ()) {
+ graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+ _filter_graphs.push_back (graph);
+ _film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format));
+ } else {
+ graph = *i;
+ }
+
+ list<shared_ptr<Image> > images = graph->process (frame);
+
+ for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
+ emit_video (*i);
+ }
+}
+
+shared_ptr<FFmpegAudioStream>
+FFmpegAudioStream::create (string t, optional<int> v)
+{
+ if (!v) {
+ /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
+ return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
+ }
+
+ stringstream s (t);
+ string type;
+ s >> type;
+ if (type != "ffmpeg") {
+ return shared_ptr<FFmpegAudioStream> ();
+ }
+
+ return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
+}
+
+FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
+{
+ stringstream n (t);
+
+ int name_index = 4;
+ if (!version) {
+ name_index = 2;
+ int channels;
+ n >> _id >> channels;
+ _channel_layout = av_get_default_channel_layout (channels);
+ _sample_rate = 0;
+ } else {
+ string type;
+ /* Current (marked version 1) */
+ n >> type >> _id >> _sample_rate >> _channel_layout;
+ assert (type == "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 ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name);
+}
+
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 0265f7478..87eebe1ec 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -32,6 +32,8 @@ extern "C" {
}
#include "util.h"
#include "decoder.h"
+#include "video_decoder.h"
+#include "audio_decoder.h"
struct AVFilterGraph;
struct AVCodecContext;
@@ -46,35 +48,63 @@ class Options;
class Image;
class Log;
+class FFmpegAudioStream : public AudioStream
+{
+public:
+ FFmpegAudioStream (std::string n, int i, int s, int64_t c)
+ : AudioStream (s, c)
+ , _name (n)
+ , _id (i)
+ {}
+
+ std::string to_string () const;
+
+ std::string name () const {
+ return _name;
+ }
+
+ int id () const {
+ return _id;
+ }
+
+ static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v);
+
+private:
+ friend class stream_test;
+
+ FFmpegAudioStream (std::string t, boost::optional<int> v);
+
+ std::string _name;
+ int _id;
+};
+
/** @class FFmpegDecoder
* @brief A decoder using FFmpeg to decode content.
*/
-class FFmpegDecoder : public Decoder
+class FFmpegDecoder : public VideoDecoder, public AudioDecoder
{
public:
FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
~FFmpegDecoder ();
- /* Methods to query our input video */
float frames_per_second () const;
Size native_size () const;
- int audio_channels () const;
- int audio_sample_rate () const;
- AVSampleFormat audio_sample_format () const;
- int64_t audio_channel_layout () const;
- bool has_subtitles () const;
+ int time_base_numerator () const;
+ int time_base_denominator () const;
+ int sample_aspect_ratio_numerator () const;
+ int sample_aspect_ratio_denominator () const;
- std::vector<AudioStream> audio_streams () const;
- std::vector<SubtitleStream> subtitle_streams () const;
+ void set_audio_stream (boost::shared_ptr<AudioStream>);
+ void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
private:
bool pass ();
PixelFormat pixel_format () const;
- int time_base_numerator () const;
- int time_base_denominator () const;
- int sample_aspect_ratio_numerator () const;
- int sample_aspect_ratio_denominator () const;
+ AVSampleFormat audio_sample_format () const;
+ int bytes_per_audio_sample () const;
+
+ void filter_and_emit_video (AVFrame *);
void setup_general ();
void setup_video ();
@@ -82,19 +112,15 @@ private:
void setup_subtitle ();
void maybe_add_subtitle ();
+ boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size);
std::string stream_name (AVStream* s) const;
AVFormatContext* _format_context;
int _video_stream;
- int _audio_stream; ///< may be < 0 if there is no audio
- int _subtitle_stream; ///< may be < 0 if there is no subtitle
AVFrame* _frame;
- std::vector<AudioStream> _audio_streams;
- std::vector<SubtitleStream> _subtitle_streams;
-
AVCodecContext* _video_codec_context;
AVCodec* _video_codec;
AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio
@@ -106,4 +132,6 @@ private:
boost::optional<double> _first_video;
boost::optional<double> _first_audio;
+
+ std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 902306fb8..3f9210080 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -51,6 +51,9 @@
#include "check_hashes_job.h"
#include "version.h"
#include "ui_signaller.h"
+#include "video_decoder.h"
+#include "audio_decoder.h"
+#include "external_audio_decoder.h"
using std::string;
using std::stringstream;
@@ -63,11 +66,15 @@ using std::ofstream;
using std::setfill;
using std::min;
using std::make_pair;
+using std::cout;
using boost::shared_ptr;
using boost::lexical_cast;
using boost::to_upper_copy;
using boost::ends_with;
using boost::starts_with;
+using boost::optional;
+
+int const Film::state_version = 1;
/** Construct a Film object in a given directory, reading any metadata
* file that exists in that directory. An exception will be thrown if
@@ -78,23 +85,20 @@ using boost::starts_with;
*/
Film::Film (string d, bool must_exist)
- : _use_dci_name (false)
+ : _use_dci_name (true)
, _dcp_content_type (0)
, _format (0)
, _scaler (Scaler::from_id ("bicubic"))
, _dcp_trim_start (0)
, _dcp_trim_end (0)
, _dcp_ab (false)
- , _audio_stream (-1)
+ , _use_content_audio (true)
, _audio_gain (0)
, _audio_delay (0)
, _still_duration (10)
- , _subtitle_stream (-1)
, _with_subtitles (false)
, _subtitle_offset (0)
, _subtitle_scale (1)
- , _audio_sample_rate (0)
- , _has_subtitles (false)
, _frames_per_second (0)
, _dirty (false)
{
@@ -126,6 +130,8 @@ Film::Film (string d, bool must_exist)
}
}
+ _external_audio_stream = ExternalAudioStream::create ();
+
read_metadata ();
_log = new FileLog (file ("log"));
@@ -146,7 +152,9 @@ Film::Film (Film const & o)
, _dcp_trim_start (o._dcp_trim_start)
, _dcp_trim_end (o._dcp_trim_end)
, _dcp_ab (o._dcp_ab)
- , _audio_stream (o._audio_stream)
+ , _content_audio_stream (o._content_audio_stream)
+ , _external_audio (o._external_audio)
+ , _use_content_audio (o._use_content_audio)
, _audio_gain (o._audio_gain)
, _audio_delay (o._audio_delay)
, _still_duration (o._still_duration)
@@ -164,10 +172,9 @@ Film::Film (Film const & o)
, _thumbs (o._thumbs)
, _size (o._size)
, _length (o._length)
- , _audio_sample_rate (o._audio_sample_rate)
, _content_digest (o._content_digest)
- , _has_subtitles (o._has_subtitles)
- , _audio_streams (o._audio_streams)
+ , _content_audio_streams (o._content_audio_streams)
+ , _external_audio_stream (o._external_audio_stream)
, _subtitle_streams (o._subtitle_streams)
, _frames_per_second (o._frames_per_second)
, _dirty (o._dirty)
@@ -259,10 +266,12 @@ Film::make_dcp (bool transcode)
o->ratio = format()->ratio_as_float (shared_from_this ());
if (dcp_length ()) {
o->video_decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
- o->audio_decode_range = make_pair (
- video_frames_to_audio_frames (o->video_decode_range.get().first, audio_sample_rate(), frames_per_second()),
- video_frames_to_audio_frames (o->video_decode_range.get().second, audio_sample_rate(), frames_per_second())
- );
+ if (audio_stream()) {
+ o->audio_decode_range = make_pair (
+ video_frames_to_audio_frames (o->video_decode_range.get().first, audio_stream()->sample_rate(), frames_per_second()),
+ video_frames_to_audio_frames (o->video_decode_range.get().second, audio_stream()->sample_rate(), frames_per_second())
+ );
+ }
}
o->decode_subtitles = with_subtitles ();
@@ -389,6 +398,8 @@ Film::write_metadata () const
throw CreateFileError (m);
}
+ f << "version " << state_version << "\n";
+
/* User stuff */
f << "name " << _name << "\n";
f << "use_dci_name " << _use_dci_name << "\n";
@@ -410,11 +421,19 @@ Film::write_metadata () const
f << "dcp_trim_start " << _dcp_trim_start << "\n";
f << "dcp_trim_end " << _dcp_trim_end << "\n";
f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
- f << "selected_audio_stream " << _audio_stream << "\n";
+ if (_content_audio_stream) {
+ f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n";
+ }
+ for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
+ f << "external_audio " << *i << "\n";
+ }
+ f << "use_content_audio " << (_use_content_audio ? "1" : "0") << "\n";
f << "audio_gain " << _audio_gain << "\n";
f << "audio_delay " << _audio_delay << "\n";
f << "still_duration " << _still_duration << "\n";
- f << "selected_subtitle_stream " << _subtitle_stream << "\n";
+ if (_subtitle_stream) {
+ f << "selected_subtitle_stream " << _subtitle_stream->to_string() << "\n";
+ }
f << "with_subtitles " << _with_subtitles << "\n";
f << "subtitle_offset " << _subtitle_offset << "\n";
f << "subtitle_scale " << _subtitle_scale << "\n";
@@ -435,16 +454,16 @@ Film::write_metadata () const
f << "width " << _size.width << "\n";
f << "height " << _size.height << "\n";
f << "length " << _length.get_value_or(0) << "\n";
- f << "audio_sample_rate " << _audio_sample_rate << "\n";
f << "content_digest " << _content_digest << "\n";
- f << "has_subtitles " << _has_subtitles << "\n";
- for (vector<AudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
- f << "audio_stream " << i->to_string () << "\n";
+ for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
+ f << "content_audio_stream " << (*i)->to_string () << "\n";
}
- for (vector<SubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
- f << "subtitle_stream " << i->to_string () << "\n";
+ f << "external_audio_stream " << _external_audio_stream->to_string() << "\n";
+
+ for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
+ f << "subtitle_stream " << (*i)->to_string () << "\n";
}
f << "frames_per_second " << _frames_per_second << "\n";
@@ -457,13 +476,36 @@ void
Film::read_metadata ()
{
boost::mutex::scoped_lock lm (_state_mutex);
+
+ _external_audio.clear ();
+ _thumbs.clear ();
+ _content_audio_streams.clear ();
+ _subtitle_streams.clear ();
+
+ boost::optional<int> version;
+
+ /* Backward compatibility things */
+ boost::optional<int> audio_sample_rate;
+ boost::optional<int> audio_stream_index;
+ boost::optional<int> subtitle_stream_index;
ifstream f (file ("metadata").c_str());
multimap<string, string> kv = read_key_value (f);
+
+ /* We need version before anything else */
+ multimap<string, string>::iterator v = kv.find ("version");
+ if (v != kv.end ()) {
+ version = atoi (v->second.c_str());
+ }
+
for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
string const k = i->first;
string const v = i->second;
+ if (k == "audio_sample_rate") {
+ audio_sample_rate = atoi (v.c_str());
+ }
+
/* User-specified stuff */
if (k == "name") {
_name = v;
@@ -493,8 +535,16 @@ Film::read_metadata ()
_dcp_trim_end = atoi (v.c_str ());
} else if (k == "dcp_ab") {
_dcp_ab = (v == "1");
- } else if (k == "selected_audio_stream") {
- _audio_stream = atoi (v.c_str ());
+ } 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") {
@@ -502,7 +552,11 @@ Film::read_metadata ()
} else if (k == "still_duration") {
_still_duration = atoi (v.c_str ());
} else if (k == "selected_subtitle_stream") {
- _subtitle_stream = atoi (v.c_str ());
+ 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") {
@@ -541,20 +595,37 @@ Film::read_metadata ()
if (vv) {
_length = vv;
}
- } else if (k == "audio_sample_rate") {
- _audio_sample_rate = atoi (v.c_str ());
} else if (k == "content_digest") {
_content_digest = v;
- } else if (k == "has_subtitles") {
- _has_subtitles = (v == "1");
- } else if (k == "audio_stream") {
- _audio_streams.push_back (AudioStream (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") {
+ _external_audio_stream = audio_stream_factory (v, version);
} else if (k == "subtitle_stream") {
- _subtitle_streams.push_back (SubtitleStream (v));
+ _subtitle_streams.push_back (subtitle_stream_factory (v, version));
} else if (k == "frames_per_second") {
_frames_per_second = atof (v.c_str ());
}
}
+
+ if (!version) {
+ if (audio_sample_rate) {
+ /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
+ for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
+ (*i)->set_sample_rate (audio_sample_rate.get());
+ }
+ }
+
+ /* 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()];
+ }
+
+ /* 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()];
+ }
+ }
_dirty = false;
}
@@ -667,15 +738,12 @@ Film::content_path () const
ContentType
Film::content_type () const
{
-#if BOOST_FILESYSTEM_VERSION == 3
- string ext = boost::filesystem::path(_content).extension().string();
-#else
- string ext = boost::filesystem::path(_content).extension();
-#endif
+ if (boost::filesystem::is_directory (_content)) {
+ /* Directory of images, we assume */
+ return VIDEO;
+ }
- transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-
- if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
+ if (still_image_file (_content)) {
return STILL;
}
@@ -686,8 +754,12 @@ Film::content_type () const
int
Film::target_audio_sample_rate () const
{
+ if (!audio_stream()) {
+ return 0;
+ }
+
/* Resample to a DCI-approved sample rate */
- double t = dcp_audio_sample_rate (audio_sample_rate());
+ double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
@@ -715,11 +787,9 @@ Film::dcp_length () const
string
Film::dci_name () const
{
- boost::mutex::scoped_lock lm (_state_mutex);
-
stringstream d;
- string fixed_name = to_upper_copy (_name);
+ string fixed_name = to_upper_copy (name());
for (size_t i = 0; i < fixed_name.length(); ++i) {
if (fixed_name[i] == ' ') {
fixed_name[i] = '-';
@@ -733,18 +803,18 @@ Film::dci_name () const
d << fixed_name << "_";
- if (_dcp_content_type) {
- d << _dcp_content_type->dci_name() << "_";
+ if (dcp_content_type()) {
+ d << dcp_content_type()->dci_name() << "_";
}
- if (_format) {
- d << _format->dci_name() << "_";
+ if (format()) {
+ d << format()->dci_name() << "_";
}
- if (!_audio_language.empty ()) {
- d << _audio_language;
- if (!_subtitle_language.empty() && _with_subtitles) {
- d << "-" << _subtitle_language;
+ if (!audio_language().empty ()) {
+ d << audio_language();
+ if (!subtitle_language().empty() && with_subtitles()) {
+ d << "-" << subtitle_language();
} else {
d << "-XX";
}
@@ -752,45 +822,43 @@ Film::dci_name () const
d << "_";
}
- if (!_territory.empty ()) {
- d << _territory;
- if (!_rating.empty ()) {
- d << "-" << _rating;
+ if (!territory().empty ()) {
+ d << territory();
+ if (!rating().empty ()) {
+ d << "-" << rating();
}
d << "_";
}
- if (_audio_stream != -1) {
- switch (_audio_streams[_audio_stream].channels()) {
- case 1:
- d << "10_";
- break;
- case 2:
- d << "20_";
- break;
- case 6:
- d << "51_";
- break;
- case 8:
- d << "71_";
- break;
- }
+ switch (audio_channels()) {
+ case 1:
+ d << "10_";
+ break;
+ case 2:
+ d << "20_";
+ break;
+ case 6:
+ d << "51_";
+ break;
+ case 8:
+ d << "71_";
+ break;
}
d << "2K_";
- if (!_studio.empty ()) {
- d << _studio << "_";
+ if (!studio().empty ()) {
+ d << studio() << "_";
}
d << boost::gregorian::to_iso_string (_dci_date) << "_";
- if (!_facility.empty ()) {
- d << _facility << "_";
+ if (!facility().empty ()) {
+ d << facility() << "_";
}
- if (!_package_type.empty ()) {
- d << _package_type;
+ if (!package_type().empty ()) {
+ d << package_type();
}
return d.str ();
@@ -872,6 +940,10 @@ Film::set_content (string c)
_content = c;
}
+ /* Reset streams here in case the new content doesn't have one or the other */
+ _content_audio_stream = shared_ptr<AudioStream> ();
+ _subtitle_stream = shared_ptr<SubtitleStream> ();
+
/* Create a temporary decoder so that we can get information
about the content.
*/
@@ -880,16 +952,21 @@ Film::set_content (string c)
shared_ptr<Options> o (new Options ("", "", ""));
o->out_size = Size (1024, 1024);
- shared_ptr<Decoder> d = decoder_factory (shared_from_this(), o, 0);
+ pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (shared_from_this(), o, 0);
+
+ set_size (d.first->native_size ());
+ set_frames_per_second (d.first->frames_per_second ());
+ set_subtitle_streams (d.first->subtitle_streams ());
+ set_content_audio_streams (d.second->audio_streams ());
+
+ /* Start off with the first audio and subtitle streams */
+ if (!d.second->audio_streams().empty()) {
+ set_content_audio_stream (d.second->audio_streams().front());
+ }
- set_size (d->native_size ());
- set_frames_per_second (d->frames_per_second ());
- set_audio_sample_rate (d->audio_sample_rate ());
- set_has_subtitles (d->has_subtitles ());
- set_audio_streams (d->audio_streams ());
- set_subtitle_streams (d->subtitle_streams ());
- set_audio_stream (audio_streams().empty() ? -1 : 0);
- set_subtitle_stream (subtitle_streams().empty() ? -1 : 0);
+ if (!d.first->subtitle_streams().empty()) {
+ set_subtitle_stream (d.first->subtitle_streams().front());
+ }
{
boost::mutex::scoped_lock lm (_state_mutex);
@@ -1049,13 +1126,42 @@ Film::set_dcp_ab (bool a)
}
void
-Film::set_audio_stream (int s)
+Film::set_content_audio_stream (shared_ptr<AudioStream> s)
+{
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _content_audio_stream = s;
+ }
+ signal_changed (CONTENT_AUDIO_STREAM);
+}
+
+void
+Film::set_external_audio (vector<string> a)
+{
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _external_audio = a;
+ }
+
+ shared_ptr<Options> o (new Options ("", "", ""));
+ o->decode_audio = true;
+ shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
+ if (decoder->audio_stream()) {
+ _external_audio_stream = decoder->audio_stream ();
+ }
+
+ signal_changed (EXTERNAL_AUDIO);
+}
+
+void
+Film::set_use_content_audio (bool e)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _audio_stream = s;
+ _use_content_audio = e;
}
- signal_changed (AUDIO_STREAM);
+
+ signal_changed (USE_CONTENT_AUDIO);
}
void
@@ -1089,7 +1195,7 @@ Film::set_still_duration (int d)
}
void
-Film::set_subtitle_stream (int s)
+Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
@@ -1239,16 +1345,6 @@ Film::unset_length ()
}
void
-Film::set_audio_sample_rate (int r)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_sample_rate = r;
- }
- signal_changed (AUDIO_SAMPLE_RATE);
-}
-
-void
Film::set_content_digest (string d)
{
{
@@ -1259,33 +1355,23 @@ Film::set_content_digest (string d)
}
void
-Film::set_has_subtitles (bool s)
+Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _has_subtitles = s;
+ _content_audio_streams = s;
}
- signal_changed (HAS_SUBTITLES);
+ signal_changed (CONTENT_AUDIO_STREAMS);
}
void
-Film::set_audio_streams (vector<AudioStream> s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_streams = s;
- }
- _dirty = true;
-}
-
-void
-Film::set_subtitle_streams (vector<SubtitleStream> s)
+Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
_subtitle_streams = s;
}
- _dirty = true;
+ signal_changed (SUBTITLE_STREAMS);
}
void
@@ -1314,12 +1400,12 @@ Film::signal_changed (Property p)
int
Film::audio_channels () const
{
- boost::mutex::scoped_lock lm (_state_mutex);
- if (_audio_stream == -1) {
+ shared_ptr<AudioStream> s = audio_stream ();
+ if (!s) {
return 0;
}
-
- return _audio_streams[_audio_stream].channels ();
+
+ return s->channels ();
}
void
@@ -1328,3 +1414,12 @@ Film::set_dci_date_today ()
_dci_date = boost::gregorian::day_clock::local_day ();
}
+boost::shared_ptr<AudioStream>
+Film::audio_stream () const
+{
+ if (use_content_audio()) {
+ return _content_audio_stream;
+ }
+
+ return _external_audio_stream;
+}
diff --git a/src/lib/film.h b/src/lib/film.h
index 64e7a3a70..2e81575e4 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -44,6 +44,7 @@ class Job;
class Filter;
class Log;
class ExamineContentJob;
+class ExternalAudioStream;
/** @class Film
* @brief A representation of a video with sound.
@@ -117,7 +118,9 @@ public:
DCP_TRIM_START,
DCP_TRIM_END,
DCP_AB,
- AUDIO_STREAM,
+ CONTENT_AUDIO_STREAM,
+ EXTERNAL_AUDIO,
+ USE_CONTENT_AUDIO,
AUDIO_GAIN,
AUDIO_DELAY,
STILL_DURATION,
@@ -129,9 +132,7 @@ public:
THUMBS,
SIZE,
LENGTH,
- AUDIO_SAMPLE_RATE,
- HAS_SUBTITLES,
- AUDIO_STREAMS,
+ CONTENT_AUDIO_STREAMS,
SUBTITLE_STREAMS,
FRAMES_PER_SECOND,
};
@@ -199,15 +200,19 @@ public:
return _dcp_ab;
}
- int audio_stream_index () const {
+ boost::shared_ptr<AudioStream> content_audio_stream () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _audio_stream;
+ return _content_audio_stream;
}
- AudioStream audio_stream () const {
+ std::vector<std::string> external_audio () const {
boost::mutex::scoped_lock lm (_state_mutex);
- assert (_audio_stream < int (_audio_streams.size()));
- return _audio_streams[_audio_stream];
+ return _external_audio;
+ }
+
+ bool use_content_audio () const {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _use_content_audio;
}
float audio_gain () const {
@@ -225,17 +230,11 @@ public:
return _still_duration;
}
- int subtitle_stream_index () const {
+ boost::shared_ptr<SubtitleStream> subtitle_stream () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _subtitle_stream;
}
- SubtitleStream subtitle_stream () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- assert (_subtitle_stream < int (_subtitle_streams.size()));
- return _subtitle_streams[_subtitle_stream];
- }
-
bool with_subtitles () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _with_subtitles;
@@ -301,27 +300,17 @@ public:
return _length;
}
- int audio_sample_rate () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _audio_sample_rate;
- }
-
std::string content_digest () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _content_digest;
}
- bool has_subtitles () const {
+ std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _has_subtitles;
+ return _content_audio_streams;
}
- std::vector<AudioStream> audio_streams () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _audio_streams;
- }
-
- std::vector<SubtitleStream> subtitle_streams () const {
+ std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _subtitle_streams;
}
@@ -331,7 +320,9 @@ public:
return _frames_per_second;
}
+ boost::shared_ptr<AudioStream> audio_stream () const;
+
/* SET */
void set_directory (std::string);
@@ -350,11 +341,13 @@ public:
void set_dcp_trim_start (int);
void set_dcp_trim_end (int);
void set_dcp_ab (bool);
- void set_audio_stream (int);
+ void set_content_audio_stream (boost::shared_ptr<AudioStream>);
+ void set_external_audio (std::vector<std::string>);
+ void set_use_content_audio (bool);
void set_audio_gain (float);
void set_audio_delay (int);
void set_still_duration (int);
- void set_subtitle_stream (int);
+ void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
void set_with_subtitles (bool);
void set_subtitle_offset (int);
void set_subtitle_scale (float);
@@ -369,16 +362,17 @@ public:
void set_size (Size);
void set_length (SourceFrame);
void unset_length ();
- void set_audio_sample_rate (int);
void set_content_digest (std::string);
- void set_has_subtitles (bool);
- void set_audio_streams (std::vector<AudioStream>);
- void set_subtitle_streams (std::vector<SubtitleStream>);
+ void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
+ void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
void set_frames_per_second (float);
/** Emitted when some property has changed */
mutable boost::signals2::signal<void (Property)> Changed;
-
+
+ /** Current version number of the state file */
+ static int const state_version;
+
private:
/** Log to write to */
@@ -429,16 +423,21 @@ private:
has the specified filters and post-processing.
*/
bool _dcp_ab;
- /** An index into our _audio_streams vector for the stream to use for audio, or -1 if there is none */
- int _audio_stream;
+ /** The audio stream to use from our content */
+ boost::shared_ptr<AudioStream> _content_audio_stream;
+ /** List of filenames of external audio files, in channel order
+ (L, R, C, Lfe, Ls, Rs)
+ */
+ std::vector<std::string> _external_audio;
+ /** true to use audio from our content file; false to use external audio */
+ bool _use_content_audio;
/** Gain to apply to audio in dB */
float _audio_gain;
/** Delay to apply to audio (positive moves audio later) in milliseconds */
int _audio_delay;
/** Duration to make still-sourced films (in seconds) */
int _still_duration;
- /** An index into our _subtitle_streams vector for the stream to use for subtitles, or -1 if there is none */
- int _subtitle_stream;
+ boost::shared_ptr<SubtitleStream> _subtitle_stream;
/** True if subtitles should be shown for this film */
bool _with_subtitles;
/** y offset for placing subtitles, in source pixels; +ve is further down
@@ -465,19 +464,18 @@ private:
Size _size;
/** Actual length of the source (in video frames) from examining it */
boost::optional<SourceFrame> _length;
- /** Sample rate of the source audio, in Hz */
- int _audio_sample_rate;
/** MD5 digest of our content file */
std::string _content_digest;
- /** true if the source has subtitles */
- bool _has_subtitles;
- /** the audio streams that the source has */
- std::vector<AudioStream> _audio_streams;
- /** the subtitle streams that the source has */
- std::vector<SubtitleStream> _subtitle_streams;
+ /** The audio streams in our content */
+ std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams;
+ /** A stream to represent possible external audio (will always exist) */
+ boost::shared_ptr<AudioStream> _external_audio_stream;
+ /** the subtitle streams that we can use */
+ std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
/** Frames per second of the source */
float _frames_per_second;
+ /** true if our state has changed since we last saved it */
mutable bool _dirty;
/** Mutex for all state except _directory */
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index 1418384b8..7320070fe 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -17,6 +17,10 @@
*/
+/** @file src/lib/filter_graph.cc
+ * @brief A graph of FFmpeg filters.
+ */
+
extern "C" {
#include <libavfilter/avfiltergraph.h>
#include <libavfilter/buffersrc.h>
@@ -28,42 +32,43 @@ extern "C" {
#endif
#include <libavformat/avio.h>
}
-#include "film.h"
#include "decoder.h"
#include "filter_graph.h"
#include "ffmpeg_compatibility.h"
#include "filter.h"
#include "exceptions.h"
#include "image.h"
+#include "film.h"
+#include "ffmpeg_decoder.h"
using std::stringstream;
using std::string;
using std::list;
using boost::shared_ptr;
-FilterGraph::FilterGraph (shared_ptr<Film> film, Decoder* decoder, bool crop, Size s, AVPixelFormat p)
+/** Construct a FilterGraph for the settings in a film.
+ * @param film Film.
+ * @param decoder Decoder that we are using.
+ * @param crop true to apply crop, otherwise false.
+ * @param s Size of the images to process.
+ * @param p Pixel format of the images to process.
+ */
+FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
, _pixel_format (p)
{
- stringstream fs;
- Size size_after_crop;
-
- if (crop) {
- size_after_crop = film->cropped_size (decoder->native_size ());
- fs << crop_string (Position (film->crop().left, film->crop().top), size_after_crop);
- } else {
- size_after_crop = decoder->native_size ();
- fs << crop_string (Position (0, 0), size_after_crop);
- }
-
string filters = Filter::ffmpeg_strings (film->filters()).first;
if (!filters.empty ()) {
filters += ",";
}
- filters += fs.str ();
+ if (crop) {
+ filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
+ } else {
+ filters += crop_string (Position (0, 0), decoder->native_size());
+ }
avfilter_register_all ();
@@ -133,8 +138,11 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, Decoder* decoder, bool crop, Si
/* XXX: leaking `inputs' / `outputs' ? */
}
+/** Take an AVFrame and process it using our configured filters, returning a
+ * set of Images.
+ */
list<shared_ptr<Image> >
-FilterGraph::process (AVFrame* frame)
+FilterGraph::process (AVFrame const * frame)
{
list<shared_ptr<Image> > images;
@@ -195,6 +203,10 @@ FilterGraph::process (AVFrame* frame)
return images;
}
+/** @param s Image size.
+ * @param p Pixel format.
+ * @return true if this chain can process images with `s' and `p', otherwise false.
+ */
bool
FilterGraph::can_process (Size s, AVPixelFormat p) const
{
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index 53908738e..3842e9f7d 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -17,28 +17,35 @@
*/
+/** @file src/lib/filter_graph.h
+ * @brief A graph of FFmpeg filters.
+ */
+
#ifndef DVDOMATIC_FILTER_GRAPH_H
#define DVDOMATIC_FILTER_GRAPH_H
#include "util.h"
-class Decoder;
class Image;
-class Film;
+class VideoFilter;
+class FFmpegDecoder;
+/** @class FilterGraph
+ * @brief A graph of FFmpeg filters.
+ */
class FilterGraph
{
public:
- FilterGraph (boost::shared_ptr<Film> film, Decoder* decoder, bool crop, Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p);
bool can_process (Size s, AVPixelFormat p) const;
- std::list<boost::shared_ptr<Image> > process (AVFrame* frame);
+ std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
private:
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
- Size _size;
- AVPixelFormat _pixel_format;
+ Size _size; ///< size of the images that this chain can process
+ AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
};
#endif
diff --git a/src/lib/gain.cc b/src/lib/gain.cc
new file mode 100644
index 000000000..cec3b3c62
--- /dev/null
+++ b/src/lib/gain.cc
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "gain.h"
+
+using boost::shared_ptr;
+
+/** @param gain gain in dB */
+Gain::Gain (Log* log, float gain)
+ : AudioProcessor (log)
+ , _gain (gain)
+{
+
+}
+
+void
+Gain::process_audio (shared_ptr<AudioBuffers> b)
+{
+ if (_gain != 0) {
+ float const linear_gain = pow (10, _gain / 20);
+ for (int i = 0; i < b->channels(); ++i) {
+ for (int j = 0; j < b->frames(); ++j) {
+ b->data(i)[j] *= linear_gain;
+ }
+ }
+ }
+
+ Audio (b);
+}
diff --git a/src/lib/gain.h b/src/lib/gain.h
new file mode 100644
index 000000000..716ee9b51
--- /dev/null
+++ b/src/lib/gain.h
@@ -0,0 +1,31 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "processor.h"
+
+class Gain : public AudioProcessor
+{
+public:
+ Gain (Log* log, float gain);
+
+ void process_audio (boost::shared_ptr<AudioBuffers>);
+
+private:
+ float _gain;
+};
diff --git a/src/lib/image.cc b/src/lib/image.cc
index c8303115b..72828ed46 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -293,7 +293,7 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
* @param p Pixel format.
* @param s Size in pixels.
*/
-SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int)> rounder)
+SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int, int const *)> stride_computer)
: Image (p)
, _size (s)
{
@@ -329,7 +329,7 @@ SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int)> rounder)
}
for (int i = 0; i < components(); ++i) {
- _stride[i] = rounder (_line_size[i]);
+ _stride[i] = stride_computer (i, _line_size);
_data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i));
}
}
@@ -371,19 +371,39 @@ SimpleImage::size () const
}
AlignedImage::AlignedImage (AVPixelFormat f, Size s)
- : SimpleImage (f, s, boost::bind (round_up, _1, 32))
+ : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 32))
{
}
+AlignedImage::AlignedImage (shared_ptr<Image> im)
+ : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 32))
+{
+ assert (components() == im->components());
+
+ for (int c = 0; c < components(); ++c) {
+
+ assert (line_size()[c] == im->line_size()[c]);
+
+ uint8_t* t = data()[c];
+ uint8_t* o = im->data()[c];
+
+ for (int y = 0; y < lines(c); ++y) {
+ memcpy (t, o, line_size()[c]);
+ t += stride()[c];
+ o += im->stride()[c];
+ }
+ }
+}
+
CompactImage::CompactImage (AVPixelFormat f, Size s)
- : SimpleImage (f, s, boost::bind (round_up, _1, 1))
+ : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 1))
{
}
CompactImage::CompactImage (shared_ptr<Image> im)
- : SimpleImage (im->pixel_format(), im->size(), boost::bind (round_up, _1, 1))
+ : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 1))
{
assert (components() == im->components());
@@ -439,53 +459,3 @@ FilterBufferImage::size () const
return Size (_buffer->video->w, _buffer->video->h);
}
-/** XXX: this could be generalised to use any format, but I don't
- * understand how avpicture_fill is supposed to be called with
- * multi-planar images.
- */
-RGBFrameImage::RGBFrameImage (Size s)
- : Image (PIX_FMT_RGB24)
- , _size (s)
-{
- _frame = avcodec_alloc_frame ();
- if (_frame == 0) {
- throw EncodeError ("could not allocate frame");
- }
-
- _data = (uint8_t *) av_malloc (size().width * size().height * 3);
- avpicture_fill ((AVPicture *) _frame, _data, PIX_FMT_RGB24, size().width, size().height);
- _frame->width = size().width;
- _frame->height = size().height;
- _frame->format = PIX_FMT_RGB24;
-}
-
-RGBFrameImage::~RGBFrameImage ()
-{
- av_free (_data);
- av_free (_frame);
-}
-
-uint8_t **
-RGBFrameImage::data () const
-{
- return _frame->data;
-}
-
-int *
-RGBFrameImage::line_size () const
-{
- return _frame->linesize;
-}
-
-int *
-RGBFrameImage::stride () const
-{
- /* XXX? */
- return line_size ();
-}
-
-Size
-RGBFrameImage::size () const
-{
- return _size;
-}
diff --git a/src/lib/image.h b/src/lib/image.h
index b2b987279..7c118f338 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -111,7 +111,7 @@ private:
class SimpleImage : public Image
{
public:
- SimpleImage (AVPixelFormat, Size, boost::function<int (int)> rounder);
+ SimpleImage (AVPixelFormat, Size, boost::function<int (int, int const *)> rounder);
~SimpleImage ();
uint8_t ** data () const;
@@ -127,12 +127,19 @@ private:
int* _stride; ///< array of strides for each line (including any alignment padding bytes)
};
+/** @class AlignedImage
+ * @brief An image whose pixel data is padded so that rows always start on 32-byte boundaries.
+ */
class AlignedImage : public SimpleImage
{
public:
AlignedImage (AVPixelFormat, Size);
+ AlignedImage (boost::shared_ptr<Image>);
};
+/** @class CompactImage
+ * @brief An image whose pixel data is not padded, so rows may start at any pixel alignment.
+ */
class CompactImage : public SimpleImage
{
public:
@@ -140,27 +147,4 @@ public:
CompactImage (boost::shared_ptr<Image>);
};
-/** @class RGBFrameImage
- * @brief An RGB image that is held within an AVFrame.
- */
-class RGBFrameImage : public Image
-{
-public:
- RGBFrameImage (Size);
- ~RGBFrameImage ();
-
- uint8_t ** data () const;
- int * line_size () const;
- int * stride () const;
- Size size () const;
- AVFrame * frame () const {
- return _frame;
- }
-
-private:
- Size _size;
- AVFrame* _frame;
- uint8_t* _data;
-};
-
#endif
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
index cc2fd9d23..d68c1648f 100644
--- a/src/lib/imagemagick_decoder.cc
+++ b/src/lib/imagemagick_decoder.cc
@@ -18,60 +18,90 @@
*/
#include <iostream>
+#include <boost/filesystem.hpp>
#include <Magick++.h>
#include "imagemagick_decoder.h"
#include "image.h"
#include "film.h"
+#include "exceptions.h"
-using namespace std;
-using namespace boost;
+using std::cout;
+using boost::shared_ptr;
ImageMagickDecoder::ImageMagickDecoder (
boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j)
: Decoder (f, o, j)
- , _done (false)
+ , VideoDecoder (f, o, j)
{
- _magick_image = new Magick::Image (_film->content_path ());
+ 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 ();
}
Size
ImageMagickDecoder::native_size () const
{
- return Size (_magick_image->columns(), _magick_image->rows());
+ 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 ());
+ Size const s = Size (image->columns(), image->rows());
+ delete image;
+
+ return s;
}
bool
ImageMagickDecoder::pass ()
{
- using namespace MagickCore;
-
- if (_done) {
+ if (_iter == _files.end()) {
return true;
}
+
+ using namespace MagickCore;
+ Magick::Image* magick_image = new Magick::Image (_film->content_path ());
+
Size size = native_size ();
- RGBFrameImage image (size);
+ shared_ptr<CompactImage> image (new CompactImage (PIX_FMT_RGB24, size));
- 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);
+ Magick::Color c = magick_image->pixelColor (x, y);
*p++ = c.redQuantum() * 255 / QuantumRange;
*p++ = c.greenQuantum() * 255 / QuantumRange;
*p++ = c.blueQuantum() * 255 / QuantumRange;
}
-
}
+
+ delete magick_image;
- process_video (image.frame ());
+ emit_video (image);
- _done = true;
+ ++_iter;
return false;
}
PixelFormat
ImageMagickDecoder::pixel_format () const
{
+ /* XXX: always true? */
return PIX_FMT_RGB24;
}
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
index b7ab9af18..f636191f2 100644
--- a/src/lib/imagemagick_decoder.h
+++ b/src/lib/imagemagick_decoder.h
@@ -17,19 +17,20 @@
*/
-#include "decoder.h"
+#include "video_decoder.h"
namespace Magick {
class Image;
}
-class ImageMagickDecoder : public Decoder
+class ImageMagickDecoder : public VideoDecoder
{
public:
ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
float frames_per_second () const {
- return static_frames_per_second ();
+ /* We don't know */
+ return 0;
}
Size native_size () const;
@@ -42,10 +43,6 @@ public:
return 0;
}
- AVSampleFormat audio_sample_format () const {
- return AV_SAMPLE_FMT_NONE;
- }
-
int64_t audio_channel_layout () const {
return 0;
}
@@ -54,10 +51,6 @@ public:
return false;
}
- static float static_frames_per_second () {
- return 24;
- }
-
protected:
bool pass ();
PixelFormat pixel_format () const;
@@ -81,6 +74,6 @@ protected:
}
private:
- Magick::Image* _magick_image;
- bool _done;
+ std::list<std::string> _files;
+ std::list<std::string>::iterator _iter;
};
diff --git a/src/lib/imagemagick_encoder.cc b/src/lib/imagemagick_encoder.cc
index 6c70e3749..480dec8bc 100644
--- a/src/lib/imagemagick_encoder.cc
+++ b/src/lib/imagemagick_encoder.cc
@@ -50,22 +50,22 @@ ImageMagickEncoder::ImageMagickEncoder (shared_ptr<const Film> f, shared_ptr<con
}
void
-ImageMagickEncoder::do_process_video (shared_ptr<const Image> image, SourceFrame frame, shared_ptr<Subtitle> sub)
+ImageMagickEncoder::do_process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
{
shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _film->scaler());
shared_ptr<Image> compact (new CompactImage (scaled));
- string tmp_file = _opt->frame_out_path (frame, true);
+ string tmp_file = _opt->frame_out_path (_video_frame, true);
Magick::Image thumb (compact->size().width, compact->size().height, "RGB", MagickCore::CharPixel, compact->data()[0]);
thumb.magick ("PNG");
thumb.write (tmp_file);
- boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false));
+ boost::filesystem::rename (tmp_file, _opt->frame_out_path (_video_frame, false));
if (sub) {
float const x_scale = float (_opt->out_size.width) / _film->size().width;
float const y_scale = float (_opt->out_size.height) / _film->size().height;
- string tmp_metadata_file = _opt->frame_out_path (frame, false, ".sub");
+ string tmp_metadata_file = _opt->frame_out_path (_video_frame, false, ".sub");
ofstream metadata (tmp_metadata_file.c_str ());
Size new_size = sub->image()->size ();
@@ -74,18 +74,18 @@ ImageMagickEncoder::do_process_video (shared_ptr<const Image> image, SourceFrame
shared_ptr<Image> scaled = sub->image()->scale (new_size, _film->scaler());
shared_ptr<Image> compact (new CompactImage (scaled));
- string tmp_sub_file = _opt->frame_out_path (frame, true, ".sub.png");
+ string tmp_sub_file = _opt->frame_out_path (_video_frame, true, ".sub.png");
Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]);
sub_thumb.magick ("PNG");
sub_thumb.write (tmp_sub_file);
- boost::filesystem::rename (tmp_sub_file, _opt->frame_out_path (frame, false, ".sub.png"));
+ boost::filesystem::rename (tmp_sub_file, _opt->frame_out_path (_video_frame, false, ".sub.png"));
metadata << "x " << sub->position().x << "\n"
<< "y " << sub->position().y << "\n";
metadata.close ();
- boost::filesystem::rename (tmp_metadata_file, _opt->frame_out_path (frame, false, ".sub"));
+ boost::filesystem::rename (tmp_metadata_file, _opt->frame_out_path (_video_frame, false, ".sub"));
}
- frame_done (frame);
+ frame_done ();
}
diff --git a/src/lib/imagemagick_encoder.h b/src/lib/imagemagick_encoder.h
index 9adfbf56b..dfc741cb2 100644
--- a/src/lib/imagemagick_encoder.h
+++ b/src/lib/imagemagick_encoder.h
@@ -36,10 +36,7 @@ class ImageMagickEncoder : public Encoder
public:
ImageMagickEncoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o);
- void process_begin (int64_t audio_channel_layout) {}
- void process_end () {}
-
private:
- void do_process_video (boost::shared_ptr<const Image>, SourceFrame, boost::shared_ptr<Subtitle>);
- void do_process_audio (boost::shared_ptr<const AudioBuffers>) {}
+ void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
+ void do_process_audio (boost::shared_ptr<AudioBuffers>) {}
};
diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc
index 2d6c457fc..dd6ef49b2 100644
--- a/src/lib/j2k_still_encoder.cc
+++ b/src/lib/j2k_still_encoder.cc
@@ -49,7 +49,7 @@ J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const Opt
}
void
-J2KStillEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame frame, shared_ptr<Subtitle> sub)
+J2KStillEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> sub)
{
pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
DCPVideoFrame* f = new DCPVideoFrame (
@@ -64,7 +64,7 @@ J2KStillEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame fram
}
string const real = _opt->frame_out_path (0, false);
- for (int i = 1; i < (_film->still_duration() * ImageMagickDecoder::static_frames_per_second()); ++i) {
+ for (int i = 1; i < (_film->still_duration() * 24); ++i) {
if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) {
string const link = _opt->frame_out_path (i, false);
#ifdef DVDOMATIC_POSIX
@@ -77,6 +77,6 @@ J2KStillEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame fram
boost::filesystem::copy_file (real, link);
#endif
}
- frame_done (0);
+ frame_done ();
}
}
diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h
index bc324e967..4ffe876af 100644
--- a/src/lib/j2k_still_encoder.h
+++ b/src/lib/j2k_still_encoder.h
@@ -36,10 +36,7 @@ class J2KStillEncoder : public Encoder
public:
J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>);
- void process_begin (int64_t audio_channel_layout) {}
- void process_end () {}
-
private:
- void do_process_video (boost::shared_ptr<const Image>, SourceFrame, boost::shared_ptr<Subtitle>);
- void do_process_audio (boost::shared_ptr<const AudioBuffers>) {}
+ void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
+ void do_process_audio (boost::shared_ptr<AudioBuffers>) {}
};
diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc
index 6d777babb..134d74623 100644
--- a/src/lib/j2k_wav_encoder.cc
+++ b/src/lib/j2k_wav_encoder.cc
@@ -46,6 +46,7 @@ using std::stringstream;
using std::list;
using std::vector;
using std::pair;
+using std::cout;
using boost::shared_ptr;
using boost::thread;
using boost::lexical_cast;
@@ -58,20 +59,22 @@ J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const Film> f, shared_ptr<const Options
, _audio_frames_written (0)
, _process_end (false)
{
- /* Create sound output files with .tmp suffixes; we will rename
- them if and when we complete.
- */
- for (int i = 0; i < _film->audio_channels(); ++i) {
- SF_INFO sf_info;
- sf_info.samplerate = dcp_audio_sample_rate (_film->audio_sample_rate());
- /* We write mono files */
- sf_info.channels = 1;
- sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
- SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info);
- if (f == 0) {
- throw CreateFileError (_opt->multichannel_audio_out_path (i, true));
+ if (_film->audio_stream()) {
+ /* Create sound output files with .tmp suffixes; we will rename
+ them if and when we complete.
+ */
+ for (int i = 0; i < _film->audio_channels(); ++i) {
+ SF_INFO sf_info;
+ sf_info.samplerate = dcp_audio_sample_rate (_film->audio_stream()->sample_rate());
+ /* We write mono files */
+ sf_info.channels = 1;
+ sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
+ SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info);
+ if (f == 0) {
+ throw CreateFileError (_opt->multichannel_audio_out_path (i, true));
+ }
+ _sound_files.push_back (f);
}
- _sound_files.push_back (f);
}
}
@@ -106,7 +109,7 @@ J2KWAVEncoder::close_sound_files ()
}
void
-J2KWAVEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame frame, shared_ptr<Subtitle> sub)
+J2KWAVEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> sub)
{
boost::mutex::scoped_lock lock (_worker_mutex);
@@ -122,13 +125,13 @@ J2KWAVEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame frame,
}
/* Only do the processing if we don't already have a file for this frame */
- if (!boost::filesystem::exists (_opt->frame_out_path (frame, false))) {
+ if (!boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) {
pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
TIMING ("adding to queue of %1", _queue.size ());
_queue.push_back (boost::shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
yuv, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(),
- _film->scaler(), frame, _film->frames_per_second(), s.second,
+ _film->scaler(), _video_frame, _film->frames_per_second(), s.second,
Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (),
_film->log()
)
@@ -205,7 +208,7 @@ J2KWAVEncoder::encoder_thread (ServerDescription* server)
if (encoded) {
encoded->write (_opt, vf->frame ());
- frame_done (vf->frame ());
+ frame_done ();
} else {
lock.lock ();
_film->log()->log (
@@ -225,24 +228,24 @@ J2KWAVEncoder::encoder_thread (ServerDescription* server)
}
void
-J2KWAVEncoder::process_begin (int64_t audio_channel_layout)
+J2KWAVEncoder::process_begin ()
{
- if (_film->audio_sample_rate() != _film->target_audio_sample_rate()) {
+ if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
#ifdef HAVE_SWRESAMPLE
stringstream s;
- s << "Will resample audio from " << _film->audio_sample_rate() << " to " << _film->target_audio_sample_rate();
+ s << "Will resample audio from " << _film->audio_stream()->sample_rate() << " to " << _film->target_audio_sample_rate();
_film->log()->log (s.str ());
/* We will be using planar float data when we call the resampler */
_swr_context = swr_alloc_set_opts (
0,
- audio_channel_layout,
+ _film->audio_stream()->channel_layout(),
AV_SAMPLE_FMT_FLTP,
_film->target_audio_sample_rate(),
- audio_channel_layout,
+ _film->audio_stream()->channel_layout(),
AV_SAMPLE_FMT_FLTP,
- _film->audio_sample_rate(),
+ _film->audio_stream()->sample_rate(),
0, 0
);
@@ -303,16 +306,16 @@ J2KWAVEncoder::process_end ()
try {
shared_ptr<EncodedData> e = (*i)->encode_locally ();
e->write (_opt, (*i)->frame ());
- frame_done ((*i)->frame ());
+ frame_done ();
} catch (std::exception& e) {
_film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
}
}
#if HAVE_SWRESAMPLE
- if (_swr_context) {
+ if (_film->audio_stream() && _swr_context) {
- shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_channels(), 256));
+ shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256));
while (1) {
int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
@@ -333,25 +336,21 @@ J2KWAVEncoder::process_end ()
}
#endif
- int const dcp_sr = dcp_audio_sample_rate (_film->audio_sample_rate ());
- int64_t const extra_audio_frames = dcp_sr - (_audio_frames_written % dcp_sr);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), extra_audio_frames));
- silence->make_silent ();
- write_audio (silence);
-
- close_sound_files ();
-
- /* Rename .wav.tmp files to .wav */
- for (int i = 0; i < _film->audio_channels(); ++i) {
- if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) {
- boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false));
+ if (_film->audio_stream()) {
+ close_sound_files ();
+
+ /* Rename .wav.tmp files to .wav */
+ for (int i = 0; i < _film->audio_channels(); ++i) {
+ if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) {
+ boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false));
+ }
+ boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false));
}
- boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false));
}
}
void
-J2KWAVEncoder::do_process_audio (shared_ptr<const AudioBuffers> audio)
+J2KWAVEncoder::do_process_audio (shared_ptr<AudioBuffers> audio)
{
shared_ptr<AudioBuffers> resampled;
@@ -360,9 +359,9 @@ J2KWAVEncoder::do_process_audio (shared_ptr<const AudioBuffers> audio)
if (_swr_context) {
/* Compute the resampled frames count and add 32 for luck */
- int const max_resampled_frames = ceil (audio->frames() * _film->target_audio_sample_rate() / _film->audio_sample_rate()) + 32;
+ int const max_resampled_frames = ceil ((int64_t) audio->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32;
- resampled.reset (new AudioBuffers (_film->audio_channels(), max_resampled_frames));
+ resampled.reset (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames));
/* Resample audio */
int const resampled_frames = swr_convert (
diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h
index b494be8e5..f3340ba72 100644
--- a/src/lib/j2k_wav_encoder.h
+++ b/src/lib/j2k_wav_encoder.h
@@ -50,13 +50,13 @@ public:
J2KWAVEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>);
~J2KWAVEncoder ();
- void process_begin (int64_t audio_channel_layout);
+ void process_begin ();
void process_end ();
private:
- void do_process_video (boost::shared_ptr<const Image>, SourceFrame, boost::shared_ptr<Subtitle>);
- void do_process_audio (boost::shared_ptr<const AudioBuffers>);
+ void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
+ void do_process_audio (boost::shared_ptr<AudioBuffers>);
void write_audio (boost::shared_ptr<const AudioBuffers> audio);
void encoder_thread (ServerDescription *);
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 3309fe16c..201397f08 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -33,6 +33,7 @@ using std::stringstream;
using boost::shared_ptr;
/** @param s Film that we are operating on.
+ * @param req Job that must be completed before this job is run.
*/
Job::Job (shared_ptr<Film> f, shared_ptr<Job> req)
: _film (f)
@@ -77,6 +78,7 @@ Job::run_wrapper ()
}
}
+/** @return true if this job is new (ie has not started running) */
bool
Job::is_new () const
{
diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc
index a1ffd9672..026724806 100644
--- a/src/lib/make_dcp_job.cc
+++ b/src/lib/make_dcp_job.cc
@@ -90,7 +90,7 @@ MakeDCPJob::run ()
frames = _film->dcp_length().get() / dfr.skip;
break;
case STILL:
- frames = _film->still_duration() * ImageMagickDecoder::static_frames_per_second ();
+ frames = _film->still_duration() * 24;
break;
}
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
new file mode 100644
index 000000000..7b4434539
--- /dev/null
+++ b/src/lib/matcher.cc
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "matcher.h"
+#include "image.h"
+#include "log.h"
+
+using std::min;
+using boost::shared_ptr;
+
+Matcher::Matcher (Log* log, int sample_rate, float frames_per_second)
+ : AudioVideoProcessor (log)
+ , _sample_rate (sample_rate)
+ , _frames_per_second (frames_per_second)
+ , _video_frames (0)
+ , _audio_frames (0)
+{
+
+}
+
+void
+Matcher::process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s)
+{
+ Video (i, s);
+ _video_frames++;
+
+ _pixel_format = i->pixel_format ();
+ _size = i->size ();
+}
+
+void
+Matcher::process_audio (boost::shared_ptr<AudioBuffers> b)
+{
+ Audio (b);
+ _audio_frames += b->frames ();
+
+ _channels = b->channels ();
+}
+
+void
+Matcher::process_end ()
+{
+ if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) {
+ /* We won't do anything */
+ return;
+ }
+
+ int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
+
+ _log->log (
+ String::compose (
+ "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
+ )
+ );
+
+ if (audio_short_by_frames < 0) {
+
+ _log->log (String::compose ("%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);
+
+ _log->log (String::compose ("Emitting %1 frames of black video", black_video_frames));
+
+ shared_ptr<Image> black (new CompactImage (_pixel_format.get(), _size.get()));
+ black->make_black ();
+ for (int i = 0; i < black_video_frames; ++i) {
+ Video (black, shared_ptr<Subtitle>());
+ }
+
+ /* Now recompute our check value */
+ audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames;
+ }
+
+ if (audio_short_by_frames > 0) {
+ _log->log (String::compose ("Emitted %1 too few audio frames", audio_short_by_frames));
+
+ /* Do things in half second blocks as I think there may be limits
+ to what FFmpeg (and in particular the resampler) can cope with.
+ */
+ int64_t const block = _sample_rate / 2;
+ shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
+ b->make_silent ();
+
+ int64_t to_do = audio_short_by_frames;
+ while (to_do > 0) {
+ int64_t const this_time = min (to_do, block);
+ b->set_frames (this_time);
+ Audio (b);
+ _audio_frames += b->frames ();
+ to_do -= this_time;
+ }
+ }
+}
diff --git a/src/lib/matcher.h b/src/lib/matcher.h
new file mode 100644
index 000000000..03efd5cc0
--- /dev/null
+++ b/src/lib/matcher.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/optional.hpp>
+#include "processor.h"
+
+class Matcher : public AudioVideoProcessor
+{
+public:
+ Matcher (Log* log, int sample_rate, float frames_per_second);
+ void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s);
+ void process_audio (boost::shared_ptr<AudioBuffers>);
+ void process_end ();
+
+private:
+ int _sample_rate;
+ float _frames_per_second;
+ int _video_frames;
+ int64_t _audio_frames;
+ boost::optional<AVPixelFormat> _pixel_format;
+ boost::optional<Size> _size;
+ boost::optional<int> _channels;
+};
diff --git a/src/lib/processor.h b/src/lib/processor.h
new file mode 100644
index 000000000..e632f813f
--- /dev/null
+++ b/src/lib/processor.h
@@ -0,0 +1,96 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/processor.h
+ * @brief Parent class for classes which accept and then emit video or audio data.
+ */
+
+#ifndef DVDOMATIC_PROCESSOR_H
+#define DVDOMATIC_PROCESSOR_H
+
+#include "video_source.h"
+#include "video_sink.h"
+#include "audio_source.h"
+#include "audio_sink.h"
+
+class Log;
+
+/** @class Processor
+ * @brief Base class for processors.
+ */
+class Processor
+{
+public:
+ /** Construct a Processor.
+ * @param log Log to use.
+ */
+ Processor (Log* log)
+ : _log (log)
+ {}
+
+ /** Will be called at the end of a processing run */
+ virtual void process_end () {}
+
+protected:
+ Log* _log; ///< log to write to
+};
+
+/** @class AudioVideoProcessor
+ * @brief A processor which handles both video and audio data.
+ */
+class AudioVideoProcessor : public Processor, public VideoSource, public VideoSink, public AudioSource, public AudioSink
+{
+public:
+ /** Construct an AudioVideoProcessor.
+ * @param log Log to write to.
+ */
+ AudioVideoProcessor (Log* log)
+ : Processor (log)
+ {}
+};
+
+/** @class AudioProcessor
+ * @brief A processor which handles just audio data.
+ */
+class AudioProcessor : public Processor, public AudioSource, public AudioSink
+{
+public:
+ /** Construct an AudioProcessor.
+ * @param log Log to write to.
+ */
+ AudioProcessor (Log* log)
+ : Processor (log)
+ {}
+};
+
+/** @class VideoProcessor
+ * @brief A processor which handles just video data.
+ */
+class VideoProcessor : public Processor, public VideoSource, public VideoSink
+{
+public:
+ /** Construct an VideoProcessor.
+ * @param log Log to write to.
+ */
+ VideoProcessor (Log* log)
+ : Processor (log)
+ {}
+};
+
+#endif
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
index d1c2b5a9e..4f12f41b9 100644
--- a/src/lib/stream.cc
+++ b/src/lib/stream.cc
@@ -20,43 +20,71 @@
#include <sstream>
#include "compose.hpp"
#include "stream.h"
+#include "ffmpeg_decoder.h"
+#include "external_audio_decoder.h"
-using namespace std;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+using boost::optional;
-AudioStream::AudioStream (string t)
+/** Construct a SubtitleStream from a value returned from to_string().
+ * @param t String returned from to_string().
+ * @param v State file version.
+ */
+SubtitleStream::SubtitleStream (string t, boost::optional<int>)
{
stringstream n (t);
- n >> _id >> _channels;
+ n >> _id;
- for (int i = 0; i < 2; ++i) {
- size_t const s = t.find (' ');
- if (s != string::npos) {
- t = t.substr (s + 1);
- }
+ size_t const s = t.find (' ');
+ if (s != string::npos) {
+ _name = t.substr (s + 1);
}
-
- _name = t;
}
+/** @return A canonical string representation of this stream */
string
-AudioStream::to_string () const
+SubtitleStream::to_string () const
{
- return String::compose ("%1 %2 %3", _id, _channels, _name);
+ return String::compose ("%1 %2", _id, _name);
}
-SubtitleStream::SubtitleStream (string t)
+/** Create a SubtitleStream from a value returned from to_string().
+ * @param t String returned from to_string().
+ * @param v State file version.
+ */
+shared_ptr<SubtitleStream>
+SubtitleStream::create (string t, optional<int> v)
{
- stringstream n (t);
- n >> _id;
+ return shared_ptr<SubtitleStream> (new SubtitleStream (t, v));
+}
- size_t const s = t.find (' ');
- if (s != string::npos) {
- _name = t.substr (s + 1);
+/** Create an AudioStream from a string returned from to_string().
+ * @param t String returned from to_string().
+ * @param v State file version.
+ * @return AudioStream, or 0.
+ */
+shared_ptr<AudioStream>
+audio_stream_factory (string t, optional<int> v)
+{
+ shared_ptr<AudioStream> s;
+
+ s = FFmpegAudioStream::create (t, v);
+ if (!s) {
+ s = ExternalAudioStream::create (t, v);
}
+
+ return s;
}
-string
-SubtitleStream::to_string () const
+/** Create a SubtitleStream from a string returned from to_string().
+ * @param t String returned from to_string().
+ * @param v State file version.
+ * @return SubtitleStream, or 0.
+ */
+shared_ptr<SubtitleStream>
+subtitle_stream_factory (string t, optional<int> v)
{
- return String::compose ("%1 %2", _id, _name);
+ return SubtitleStream::create (t, v);
}
diff --git a/src/lib/stream.h b/src/lib/stream.h
index 2db63c620..c2a19e5ab 100644
--- a/src/lib/stream.h
+++ b/src/lib/stream.h
@@ -17,66 +17,104 @@
*/
+/** @file src/lib/stream.h
+ * @brief Representations of audio and subtitle streams.
+ *
+ * Some content may have multiple `streams' of audio and/or subtitles; perhaps
+ * for multiple languages, or for stereo / surround mixes. These classes represent
+ * those streams, and know about their details.
+ */
+
#ifndef DVDOMATIC_STREAM_H
#define DVDOMATIC_STREAM_H
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+extern "C" {
+#include <libavutil/audioconvert.h>
+}
+
+/** @class Stream
+ * @brief Parent class for streams.
+ */
class Stream
{
public:
- Stream ()
- : _id (-1)
- {}
-
- Stream (std::string n, int i)
- : _name (n)
- , _id (i)
- {}
-
virtual std::string to_string () const = 0;
-
- std::string name () const {
- return _name;
- }
-
- int id () const {
- return _id;
- }
-
-protected:
- std::string _name;
- int _id;
};
+/** @class AudioStream
+ * @brief A stream of audio data.
+ */
struct AudioStream : public Stream
{
public:
- AudioStream (std::string t);
-
- AudioStream (std::string n, int i, int c)
- : Stream (n, i)
- , _channels (c)
+ AudioStream (int r, int64_t l)
+ : _sample_rate (r)
+ , _channel_layout (l)
{}
- std::string to_string () const;
+ /* Only used for backwards compatibility for state file version < 1 */
+ void set_sample_rate (int s) {
+ _sample_rate = s;
+ }
int channels () const {
- return _channels;
+ return av_get_channel_layout_nb_channels (_channel_layout);
}
-private:
- int _channels;
+ int sample_rate () const {
+ return _sample_rate;
+ }
+
+ int64_t channel_layout () const {
+ return _channel_layout;
+ }
+
+protected:
+ AudioStream ()
+ : _sample_rate (0)
+ , _channel_layout (0)
+ {}
+
+ int _sample_rate;
+ int64_t _channel_layout;
};
+/** @class SubtitleStream
+ * @brief A stream of subtitle data.
+ */
class SubtitleStream : public Stream
{
public:
- SubtitleStream (std::string t);
-
SubtitleStream (std::string n, int i)
- : Stream (n, i)
+ : _name (n)
+ , _id (i)
{}
std::string to_string () const;
+
+ std::string name () const {
+ return _name;
+ }
+
+ int id () const {
+ return _id;
+ }
+
+ static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v);
+
+private:
+ friend class stream_test;
+
+ SubtitleStream (std::string t, boost::optional<int> v);
+
+ std::string _name;
+ int _id;
};
+boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version);
+boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version);
+
#endif
diff --git a/src/lib/tiff_decoder.cc b/src/lib/tiff_decoder.cc
deleted file mode 100644
index 7cca511dd..000000000
--- a/src/lib/tiff_decoder.cc
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/tiff_decoder.cc
- * @brief A decoder which reads a numbered set of TIFF files, one per frame.
- */
-
-#include <vector>
-#include <string>
-#include <iostream>
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/filesystem.hpp>
-#include <tiffio.h>
-extern "C" {
-#include <libavformat/avformat.h>
-}
-#include "util.h"
-#include "tiff_decoder.h"
-#include "exceptions.h"
-#include "image.h"
-#include "options.h"
-#include "film.h"
-
-using namespace std;
-using namespace boost;
-
-/** @param f Our Film.
- * @param o Options.
- * @param j Job that we are associated with, or 0.
- */
-TIFFDecoder::TIFFDecoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j)
- : Decoder (f, o, j)
-{
- string const dir = _film->content_path ();
-
- if (!filesystem::is_directory (dir)) {
- throw DecodeError ("TIFF content must be in a directory");
- }
-
- for (filesystem::directory_iterator i = filesystem::directory_iterator (dir); i != filesystem::directory_iterator(); ++i) {
- /* Aah, the sweet smell of progress */
-#if BOOST_FILESYSTEM_VERSION == 3
- string const ext = filesystem::path(*i).extension().string();
- string const l = filesystem::path(*i).leaf().generic_string();
-#else
- string const ext = filesystem::path(*i).extension();
- string const l = i->leaf ();
-#endif
- if (ext == ".tif" || ext == ".tiff") {
- _files.push_back (l);
- }
- }
-
- _files.sort ();
-
- _iter = _files.begin ();
-
-}
-
-float
-TIFFDecoder::frames_per_second () const
-{
- /* We don't know */
- return 0;
-}
-
-Size
-TIFFDecoder::native_size () const
-{
- if (_files.empty ()) {
- throw DecodeError ("no TIFF files found");
- }
-
- TIFF* t = TIFFOpen (file_path (_files.front()).c_str (), "r");
- if (t == 0) {
- throw DecodeError ("could not open TIFF file");
- }
-
- uint32_t width;
- TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width);
- uint32_t height;
- TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height);
-
- return Size (width, height);
-}
-
-int
-TIFFDecoder::audio_channels () const
-{
- /* No audio */
- return 0;
-}
-
-int
-TIFFDecoder::audio_sample_rate () const
-{
- return 0;
-}
-
-AVSampleFormat
-TIFFDecoder::audio_sample_format () const
-{
- return AV_SAMPLE_FMT_NONE;
-}
-
-
-int64_t
-TIFFDecoder::audio_channel_layout () const
-{
- return 0;
-}
-
-bool
-TIFFDecoder::pass ()
-{
- if (_iter == _files.end ()) {
- return true;
- }
-
- TIFF* t = TIFFOpen (file_path (*_iter).c_str (), "r");
- if (t == 0) {
- throw DecodeError ("could not open TIFF file");
- }
-
- uint32_t width;
- TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width);
- uint32_t height;
- TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height);
-
- int const num_pixels = width * height;
- uint32_t * raster = (uint32_t *) _TIFFmalloc (num_pixels * sizeof (uint32_t));
- if (raster == 0) {
- throw DecodeError ("could not allocate memory to decode TIFF file");
- }
-
- if (TIFFReadRGBAImage (t, width, height, raster, 0) < 0) {
- throw DecodeError ("could not read TIFF data");
- }
-
- RGBFrameImage image (Size (width, height));
-
- uint8_t* p = image.data()[0];
- for (uint32_t y = 0; y < height; ++y) {
- for (uint32_t x = 0; x < width; ++x) {
- uint32_t const i = (height - y - 1) * width + x;
- *p++ = raster[i] & 0xff;
- *p++ = (raster[i] & 0xff00) >> 8;
- *p++ = (raster[i] & 0xff0000) >> 16;
- }
- }
-
- _TIFFfree (raster);
- TIFFClose (t);
-
- process_video (image.frame ());
-
- ++_iter;
- return false;
-}
-
-/** @param file name within our content directory
- * @return full path to the file
- */
-string
-TIFFDecoder::file_path (string f) const
-{
- stringstream s;
- s << _film->content_path() << "/" << f;
- return _film->file (s.str ());
-}
-
-PixelFormat
-TIFFDecoder::pixel_format () const
-{
- return PIX_FMT_RGB24;
-}
-
-int
-TIFFDecoder::time_base_numerator () const
-{
- return dcp_frame_rate(_film->frames_per_second()).frames_per_second;
-}
-
-
-int
-TIFFDecoder::time_base_denominator () const
-{
- return 1;
-}
-
-int
-TIFFDecoder::sample_aspect_ratio_numerator () const
-{
- /* XXX */
- return 1;
-}
-
-int
-TIFFDecoder::sample_aspect_ratio_denominator () const
-{
- /* XXX */
- return 1;
-}
diff --git a/src/lib/tiff_decoder.h b/src/lib/tiff_decoder.h
deleted file mode 100644
index 1c33443cf..000000000
--- a/src/lib/tiff_decoder.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/tiff_decoder.h
- * @brief A decoder which reads a numbered set of TIFF files, one per frame.
- */
-
-#ifndef DVDOMATIC_TIFF_DECODER_H
-#define DVDOMATIC_TIFF_DECODER_H
-
-#include <vector>
-#include <string>
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-#include "decoder.h"
-
-class Job;
-class FilmState;
-class Options;
-class Image;
-
-/** @class TIFFDecoder.
- * @brief A decoder which reads a numbered set of TIFF files, one per frame.
- */
-class TIFFDecoder : public Decoder
-{
-public:
- TIFFDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
-
- /* Methods to query our input video */
- float frames_per_second () const;
- Size native_size () const;
- int audio_channels () const;
- int audio_sample_rate () const;
- AVSampleFormat audio_sample_format () const;
- int64_t audio_channel_layout () const;
- bool has_subtitles () const {
- return false;
- }
-
-private:
- bool pass ();
- PixelFormat pixel_format () const;
- int time_base_numerator () const;
- int time_base_denominator () const;
- int sample_aspect_ratio_numerator () const;
- int sample_aspect_ratio_denominator () const;
-
- std::string file_path (std::string) const;
- std::list<std::string> _files;
- std::list<std::string>::iterator _iter;
-};
-
-#endif
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 25581c8f5..081e04252 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -39,6 +39,7 @@ using boost::shared_ptr;
/** @param s Film to use.
* @param o Options.
+ * @param req Job that must be completed before this job is run.
*/
TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req)
: Job (f, req)
@@ -116,6 +117,6 @@ TranscodeJob::remaining_time () const
}
/* We assume that dcp_length() is valid */
- SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->last_frame();
+ SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame();
return left / fps;
}
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 66d5606af..537b9b664 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -29,9 +29,18 @@
#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"
using std::string;
+using std::cout;
using boost::shared_ptr;
+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.
@@ -42,12 +51,34 @@ using boost::shared_ptr;
Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e)
: _job (j)
, _encoder (e)
- , _decoder (decoder_factory (f, o, j))
+ , _decoders (decoder_factory (f, o, j))
{
assert (_encoder);
+
+ if (f->audio_stream()) {
+ shared_ptr<AudioStream> st = f->audio_stream();
+ _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->frames_per_second()));
+ _delay_line.reset (new DelayLine (f->log(), st->channels(), f->audio_delay() * st->sample_rate() / 1000));
+ _gain.reset (new Gain (f->log(), f->audio_gain()));
+ }
+
+ /* Set up the decoder to use the film's set streams */
+ _decoders.first->set_subtitle_stream (f->subtitle_stream ());
+ _decoders.second->set_audio_stream (f->audio_stream ());
+
+ if (_matcher) {
+ _decoders.first->connect_video (_matcher);
+ _matcher->connect_video (_encoder);
+ } else {
+ _decoders.first->connect_video (_encoder);
+ }
- _decoder->Video.connect (bind (&Encoder::process_video, e, _1, _2, _3));
- _decoder->Audio.connect (bind (&Encoder::process_audio, e, _1, _2));
+ if (_matcher && _delay_line) {
+ _decoders.second->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
@@ -56,16 +87,40 @@ Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j,
void
Transcoder::go ()
{
- _encoder->process_begin (_decoder->audio_channel_layout());
+ _encoder->process_begin ();
try {
- _decoder->go ();
+ bool done[2] = { false, false };
+
+ while (1) {
+ if (!done[0]) {
+ done[0] = _decoders.first->pass ();
+ _decoders.first->set_progress ();
+ }
+
+ if (!done[1] && dynamic_pointer_cast<Decoder> (_decoders.second) != dynamic_pointer_cast<Decoder> (_decoders.first)) {
+ done[1] = _decoders.second->pass ();
+ } else {
+ done[1] = true;
+ }
+
+ if (done[0] && done[1]) {
+ break;
+ }
+ }
+
} catch (...) {
- /* process_end() is important as the decoder may have worker
- threads that need to be cleaned up.
- */
_encoder->process_end ();
throw;
}
-
+
+ if (_delay_line) {
+ _delay_line->process_end ();
+ }
+ if (_matcher) {
+ _matcher->process_end ();
+ }
+ if (_gain) {
+ _gain->process_end ();
+ }
_encoder->process_end ();
}
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index 79ef89a18..e3ca2bb32 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -17,8 +17,6 @@
*/
-#include "decoder.h"
-
/** @file src/transcoder.h
* @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
*
@@ -30,6 +28,13 @@ class Film;
class Job;
class Encoder;
class FilmState;
+class Matcher;
+class VideoFilter;
+class Gain;
+class VideoDecoder;
+class AudioDecoder;
+class DelayLine;
+class Options;
/** @class Transcoder
* @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
@@ -44,16 +49,14 @@ public:
void go ();
- /** @return Our decoder */
- boost::shared_ptr<Decoder> decoder () {
- return _decoder;
- }
-
protected:
/** A Job that is running this Transcoder, or 0 */
Job* _job;
/** The encoder that we will use */
boost::shared_ptr<Encoder> _encoder;
- /** The decoder that we will use */
- boost::shared_ptr<Decoder> _decoder;
+ /** The decoders that we will use */
+ std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _decoders;
+ boost::shared_ptr<Matcher> _matcher;
+ boost::shared_ptr<DelayLine> _delay_line;
+ boost::shared_ptr<Gain> _gain;
};
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 6179eedac..b69581eba 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -37,6 +37,7 @@
#include <boost/lambda/lambda.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
+#include <boost/filesystem.hpp>
#include <openjpeg.h>
#include <openssl/md5.h>
#include <magick/MagickCore.h>
@@ -238,6 +239,10 @@ dvdomatic_setup ()
ui_thread = this_thread::get_id ();
}
+/** @param start Start position for the crop within the image.
+ * @param size Size of the cropped area.
+ * @return FFmpeg crop filter string.
+ */
string
crop_string (Position start, Size size)
{
@@ -246,6 +251,9 @@ crop_string (Position start, Size size)
return s.str ();
}
+/** @param s A string.
+ * @return Parts of the string split at spaces, except when a space is within quotation marks.
+ */
vector<string>
split_at_spaces_considering_quotes (string s)
{
@@ -322,6 +330,9 @@ md5_digest (string file)
return s.str ();
}
+/** @param fps Arbitrary frames-per-second value.
+ * @return DCPFrameRate for this frames-per-second.
+ */
DCPFrameRate
dcp_frame_rate (float fps)
{
@@ -548,6 +559,9 @@ Socket::read_indefinite (uint8_t* data, int size, int timeout)
memcpy (data, _buffer, size);
}
+/** @param other A Rect.
+ * @return The intersection of this with `other'.
+ */
Rect
Rect::intersection (Rect const & other) const
{
@@ -566,14 +580,19 @@ Rect::intersection (Rect const & other) const
* @param t Multiple to round to.
* @return Rounded number.
*/
-
int
-round_up (int a, int t)
+stride_round_up (int c, int const * stride, int t)
{
- a += (t - 1);
+ int const a = stride[c] + (t - 1);
return a - (a % t);
}
+int
+stride_lookup (int c, int const * stride)
+{
+ return stride[c];
+}
+
/** Read a sequence of key / value pairs from a text stream;
* the keys are the first words on the line, and the values are
* the remainder of the line following the key. Lines beginning
@@ -591,7 +610,7 @@ read_key_value (istream &s)
if (line.empty ()) {
continue;
}
-
+
if (line[0] == '#') {
continue;
}
@@ -671,6 +690,10 @@ get_optional_int (multimap<string, string> const & kv, string k)
return lexical_cast<int> (i->second);
}
+/** Construct an AudioBuffers. Audio data is undefined after this constructor.
+ * @param channels Number of channels.
+ * @param frames Number of frames to reserve space for.
+ */
AudioBuffers::AudioBuffers (int channels, int frames)
: _channels (channels)
, _frames (frames)
@@ -682,6 +705,9 @@ AudioBuffers::AudioBuffers (int channels, int frames)
}
}
+/** Copy constructor.
+ * @param other Other AudioBuffers; data is copied.
+ */
AudioBuffers::AudioBuffers (AudioBuffers const & other)
: _channels (other._channels)
, _frames (other._frames)
@@ -694,6 +720,7 @@ AudioBuffers::AudioBuffers (AudioBuffers const & other)
}
}
+/** AudioBuffers destructor */
AudioBuffers::~AudioBuffers ()
{
for (int i = 0; i < _channels; ++i) {
@@ -703,13 +730,20 @@ AudioBuffers::~AudioBuffers ()
delete[] _data;
}
+/** @param c Channel index.
+ * @return Buffer for this channel.
+ */
float*
AudioBuffers::data (int c) const
{
assert (c >= 0 && c < _channels);
return _data[c];
}
-
+
+/** Set the number of frames that these AudioBuffers will report themselves
+ * as having.
+ * @param f Frames; must be less than or equal to the number of allocated frames.
+ */
void
AudioBuffers::set_frames (int f)
{
@@ -717,26 +751,54 @@ AudioBuffers::set_frames (int f)
_frames = f;
}
+/** Make all samples on all channels silent */
void
AudioBuffers::make_silent ()
{
for (int i = 0; i < _channels; ++i) {
- for (int j = 0; j < _frames; ++j) {
- _data[i][j] = 0;
- }
+ make_silent (i);
}
}
+/** Make all samples on a given channel silent.
+ * @param c Channel.
+ */
+void
+AudioBuffers::make_silent (int c)
+{
+ assert (c >= 0 && c < _channels);
+
+ for (int i = 0; i < _frames; ++i) {
+ _data[c][i] = 0;
+ }
+}
+
+/** Copy data from another AudioBuffers to this one. All channels are copied.
+ * @param from AudioBuffers to copy from; must have the same number of channels as this.
+ * @param frames_to_copy Number of frames to copy.
+ * @param read_offset Offset to read from in `from'.
+ * @param write_offset Offset to write to in `to'.
+ */
void
AudioBuffers::copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset)
{
assert (from->channels() == channels());
+ assert (from);
+ assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames);
+ assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames);
+
for (int i = 0; i < _channels; ++i) {
memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float));
}
}
+/** Move audio data around.
+ * @param from Offset to move from.
+ * @param to Offset to move to.
+ * @param frames Number of frames to move.
+ */
+
void
AudioBuffers::move (int from, int to, int frames)
{
@@ -758,14 +820,37 @@ AudioBuffers::move (int from, int to, int frames)
}
}
+/** Trip an assert if the caller is not in the UI thread */
void
ensure_ui_thread ()
{
assert (this_thread::get_id() == ui_thread);
}
+/** @param v Source 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)
{
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)
+{
+#if BOOST_FILESYSTEM_VERSION == 3
+ string ext = boost::filesystem::path(f).extension().string();
+#else
+ string ext = boost::filesystem::path(f).extension();
+#endif
+
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+
+ return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png");
+}
diff --git a/src/lib/util.h b/src/lib/util.h
index 7aa9f25e1..68c7bd384 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -41,6 +41,9 @@ extern "C" {
#define TIMING(...)
#endif
+/** The maximum number of audio channels that we can cope with */
+#define MAX_AUDIO_CHANNELS 6
+
class Scaler;
extern std::string seconds_to_hms (int);
@@ -58,14 +61,21 @@ typedef int SourceFrame;
struct DCPFrameRate
{
+ /** frames per second for the DCP */
int frames_per_second;
+ /** Skip every `skip' frames. e.g. if this is 1, we skip nothing;
+ * if it's 2, we skip every other frame.
+ */
int skip;
+ /** true if this DCP will run its video faster than the source
+ * (e.g. if the source is 29.97fps and we will run the DCP at 30fps)
+ */
bool run_fast;
};
enum ContentType {
- STILL,
- VIDEO
+ STILL, ///< content is still images
+ VIDEO ///< content is a video
};
/** @class Size
@@ -94,7 +104,9 @@ struct Size
extern bool operator== (Size const & a, Size const & b);
-/** A description of the crop of an image or 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) {}
@@ -112,7 +124,9 @@ struct Crop
extern bool operator== (Crop const & a, Crop const & b);
extern bool operator!= (Crop const & a, Crop const & b);
-/** A position */
+/** @struct Position
+ * @brief A position.
+ */
struct Position
{
Position ()
@@ -131,7 +145,9 @@ struct Position
int y;
};
-/** A rectangle */
+/** @struct Rect
+ * @brief A rectangle.
+ */
struct Rect
{
Rect ()
@@ -168,7 +184,8 @@ extern std::string crop_string (Position, Size);
extern int dcp_audio_sample_rate (int);
extern DCPFrameRate dcp_frame_rate (float);
extern std::string colour_lut_index_to_name (int index);
-extern int round_up (int, int);
+extern int stride_round_up (int, int const *, int);
+extern int stride_lookup (int c, int const * stride);
extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
@@ -216,6 +233,9 @@ private:
int _buffer_data;
};
+/** @class AudioBuffers
+ * @brief A class to hold multi-channel audio data in float format.
+ */
class AudioBuffers
{
public:
@@ -240,18 +260,24 @@ public:
void set_frames (int f);
void make_silent ();
+ void make_silent (int c);
void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset);
void move (int from, int to, int frames);
private:
+ /** Number of channels */
int _channels;
+ /** Number of frames (where a frame is one sample across all channels) */
int _frames;
+ /** Number of frames that _data can hold */
int _allocated_frames;
+ /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */
float** _data;
};
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);
#endif
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
new file mode 100644
index 000000000..23a69f958
--- /dev/null
+++ b/src/lib/video_decoder.cc
@@ -0,0 +1,98 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "video_decoder.h"
+#include "subtitle.h"
+#include "film.h"
+#include "image.h"
+#include "log.h"
+#include "options.h"
+#include "job.h"
+
+using boost::shared_ptr;
+using boost::optional;
+
+VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+ : Decoder (f, o, j)
+ , _video_frame (0)
+{
+
+}
+
+/** Called by subclasses to tell the world that some video data is ready.
+ * We find a subtitle then emit it for listeners.
+ * @param frame to decode; caller manages memory.
+ */
+void
+VideoDecoder::emit_video (shared_ptr<Image> image)
+{
+ shared_ptr<Subtitle> sub;
+ if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) {
+ sub = _timed_subtitle->subtitle ();
+ }
+
+ signal_video (image, sub);
+}
+
+void
+VideoDecoder::repeat_last_video ()
+{
+ if (!_last_image) {
+ _last_image.reset (new CompactImage (pixel_format(), native_size()));
+ _last_image->make_black ();
+ }
+
+ signal_video (_last_image, _last_subtitle);
+}
+
+void
+VideoDecoder::signal_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
+{
+ TIMING ("Decoder emits %1", _video_frame);
+ Video (image, sub);
+ ++_video_frame;
+
+ _last_image = image;
+ _last_subtitle = sub;
+}
+
+void
+VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
+{
+ _timed_subtitle = s;
+
+ if (_timed_subtitle && _opt->apply_crop) {
+ Position const p = _timed_subtitle->subtitle()->position ();
+ _timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top));
+ }
+}
+
+void
+VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
+{
+ _subtitle_stream = s;
+}
+
+void
+VideoDecoder::set_progress () const
+{
+ if (_job && _film->dcp_length()) {
+ _job->set_progress (float (_video_frame) / _film->length().get());
+ }
+}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
new file mode 100644
index 000000000..ea1899840
--- /dev/null
+++ b/src/lib/video_decoder.h
@@ -0,0 +1,82 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_VIDEO_DECODER_H
+#define DVDOMATIC_VIDEO_DECODER_H
+
+#include "video_source.h"
+#include "stream.h"
+#include "decoder.h"
+
+class VideoDecoder : public VideoSource, public virtual Decoder
+{
+public:
+ VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+
+ /** @return video frames per second, or 0 if unknown */
+ virtual float frames_per_second () const = 0;
+ /** @return native size in pixels */
+ virtual Size native_size () const = 0;
+
+ virtual int time_base_numerator () const = 0;
+ virtual int time_base_denominator () const = 0;
+ virtual int sample_aspect_ratio_numerator () const = 0;
+ virtual int sample_aspect_ratio_denominator () const = 0;
+
+ virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+
+ void set_progress () const;
+
+ SourceFrame video_frame () const {
+ return _video_frame;
+ }
+
+ boost::shared_ptr<SubtitleStream> subtitle_stream () const {
+ return _subtitle_stream;
+ }
+
+ std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
+ return _subtitle_streams;
+ }
+
+protected:
+
+ virtual PixelFormat pixel_format () const = 0;
+
+ void emit_video (boost::shared_ptr<Image>);
+ void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
+ void repeat_last_video ();
+
+ /** Subtitle stream to use when decoding */
+ boost::shared_ptr<SubtitleStream> _subtitle_stream;
+ /** Subtitle streams that this decoder's content has */
+ std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
+
+private:
+ void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
+
+ SourceFrame _video_frame;
+
+ boost::shared_ptr<TimedSubtitle> _timed_subtitle;
+
+ boost::shared_ptr<Image> _last_image;
+ boost::shared_ptr<Subtitle> _last_subtitle;
+};
+
+#endif
diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h
new file mode 100644
index 000000000..78500c643
--- /dev/null
+++ b/src/lib/video_sink.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_VIDEO_SINK_H
+#define DVDOMATIC_VIDEO_SINK_H
+
+#include <boost/shared_ptr.hpp>
+#include "util.h"
+
+class Subtitle;
+class Image;
+
+class VideoSink
+{
+public:
+ /** Call with a frame of video.
+ * @param i Video frame image.
+ * @param s A subtitle that should be on this frame, or 0.
+ */
+ virtual void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) = 0;
+};
+
+#endif
diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc
new file mode 100644
index 000000000..c6bd4a5cf
--- /dev/null
+++ b/src/lib/video_source.cc
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "video_source.h"
+#include "video_sink.h"
+
+using boost::shared_ptr;
+using boost::bind;
+
+void
+VideoSource::connect_video (shared_ptr<VideoSink> s)
+{
+ Video.connect (bind (&VideoSink::process_video, s, _1, _2));
+}
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
new file mode 100644
index 000000000..d53589e2e
--- /dev/null
+++ b/src/lib/video_source.h
@@ -0,0 +1,51 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/video_source.h
+ * @brief Parent class for classes which emit video data.
+ */
+
+#ifndef DVDOMATIC_VIDEO_SOURCE_H
+#define DVDOMATIC_VIDEO_SOURCE_H
+
+#include <boost/shared_ptr.hpp>
+#include <boost/signals2.hpp>
+#include "util.h"
+
+class VideoSink;
+class Subtitle;
+class Image;
+
+/** @class VideoSink
+ * @param A class that emits video data.
+ */
+class VideoSource
+{
+public:
+
+ /** Emitted when a video frame is ready.
+ * First parameter is the video image.
+ * Second parameter is either 0 or a subtitle that should be on this frame.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>)> Video;
+
+ void connect_video (boost::shared_ptr<VideoSink>);
+};
+
+#endif
diff --git a/src/lib/wscript b/src/lib/wscript
index 2d31e09a2..0d3c7d99d 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -8,8 +8,11 @@ def build(bld):
obj.source = """
ab_transcode_job.cc
ab_transcoder.cc
+ audio_decoder.cc
+ audio_source.cc
check_hashes_job.cc
config.cc
+ combiner.cc
cross.cc
dcp_content_type.cc
dcp_video_frame.cc
@@ -20,12 +23,14 @@ def build(bld):
encoder.cc
encoder_factory.cc
examine_content_job.cc
+ external_audio_decoder.cc
filter_graph.cc
ffmpeg_compatibility.cc
ffmpeg_decoder.cc
film.cc
filter.cc
format.cc
+ gain.cc
image.cc
imagemagick_decoder.cc
imagemagick_encoder.cc
@@ -36,19 +41,21 @@ def build(bld):
log.cc
lut.cc
make_dcp_job.cc
+ matcher.cc
scp_dcp_job.cc
scaler.cc
server.cc
sound_processor.cc
stream.cc
subtitle.cc
- tiff_decoder.cc
timer.cc
transcode_job.cc
transcoder.cc
ui_signaller.cc
util.cc
version.cc
+ video_decoder.cc
+ video_source.cc
"""
obj.target = 'dvdomatic'
diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc
index ea93436be..7700a38ef 100644
--- a/src/tools/dvdomatic.cc
+++ b/src/tools/dvdomatic.cc
@@ -259,6 +259,12 @@ public:
int const r = d->ShowModal ();
if (r == wxID_OK) {
+
+ if (boost::filesystem::exists (d->get_path())) {
+ error_dialog (this, String::compose ("The directory %1 already exists.", d->get_path()));
+ return;
+ }
+
maybe_save_then_delete_film ();
film.reset (new Film (d->get_path (), false));
#if BOOST_FILESYSTEM_VERSION == 3
diff --git a/src/tools/test.cc b/src/tools/test.cc
index f81814160..4baaeb73f 100644
--- a/src/tools/test.cc
+++ b/src/tools/test.cc
@@ -1,3 +1,22 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include "image.h"
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index 67cd17d4a..396958719 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -24,6 +24,7 @@
#include <iostream>
#include <iomanip>
#include <wx/wx.h>
+#include <wx/notebook.h>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
@@ -35,6 +36,8 @@
#include "lib/job_manager.h"
#include "lib/filter.h"
#include "lib/config.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/external_audio_decoder.h"
#include "filter_dialog.h"
#include "wx_util.h"
#include "film_editor.h"
@@ -44,6 +47,7 @@
#include "scaler.h"
using std::string;
+using std::cout;
using std::stringstream;
using std::pair;
using std::fixed;
@@ -51,6 +55,7 @@ using std::setprecision;
using std::list;
using std::vector;
using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
/** @param f Film to edit */
FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
@@ -58,225 +63,300 @@ FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
, _film (f)
, _generally_sensitive (true)
{
- _sizer = new wxFlexGridSizer (2, 4, 4);
- SetSizer (_sizer);
+ wxSizer* s = new wxBoxSizer (wxVERTICAL);
+ SetSizer (s);
+ _notebook = new wxNotebook (this, wxID_ANY);
+ s->Add (_notebook, 1);
+
+ make_film_panel ();
+ _notebook->AddPage (_film_panel, _("Film"), true);
+ make_video_panel ();
+ _notebook->AddPage (_video_panel, _("Video"), false);
+ make_audio_panel ();
+ _notebook->AddPage (_audio_panel, _("Audio"), false);
+ make_subtitle_panel ();
+ _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
+
+ set_film (_film);
+ connect_to_widgets ();
+
+ JobManager::instance()->ActiveJobsChanged.connect (
+ bind (&FilmEditor::active_jobs_changed, this, _1)
+ );
+
+ setup_visibility ();
+ setup_formats ();
+}
+
+void
+FilmEditor::make_film_panel ()
+{
+ _film_panel = new wxPanel (_notebook);
+ _film_sizer = new wxFlexGridSizer (2, 4, 4);
+ _film_panel->SetSizer (_film_sizer);
+
+ add_label_to_sizer (_film_sizer, _film_panel, "Name");
+ _name = new wxTextCtrl (_film_panel, wxID_ANY);
+ _film_sizer->Add (_name, 1, wxEXPAND);
+
+ add_label_to_sizer (_film_sizer, _film_panel, "DCP Name");
+ _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
+ _film_sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK);
+
+ _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Use DCI name"));
+ _film_sizer->Add (_use_dci_name, 1, wxEXPAND);
+ _edit_dci_button = new wxButton (_film_panel, wxID_ANY, wxT ("Details..."));
+ _film_sizer->Add (_edit_dci_button, 0);
+
+ add_label_to_sizer (_film_sizer, _film_panel, "Content");
+ _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
+ _film_sizer->Add (_content, 1, wxEXPAND);
+
+ add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
+ _dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+ _film_sizer->Add (_dcp_content_type);
+
+ video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second"));
+ _frames_per_second = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
+ _film_sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
+
+ video_control (add_label_to_sizer (_film_sizer, _film_panel, "Original Size"));
+ _original_size = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
+ _film_sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
+
+ video_control (add_label_to_sizer (_film_sizer, _film_panel, "Length"));
+ _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
+ _film_sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
- add_label_to_sizer (_sizer, this, "Name");
- _name = new wxTextCtrl (this, wxID_ANY);
- _sizer->Add (_name, 1, wxEXPAND);
- add_label_to_sizer (_sizer, this, "DCP Name");
- _dcp_name = new wxStaticText (this, wxID_ANY, wxT (""));
- _sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK);
+ {
+ video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames"));
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ add_label_to_sizer (s, _film_panel, "Start");
+ _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ s->Add (_dcp_trim_start);
+ add_label_to_sizer (s, _film_panel, "End");
+ _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ s->Add (_dcp_trim_end);
+
+ _film_sizer->Add (s);
+ }
+
+ _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, wxT ("A/B"));
+ video_control (_dcp_ab);
+ _film_sizer->Add (_dcp_ab, 1);
+ _film_sizer->AddSpacer (0);
+
+ /* STILL-only stuff */
+ {
+ still_control (add_label_to_sizer (_film_sizer, _film_panel, "Duration"));
+ wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _still_duration = new wxSpinCtrl (_film_panel);
+ still_control (_still_duration);
+ s->Add (_still_duration, 1, wxEXPAND);
+ still_control (add_label_to_sizer (s, _film_panel, "s"));
+ _film_sizer->Add (s);
+ }
- _use_dci_name = new wxCheckBox (this, wxID_ANY, wxT ("Use DCI name"));
- _sizer->Add (_use_dci_name, 1, wxEXPAND);
- _edit_dci_button = new wxButton (this, wxID_ANY, wxT ("Details..."));
- _sizer->Add (_edit_dci_button, 0);
+ vector<DCPContentType const *> const ct = DCPContentType::all ();
+ for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
+ _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
+ }
+}
- add_label_to_sizer (_sizer, this, "Content");
- _content = new wxFilePickerCtrl (this, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
- _sizer->Add (_content, 1, wxEXPAND);
+void
+FilmEditor::connect_to_widgets ()
+{
+ _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
+ _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
+ _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
+ _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
+ _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
+ _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
+ _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
+ _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
+ _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
+ _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
+ _scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
+ _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
+ _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
+ _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
+ _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
+ _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
+ _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
+ _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
+ _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
+ _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
+ _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
+ _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
+ _audio_gain_calculate_button->Connect (
+ wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
+ );
+ _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
+ _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
+ _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ _external_audio[i]->Connect (
+ wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this
+ );
+ }
+}
- add_label_to_sizer (_sizer, this, "Content Type");
- _dcp_content_type = new wxComboBox (this, wxID_ANY);
- _sizer->Add (_dcp_content_type);
+void
+FilmEditor::make_video_panel ()
+{
+ _video_panel = new wxPanel (_notebook);
+ _video_sizer = new wxFlexGridSizer (2, 4, 4);
+ _video_panel->SetSizer (_video_sizer);
- add_label_to_sizer (_sizer, this, "Format");
- _format = new wxComboBox (this, wxID_ANY);
- _sizer->Add (_format);
+ add_label_to_sizer (_video_sizer, _video_panel, "Format");
+ _format = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+ _video_sizer->Add (_format);
{
- add_label_to_sizer (_sizer, this, "Crop");
+ add_label_to_sizer (_video_sizer, _video_panel, "Crop");
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- add_label_to_sizer (s, this, "L");
- _left_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ add_label_to_sizer (s, _video_panel, "L");
+ _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_left_crop, 0);
- add_label_to_sizer (s, this, "R");
- _right_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ add_label_to_sizer (s, _video_panel, "R");
+ _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_right_crop, 0);
- add_label_to_sizer (s, this, "T");
- _top_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ add_label_to_sizer (s, _video_panel, "T");
+ _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_top_crop, 0);
- add_label_to_sizer (s, this, "B");
- _bottom_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ add_label_to_sizer (s, _video_panel, "B");
+ _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_bottom_crop, 0);
- _sizer->Add (s);
+ _video_sizer->Add (s);
}
/* VIDEO-only stuff */
{
- video_control (add_label_to_sizer (_sizer, this, "Filters"));
+ video_control (add_label_to_sizer (_video_sizer, _video_panel, "Filters"));
wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _filters = new wxStaticText (this, wxID_ANY, wxT ("None"));
+ _filters = new wxStaticText (_video_panel, wxID_ANY, wxT ("None"));
video_control (_filters);
s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
- _filters_button = new wxButton (this, wxID_ANY, wxT ("Edit..."));
+ _filters_button = new wxButton (_video_panel, wxID_ANY, wxT ("Edit..."));
video_control (_filters_button);
s->Add (_filters_button, 0);
- _sizer->Add (s, 1);
+ _video_sizer->Add (s, 1);
}
- video_control (add_label_to_sizer (_sizer, this, "Scaler"));
- _scaler = new wxComboBox (this, wxID_ANY);
- _sizer->Add (video_control (_scaler), 1);
+ video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler"));
+ _scaler = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+ _video_sizer->Add (video_control (_scaler), 1);
- {
- video_control (add_label_to_sizer (_sizer, this, "Audio Stream"));
- wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _audio_stream = new wxComboBox (this, wxID_ANY);
- s->Add (video_control (_audio_stream), 1);
- _audio = new wxStaticText (this, wxID_ANY, wxT (""));
- s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
- _sizer->Add (s, 1, wxEXPAND);
+ vector<Scaler const *> const sc = Scaler::all ();
+ for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
+ _scaler->Append (std_to_wx ((*i)->name()));
}
+ _left_crop->SetRange (0, 1024);
+ _top_crop->SetRange (0, 1024);
+ _right_crop->SetRange (0, 1024);
+ _bottom_crop->SetRange (0, 1024);
+ _still_duration->SetRange (0, 60 * 60);
+ _dcp_trim_start->SetRange (0, 100);
+ _dcp_trim_end->SetRange (0, 100);
+}
+
+void
+FilmEditor::make_audio_panel ()
+{
+ _audio_panel = new wxPanel (_notebook);
+ _audio_sizer = new wxFlexGridSizer (2, 4, 4);
+ _audio_panel->SetSizer (_audio_sizer);
+
{
- video_control (add_label_to_sizer (_sizer, this, "Audio Gain"));
+ video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Gain"));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _audio_gain = new wxSpinCtrl (this);
+ _audio_gain = new wxSpinCtrl (_audio_panel);
s->Add (video_control (_audio_gain), 1);
- video_control (add_label_to_sizer (s, this, "dB"));
- _audio_gain_calculate_button = new wxButton (this, wxID_ANY, _("Calculate..."));
+ video_control (add_label_to_sizer (s, _audio_panel, "dB"));
+ _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
video_control (_audio_gain_calculate_button);
s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
- _sizer->Add (s);
+ _audio_sizer->Add (s);
}
{
- video_control (add_label_to_sizer (_sizer, this, "Audio Delay"));
+ video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Delay"));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _audio_delay = new wxSpinCtrl (this);
+ _audio_delay = new wxSpinCtrl (_audio_panel);
s->Add (video_control (_audio_delay), 1);
- video_control (add_label_to_sizer (s, this, "ms"));
- _sizer->Add (s);
- }
-
- _with_subtitles = new wxCheckBox (this, wxID_ANY, wxT("With Subtitles"));
- video_control (_with_subtitles);
- _sizer->Add (_with_subtitles, 1);
-
- _subtitle_stream = new wxComboBox (this, wxID_ANY);
- _sizer->Add (_subtitle_stream);
-
- video_control (add_label_to_sizer (_sizer, this, "Subtitle Offset"));
- _subtitle_offset = new wxSpinCtrl (this);
- _sizer->Add (video_control (_subtitle_offset), 1);
-
- {
- video_control (add_label_to_sizer (_sizer, this, "Subtitle Scale"));
- wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _subtitle_scale = new wxSpinCtrl (this);
- s->Add (video_control (_subtitle_scale));
- video_control (add_label_to_sizer (s, this, "%"));
- _sizer->Add (s);
+ video_control (add_label_to_sizer (s, _audio_panel, "ms"));
+ _audio_sizer->Add (s);
}
-
- video_control (add_label_to_sizer (_sizer, this, "Frames Per Second"));
- _frames_per_second = new wxStaticText (this, wxID_ANY, wxT (""));
- _sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
-
- video_control (add_label_to_sizer (_sizer, this, "Original Size"));
- _original_size = new wxStaticText (this, wxID_ANY, wxT (""));
- _sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
-
- video_control (add_label_to_sizer (_sizer, this, "Length"));
- _length = new wxStaticText (this, wxID_ANY, wxT (""));
- _sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
-
{
- video_control (add_label_to_sizer (_sizer, this, "Trim frames"));
+ _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
+ _audio_sizer->Add (video_control (_use_content_audio));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- add_label_to_sizer (s, this, "Start");
- _dcp_trim_start = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (_dcp_trim_start);
- add_label_to_sizer (s, this, "End");
- _dcp_trim_end = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (_dcp_trim_end);
-
- _sizer->Add (s);
+ _audio_stream = new wxComboBox (_audio_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+ s->Add (video_control (_audio_stream), 1);
+ _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
+ s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
+ _audio_sizer->Add (s, 1, wxEXPAND);
}
- _dcp_ab = new wxCheckBox (this, wxID_ANY, wxT ("A/B"));
- video_control (_dcp_ab);
- _sizer->Add (_dcp_ab, 1);
- _sizer->AddSpacer (0);
-
- /* STILL-only stuff */
- {
- still_control (add_label_to_sizer (_sizer, this, "Duration"));
- wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _still_duration = new wxSpinCtrl (this);
- still_control (_still_duration);
- s->Add (_still_duration, 1, wxEXPAND);
- still_control (add_label_to_sizer (s, this, "s"));
- _sizer->Add (s);
+ _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
+ _audio_sizer->Add (video_control (_use_external_audio));
+ _audio_sizer->AddSpacer (0);
+
+ assert (MAX_AUDIO_CHANNELS == 6);
+
+ char const * channels[] = {
+ "Left",
+ "Right",
+ "Centre",
+ "Lfe (sub)",
+ "Left surround",
+ "Right surround"
+ };
+
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]);
+ _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav"));
+ _audio_sizer->Add (video_control (_external_audio[i]), 1, wxEXPAND);
}
- /* Set up our editing widgets */
-
- _left_crop->SetRange (0, 1024);
- _top_crop->SetRange (0, 1024);
- _right_crop->SetRange (0, 1024);
- _bottom_crop->SetRange (0, 1024);
_audio_gain->SetRange (-60, 60);
_audio_delay->SetRange (-1000, 1000);
- _still_duration->SetRange (0, 60 * 60);
- _subtitle_offset->SetRange (-1024, 1024);
- _subtitle_scale->SetRange (1, 1000);
- _dcp_trim_start->SetRange (0, 100);
- _dcp_trim_end->SetRange (0, 100);
+}
- vector<DCPContentType const *> const ct = DCPContentType::all ();
- for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
- _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
- }
+void
+FilmEditor::make_subtitle_panel ()
+{
+ _subtitle_panel = new wxPanel (_notebook);
+ _subtitle_sizer = new wxFlexGridSizer (2, 4, 4);
+ _subtitle_panel->SetSizer (_subtitle_sizer);
- vector<Scaler const *> const sc = Scaler::all ();
- for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
- _scaler->Append (std_to_wx ((*i)->name()));
- }
+ _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, wxT("With Subtitles"));
+ video_control (_with_subtitles);
+ _subtitle_sizer->Add (_with_subtitles, 1);
+
+ _subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
+ _subtitle_sizer->Add (_subtitle_stream);
- JobManager::instance()->ActiveJobsChanged.connect (
- bind (&FilmEditor::active_jobs_changed, this, _1)
- );
+ video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset"));
+ _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
+ _subtitle_sizer->Add (video_control (_subtitle_offset), 1);
- /* And set their values from the Film */
- set_film (f);
-
- /* Now connect to them, since initial values are safely set */
- _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
- _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
- _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
- _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
- _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
- _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
- _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
- _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
- _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
- _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
- _scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
- _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
- _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
- _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
- _audio_gain_calculate_button->Connect (
- wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
- );
- _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
- _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
- _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
- _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
- _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
- _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
- _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
- _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
- _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
+ {
+ video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Scale"));
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
+ s->Add (video_control (_subtitle_scale));
+ video_control (add_label_to_sizer (s, _subtitle_panel, "%"));
+ _subtitle_sizer->Add (s);
+ }
- setup_visibility ();
- setup_formats ();
+ _subtitle_offset->SetRange (-1024, 1024);
+ _subtitle_scale->SetRange (1, 1000);
}
/** Called when the left crop widget has been changed */
@@ -407,12 +487,11 @@ FilmEditor::film_changed (Film::Property p)
setup_subtitle_control_sensitivity ();
setup_streams ();
break;
- case Film::HAS_SUBTITLES:
+ case Film::SUBTITLE_STREAMS:
setup_subtitle_control_sensitivity ();
setup_streams ();
break;
- case Film::AUDIO_STREAMS:
- case Film::SUBTITLE_STREAMS:
+ case Film::CONTENT_AUDIO_STREAMS:
setup_streams ();
break;
case Film::FORMAT:
@@ -423,7 +502,11 @@ FilmEditor::film_changed (Film::Property p)
++i;
++n;
}
- checked_set (_format, n);
+ if (i == _formats.end()) {
+ checked_set (_format, -1);
+ } else {
+ checked_set (_format, n);
+ }
_dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
break;
}
@@ -442,7 +525,7 @@ FilmEditor::film_changed (Film::Property p)
string const b = p.first + " " + p.second;
_filters->SetLabel (std_to_wx (b));
}
- _sizer->Layout ();
+ _film_sizer->Layout ();
break;
}
case Film::NAME:
@@ -454,9 +537,6 @@ FilmEditor::film_changed (Film::Property p)
s << fixed << setprecision(2) << _film->frames_per_second();
_frames_per_second->SetLabel (std_to_wx (s.str ()));
break;
- case Film::AUDIO_SAMPLE_RATE:
- setup_audio_details ();
- break;
case Film::SIZE:
if (_film->size().width == 0 && _film->size().height == 0) {
_original_size->SetLabel (wxT (""));
@@ -524,15 +604,36 @@ FilmEditor::film_changed (Film::Property p)
case Film::DCI_METADATA:
_dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
break;
- case Film::AUDIO_STREAM:
- checked_set (_audio_stream, _film->audio_stream_index ());
+ case Film::CONTENT_AUDIO_STREAM:
+ if (_film->content_audio_stream()) {
+ checked_set (_audio_stream, _film->content_audio_stream()->to_string());
+ }
_dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
setup_audio_details ();
+ setup_audio_control_sensitivity ();
+ break;
+ case Film::USE_CONTENT_AUDIO:
+ checked_set (_use_content_audio, _film->use_content_audio());
+ checked_set (_use_external_audio, !_film->use_content_audio());
+ _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
+ setup_audio_details ();
+ setup_audio_control_sensitivity ();
break;
case Film::SUBTITLE_STREAM:
- checked_set (_subtitle_stream, _film->subtitle_stream_index ());
+ if (_film->subtitle_stream()) {
+ checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
+ }
+ break;
+ case Film::EXTERNAL_AUDIO:
+ {
+ vector<string> a = _film->external_audio ();
+ for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) {
+ checked_set (_external_audio[i], a[i]);
+ }
+ setup_audio_details ();
break;
}
+ }
}
/** Called when the format widget has been changed */
@@ -559,7 +660,7 @@ FilmEditor::dcp_content_type_changed (wxCommandEvent &)
}
int const n = _dcp_content_type->GetSelection ();
- if (n >= 0) {
+ if (n != wxNOT_FOUND) {
_film->set_dcp_content_type (DCPContentType::from_index (n));
}
}
@@ -583,28 +684,30 @@ FilmEditor::set_film (shared_ptr<Film> f)
}
film_changed (Film::NAME);
+ film_changed (Film::USE_DCI_NAME);
film_changed (Film::CONTENT);
film_changed (Film::DCP_CONTENT_TYPE);
film_changed (Film::FORMAT);
film_changed (Film::CROP);
film_changed (Film::FILTERS);
+ film_changed (Film::SCALER);
film_changed (Film::DCP_TRIM_START);
film_changed (Film::DCP_TRIM_END);
film_changed (Film::DCP_AB);
- film_changed (Film::SIZE);
- film_changed (Film::LENGTH);
- film_changed (Film::FRAMES_PER_SECOND);
- film_changed (Film::AUDIO_SAMPLE_RATE);
- film_changed (Film::SCALER);
+ film_changed (Film::CONTENT_AUDIO_STREAM);
+ film_changed (Film::EXTERNAL_AUDIO);
+ film_changed (Film::USE_CONTENT_AUDIO);
film_changed (Film::AUDIO_GAIN);
film_changed (Film::AUDIO_DELAY);
- film_changed (Film::STILL_DURATION);
film_changed (Film::WITH_SUBTITLES);
- film_changed (Film::HAS_SUBTITLES);
film_changed (Film::SUBTITLE_OFFSET);
film_changed (Film::SUBTITLE_SCALE);
- film_changed (Film::USE_DCI_NAME);
film_changed (Film::DCI_METADATA);
+ film_changed (Film::SIZE);
+ film_changed (Film::LENGTH);
+ film_changed (Film::CONTENT_AUDIO_STREAMS);
+ film_changed (Film::SUBTITLE_STREAMS);
+ film_changed (Film::FRAMES_PER_SECOND);
}
/** Updates the sensitivity of lots of widgets to a given value.
@@ -637,6 +740,7 @@ FilmEditor::set_things_sensitive (bool s)
_still_duration->Enable (s);
setup_subtitle_control_sensitivity ();
+ setup_audio_control_sensitivity ();
}
/** Called when the `Edit filters' button has been clicked */
@@ -714,7 +818,7 @@ FilmEditor::setup_visibility ()
(*i)->Show (c == STILL);
}
- _sizer->Layout ();
+ _film_sizer->Layout ();
}
void
@@ -799,7 +903,7 @@ FilmEditor::setup_formats ()
_format->Append (std_to_wx ((*i)->name ()));
}
- _sizer->Layout ();
+ _film_sizer->Layout ();
}
void
@@ -817,7 +921,7 @@ FilmEditor::setup_subtitle_control_sensitivity ()
{
bool h = false;
if (_generally_sensitive && _film) {
- h = _film->has_subtitles();
+ h = !_film->subtitle_streams().empty();
}
_with_subtitles->Enable (h);
@@ -827,6 +931,21 @@ FilmEditor::setup_subtitle_control_sensitivity ()
}
void
+FilmEditor::setup_audio_control_sensitivity ()
+{
+ _use_content_audio->Enable (_generally_sensitive);
+ _use_external_audio->Enable (_generally_sensitive);
+
+ bool const source = _generally_sensitive && _use_content_audio->GetValue();
+ bool const external = _generally_sensitive && _use_external_audio->GetValue();
+
+ _audio_stream->Enable (source);
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ _external_audio[i]->Enable (external);
+ }
+}
+
+void
FilmEditor::use_dci_name_toggled (wxCommandEvent &)
{
if (!_film) {
@@ -852,18 +971,26 @@ void
FilmEditor::setup_streams ()
{
_audio_stream->Clear ();
- vector<AudioStream> a = _film->audio_streams ();
- for (vector<AudioStream>::iterator i = a.begin(); i != a.end(); ++i) {
- _audio_stream->Append (std_to_wx (i->name()));
+ vector<shared_ptr<AudioStream> > a = _film->content_audio_streams ();
+ for (vector<shared_ptr<AudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
+ shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (*i);
+ _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ())));
+ }
+
+ if (_film->use_content_audio() && _film->audio_stream()) {
+ checked_set (_audio_stream, _film->audio_stream()->to_string());
}
- _audio_stream->SetSelection (_film->audio_stream_index ());
_subtitle_stream->Clear ();
- vector<SubtitleStream> s = _film->subtitle_streams ();
- for (vector<SubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) {
- _subtitle_stream->Append (std_to_wx (i->name()));
+ vector<shared_ptr<SubtitleStream> > s = _film->subtitle_streams ();
+ for (vector<shared_ptr<SubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
+ _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ())));
+ }
+ if (_film->subtitle_stream()) {
+ checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
+ } else {
+ _subtitle_stream->SetValue (wxT (""));
}
- _subtitle_stream->SetSelection (_film->subtitle_stream_index ());
}
void
@@ -873,7 +1000,12 @@ FilmEditor::audio_stream_changed (wxCommandEvent &)
return;
}
- _film->set_audio_stream (_audio_stream->GetSelection ());
+ _film->set_content_audio_stream (
+ audio_stream_factory (
+ string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
+ Film::state_version
+ )
+ );
}
void
@@ -883,17 +1015,22 @@ FilmEditor::subtitle_stream_changed (wxCommandEvent &)
return;
}
- _film->set_subtitle_stream (_subtitle_stream->GetSelection ());
+ _film->set_subtitle_stream (
+ subtitle_stream_factory (
+ string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
+ Film::state_version
+ )
+ );
}
void
FilmEditor::setup_audio_details ()
{
- if (_film->audio_channels() == 0 && _film->audio_sample_rate() == 0) {
+ if (!_film->audio_stream()) {
_audio->SetLabel (wxT (""));
} else {
stringstream s;
- s << _film->audio_channels () << " channels, " << _film->audio_sample_rate() << "Hz";
+ s << _film->audio_stream()->channels () << " channels, " << _film->audio_stream()->sample_rate() << "Hz";
_audio->SetLabel (std_to_wx (s.str ()));
}
}
@@ -903,3 +1040,20 @@ FilmEditor::active_jobs_changed (bool a)
{
set_things_sensitive (!a);
}
+
+void
+FilmEditor::use_audio_changed (wxCommandEvent &)
+{
+ _film->set_use_content_audio (_use_content_audio->GetValue());
+}
+
+void
+FilmEditor::external_audio_changed (wxCommandEvent &)
+{
+ vector<string> a;
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ a.push_back (wx_to_std (_external_audio[i]->GetPath()));
+ }
+
+ _film->set_external_audio (a);
+}
diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h
index c5fe4b6cc..428b994b8 100644
--- a/src/wx/film_editor.h
+++ b/src/wx/film_editor.h
@@ -28,6 +28,8 @@
#include <boost/signals2.hpp>
#include "lib/film.h"
+class wxNotebook;
+
class Film;
/** @class FilmEditor
@@ -44,6 +46,12 @@ public:
boost::signals2::signal<void (std::string)> FileChanged;
private:
+ void make_film_panel ();
+ void make_video_panel ();
+ void make_audio_panel ();
+ void make_subtitle_panel ();
+ void connect_to_widgets ();
+
/* Handle changes to the view */
void name_changed (wxCommandEvent &);
void use_dci_name_toggled (wxCommandEvent &);
@@ -68,17 +76,19 @@ private:
void still_duration_changed (wxCommandEvent &);
void audio_stream_changed (wxCommandEvent &);
void subtitle_stream_changed (wxCommandEvent &);
+ void use_audio_changed (wxCommandEvent &);
+ void external_audio_changed (wxCommandEvent &);
/* Handle changes to the model */
void film_changed (Film::Property);
/* Button clicks */
void edit_filters_clicked (wxCommandEvent &);
- void change_dcp_range_clicked (wxCommandEvent &);
void set_things_sensitive (bool);
void setup_formats ();
void setup_subtitle_control_sensitivity ();
+ void setup_audio_control_sensitivity ();
void setup_streams ();
void setup_audio_details ();
@@ -87,6 +97,16 @@ private:
void active_jobs_changed (bool);
+ wxNotebook* _notebook;
+ wxPanel* _film_panel;
+ wxSizer* _film_sizer;
+ wxPanel* _video_panel;
+ wxSizer* _video_sizer;
+ wxPanel* _audio_panel;
+ wxSizer* _audio_sizer;
+ wxPanel* _subtitle_panel;
+ wxSizer* _subtitle_sizer;
+
/** The film we are editing */
boost::shared_ptr<Film> _film;
/** The Film's name */
@@ -112,7 +132,10 @@ private:
wxButton* _filters_button;
/** The Film's scaler */
wxComboBox* _scaler;
+ wxRadioButton* _use_content_audio;
wxComboBox* _audio_stream;
+ wxRadioButton* _use_external_audio;
+ wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
/** The Film's audio gain */
wxSpinCtrl* _audio_gain;
/** A button to open the gain calculation dialogue */
@@ -146,7 +169,5 @@ private:
std::vector<Format const *> _formats;
- wxSizer* _sizer;
-
bool _generally_sensitive;
};
diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc
index a677fd9ac..186e9c86b 100644
--- a/src/wx/wx_util.cc
+++ b/src/wx/wx_util.cc
@@ -109,10 +109,22 @@ ThreadedStaticText::thread_finished (wxCommandEvent& ev)
SetLabel (ev.GetString ());
}
+string
+string_client_data (wxClientData* o)
+{
+ return wx_to_std (dynamic_cast<wxStringClientData*>(o)->GetData());
+}
+
void
checked_set (wxFilePickerCtrl* widget, string value)
{
if (widget->GetPath() != std_to_wx (value)) {
+ if (value.empty()) {
+ /* Hack to make wxWidgets clear the control when we are passed
+ an empty value.
+ */
+ value = " ";
+ }
widget->SetPath (std_to_wx (value));
}
}
@@ -129,7 +141,28 @@ void
checked_set (wxComboBox* widget, int value)
{
if (widget->GetSelection() != value) {
- widget->SetSelection (value);
+ if (value == wxNOT_FOUND) {
+ /* Work around an apparent wxWidgets bug; SetSelection (wxNOT_FOUND)
+ appears not to work sometimes.
+ */
+ widget->SetValue (wxT (""));
+ } else {
+ widget->SetSelection (value);
+ }
+ }
+}
+
+void
+checked_set (wxComboBox* widget, string value)
+{
+ wxClientData* o = widget->GetClientObject (widget->GetSelection ());
+
+ if (!o || string_client_data(o) != value) {
+ for (unsigned int i = 0; i < widget->GetCount(); ++i) {
+ if (string_client_data (widget->GetClientObject (i)) == value) {
+ widget->SetSelection (i);
+ }
+ }
}
}
@@ -148,3 +181,11 @@ checked_set (wxCheckBox* widget, bool value)
widget->SetValue (value);
}
}
+
+void
+checked_set (wxRadioButton* widget, bool value)
+{
+ if (widget->GetValue() != value) {
+ widget->SetValue (value);
+ }
+}
diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h
index c2c3b6dde..6cb7fd002 100644
--- a/src/wx/wx_util.h
+++ b/src/wx/wx_util.h
@@ -54,8 +54,12 @@ private:
static const int _update_event_id;
};
+extern std::string string_client_data (wxClientData* o);
+
extern void checked_set (wxFilePickerCtrl* widget, std::string value);
extern void checked_set (wxSpinCtrl* widget, int value);
extern void checked_set (wxComboBox* widget, int value);
+extern void checked_set (wxComboBox* widget, std::string value);
extern void checked_set (wxTextCtrl* widget, std::string value);
extern void checked_set (wxCheckBox* widget, bool value);
+extern void checked_set (wxRadioButton* widget, bool value);