diff options
73 files changed, 2882 insertions, 1540 deletions
@@ -1,3 +1,16 @@ +2012-11-15 Carl Hetherington <cth@carlh.net> + + * Default to using a DCI name. + + * Support for using external sound files instead + of the ones in the video source. + +2012-11-14 Carl Hetherington <cth@carlh.net> + + * Rearrange the GUI a bit to tidy things up. + + * Some internal reorganisation. + 2012-12-03 Carl Hetherington <cth@carlh.net> * Version 0.58 released. diff --git a/doc/mainpage.txt b/doc/mainpage.txt index e89ca8d26..59c578899 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -5,9 +5,8 @@ * and distributed under the GPL. * * Video files are decoded using FFmpeg (http://ffmpeg.org), so any video - * supported by FFmpeg should be usable with DVD-o-matic. Most testing has, however, - * happened with files from DVD. DVD-o-matic's output has been tested on Christie - * digital projectors with Doremi servers. + * supported by FFmpeg should be usable with DVD-o-matic. DVD-o-matic's output has been + * tested on numerous digital projectors. * * DVD-o-matic allows you to crop black borders from movies, scale them to the correct * aspect ratio and apply FFmpeg filters. The time-consuming encoding of JPEG2000 files @@ -16,13 +15,13 @@ * * DVD-o-matic can also make DCPs from still images, for advertisements and such-like. * - * Portions of DVD-o-matic are based on OpenDCP (http://code.google.com/p/opendcp), + * Parts of DVD-o-matic are based on OpenDCP (http://code.google.com/p/opendcp), * written by Terrence Meiczinger. * * DVD-o-matic uses libopenjpeg (http://code.google.com/p/openjpeg/) for JPEG2000 encoding * and libsndfile (http://www.mega-nerd.com/libsndfile/) for WAV file manipulation. It - * also makes heavy use of the boost libraries (http://www.boost.org/). libtiff - * (http://www.libtiff.org/) is used for TIFF encoding and decoding, and the GUI is + * also makes heavy use of the boost libraries (http://www.boost.org/). ImageMagick + * (http://www.imagemagick.org/) is used for still-image encoding and decoding, and the GUI is * built using wxWidgets (http://wxwidgets.org/). It also uses libmhash (http://mhash.sourceforge.net/) * for debugging purposes. * @@ -34,5 +33,5 @@ * * Email correspondance is welcome to cth@carlh.net * - * More details can be found at http://carlh.net/dvdomatic + * More details can be found at http://carlh.net/software/dvdomatic */ @@ -52,6 +52,8 @@ def append_to_changelog(version): f.write('%d-%02d-%02d Carl Hetherington <cth@carlh.net>\n\n\t* Version %s released.\n\n' % (now.year, now.month, now.day, version)) f.write(c) +command("git checkout master") + release_version_string = rewrite_wscript(release_version) append_to_changelog(release_version_string) 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); diff --git a/test.log b/test.log deleted file mode 100644 index 31b9699bc..000000000 --- a/test.log +++ /dev/null @@ -1,5 +0,0 @@ -0:0 Encoder thread 0 sleeps. -0:75 Encoder thread 0 wakes -0:100 Encoder thread 0 sleeps. -0:150 Encoder thread 0 wakes - diff --git a/test/metadata.ref b/test/metadata.ref index 8276d59e2..3f129c6e2 100644 --- a/test/metadata.ref +++ b/test/metadata.ref @@ -1,5 +1,6 @@ +version 1 name fred -use_dci_name 0 +use_dci_name 1 content dcp_content_type Short format 185 @@ -13,11 +14,10 @@ scaler bicubic dcp_trim_start 42 dcp_trim_end 99 dcp_ab 1 -selected_audio_stream -1 +use_content_audio 1 audio_gain 0 audio_delay 0 still_duration 10 -selected_subtitle_stream -1 with_subtitles 0 subtitle_offset 0 subtitle_scale 1 @@ -31,7 +31,6 @@ package_type width 0 height 0 length 0 -audio_sample_rate 0 content_digest -has_subtitles 0 +external_audio_stream external 0 0 frames_per_second 0 diff --git a/test/test.cc b/test/test.cc index 5addc2d75..e2f9f41ee 100644 --- a/test/test.cc +++ b/test/test.cc @@ -37,6 +37,8 @@ #include "job.h" #include "subtitle.h" #include "scaler.h" +#include "ffmpeg_decoder.h" +#include "external_audio_decoder.h" #define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE dvdomatic_test #include <boost/test/unit_test.hpp> @@ -47,6 +49,7 @@ using std::stringstream; using std::vector; using boost::shared_ptr; using boost::thread; +using boost::dynamic_pointer_cast; void setup_test_config () @@ -128,6 +131,39 @@ BOOST_AUTO_TEST_CASE (film_metadata_test) BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); } +BOOST_AUTO_TEST_CASE (stream_test) +{ + FFmpegAudioStream a ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (a.id(), 4); + BOOST_CHECK_EQUAL (a.sample_rate(), 44100); + BOOST_CHECK_EQUAL (a.channel_layout(), 1); + BOOST_CHECK_EQUAL (a.name(), "hello there world"); + BOOST_CHECK_EQUAL (a.to_string(), "ffmpeg 4 44100 1 hello there world"); + + ExternalAudioStream e ("external 44100 1", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (e.sample_rate(), 44100); + BOOST_CHECK_EQUAL (e.channel_layout(), 1); + BOOST_CHECK_EQUAL (e.to_string(), "external 44100 1"); + + SubtitleStream s ("5 a b c", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (s.id(), 5); + BOOST_CHECK_EQUAL (s.name(), "a b c"); + + shared_ptr<AudioStream> ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1)); + shared_ptr<FFmpegAudioStream> cff = dynamic_pointer_cast<FFmpegAudioStream> (ff); + BOOST_CHECK (cff); + BOOST_CHECK_EQUAL (cff->id(), 4); + BOOST_CHECK_EQUAL (cff->sample_rate(), 44100); + BOOST_CHECK_EQUAL (cff->channel_layout(), 1); + BOOST_CHECK_EQUAL (cff->name(), "hello there world"); + BOOST_CHECK_EQUAL (cff->to_string(), "ffmpeg 4 44100 1 hello there world"); + + shared_ptr<AudioStream> fe = audio_stream_factory ("external 44100 1", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (fe->sample_rate(), 44100); + BOOST_CHECK_EQUAL (fe->channel_layout(), 1); + BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1"); +} + BOOST_AUTO_TEST_CASE (format_test) { Format::setup_formats (); @@ -158,10 +194,18 @@ BOOST_AUTO_TEST_CASE (util_test) BOOST_CHECK_EQUAL (*i++, "them"); } +class NullLog : public Log +{ +public: + void do_log (string) {} +}; + void do_positive_delay_line_test (int delay_length, int data_length) { - DelayLine d (6, delay_length); + NullLog log; + + DelayLine d (&log, 6, delay_length); shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length)); int in = 0; @@ -177,7 +221,8 @@ do_positive_delay_line_test (int delay_length, int data_length) } } - d.feed (data); + /* This only works because the delay line modifies the parameter */ + d.process_audio (data); returned += data->frames (); for (int j = 0; j < data->frames(); ++j) { @@ -201,7 +246,9 @@ do_positive_delay_line_test (int delay_length, int data_length) void do_negative_delay_line_test (int delay_length, int data_length) { - DelayLine d (6, delay_length); + NullLog log; + + DelayLine d (&log, 6, delay_length); shared_ptr<AudioBuffers> data (new AudioBuffers (6, data_length)); int in = 0; @@ -217,7 +264,8 @@ do_negative_delay_line_test (int delay_length, int data_length) } } - d.feed (data); + /* This only works because the delay line modifies the parameter */ + d.process_audio (data); returned += data->frames (); for (int j = 0; j < data->frames(); ++j) { @@ -392,21 +440,21 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test"); f->set_frames_per_second (24); - f->set_audio_sample_rate (48000); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); - f->set_audio_sample_rate (44100); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); - f->set_audio_sample_rate (80000); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000); f->set_frames_per_second (23.976); - f->set_audio_sample_rate (48000); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952); f->set_frames_per_second (29.97); - f->set_audio_sample_rate (48000); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952); } @@ -487,15 +535,3 @@ BOOST_AUTO_TEST_CASE (job_manager_test) BOOST_CHECK_EQUAL (b->running(), false); } -BOOST_AUTO_TEST_CASE (stream_test) -{ - AudioStream a ("4 9 hello there world"); - BOOST_CHECK_EQUAL (a.id(), 4); - BOOST_CHECK_EQUAL (a.channels(), 9); - BOOST_CHECK_EQUAL (a.name(), "hello there world"); - BOOST_CHECK_EQUAL (a.to_string(), "4 9 hello there world"); - - SubtitleStream s ("5 a b c"); - BOOST_CHECK_EQUAL (s.id(), 5); - BOOST_CHECK_EQUAL (s.name(), "a b c"); -} @@ -57,7 +57,6 @@ def configure(conf): conf.check_cfg(package = 'libdcp', atleast_version = '0.32', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True) conf.check_cfg(package = 'glib-2.0', args = '--cflags --libs', uselib_store = 'GLIB', mandatory = True) conf.check_cfg(package = '', path = 'Magick++-config', args = '--cppflags --cxxflags --libs', uselib_store = 'MAGICK', mandatory = True) - conf.check_cc(msg = 'Checking for library libtiff', function_name = 'TIFFOpen', header_name = 'tiffio.h', lib = 'tiff', uselib_store = 'TIFF') conf.check_cc(fragment = """ #include <stdio.h>\n #include <openjpeg.h>\n |
