summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2013-09-17 23:39:05 +0100
committerCarl Hetherington <cth@carlh.net>2013-09-17 23:39:05 +0100
commit373f010a7f04add1f49169cbaa60cb7ae5f508d4 (patch)
treea61fe014cbefc775dcf3a5c9a45d06e391e65b31 /src/lib
parent048f9b6b5569f03d1342a04f75c83a2bad340996 (diff)
parente888e92f354b9868337b0b022ff9be38b9c36c0f (diff)
Merge 1.0 in.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ab_transcode_job.cc66
-rw-r--r--src/lib/ab_transcode_job.h57
-rw-r--r--src/lib/ab_transcoder.cc119
-rw-r--r--src/lib/ab_transcoder.h73
-rw-r--r--src/lib/analyse_audio_job.cc108
-rw-r--r--src/lib/analyse_audio_job.h (renamed from src/lib/make_dcp_job.h)29
-rw-r--r--src/lib/audio_analysis.cc146
-rw-r--r--src/lib/audio_analysis.h (renamed from src/lib/external_audio_decoder.h)58
-rw-r--r--src/lib/audio_buffers.cc275
-rw-r--r--src/lib/audio_buffers.h81
-rw-r--r--src/lib/audio_content.cc120
-rw-r--r--src/lib/audio_content.h83
-rw-r--r--src/lib/audio_decoder.cc21
-rw-r--r--src/lib/audio_decoder.h34
-rw-r--r--src/lib/audio_mapping.cc117
-rw-r--r--src/lib/audio_mapping.h70
-rw-r--r--src/lib/audio_merger.h109
-rw-r--r--src/lib/check_hashes_job.cc123
-rw-r--r--src/lib/colour_conversion.cc198
-rw-r--r--src/lib/colour_conversion.h74
-rw-r--r--src/lib/combiner.cc67
-rw-r--r--src/lib/config.cc255
-rw-r--r--src/lib/config.h118
-rw-r--r--src/lib/content.cc176
-rw-r--r--src/lib/content.h133
-rw-r--r--src/lib/content_factory.cc47
-rw-r--r--src/lib/content_factory.h (renamed from src/lib/gain.h)15
-rw-r--r--src/lib/cross.cc208
-rw-r--r--src/lib/cross.h11
-rw-r--r--src/lib/dci_metadata.cc73
-rw-r--r--src/lib/dci_metadata.h (renamed from src/lib/video_sink.h)38
-rw-r--r--src/lib/dcp_content_type.cc34
-rw-r--r--src/lib/dcp_content_type.h7
-rw-r--r--src/lib/dcp_video_frame.cc477
-rw-r--r--src/lib/dcp_video_frame.h83
-rw-r--r--src/lib/decoder.cc46
-rw-r--r--src/lib/decoder.h56
-rw-r--r--src/lib/decoder_factory.cc56
-rw-r--r--src/lib/decoder_factory.h49
-rw-r--r--src/lib/delay_line.cc102
-rw-r--r--src/lib/dolby_cp750.cc4
-rw-r--r--src/lib/encoder.cc412
-rw-r--r--src/lib/encoder.h103
-rw-r--r--src/lib/examine_content_job.cc72
-rw-r--r--src/lib/examine_content_job.h16
-rw-r--r--src/lib/exceptions.cc63
-rw-r--r--src/lib/exceptions.h85
-rw-r--r--src/lib/external_audio_decoder.cc186
-rw-r--r--src/lib/ffmpeg.cc148
-rw-r--r--src/lib/ffmpeg.h75
-rw-r--r--src/lib/ffmpeg_compatibility.cc117
-rw-r--r--src/lib/ffmpeg_content.cc407
-rw-r--r--src/lib/ffmpeg_content.h169
-rw-r--r--src/lib/ffmpeg_decoder.cc849
-rw-r--r--src/lib/ffmpeg_decoder.h118
-rw-r--r--src/lib/ffmpeg_examiner.cc166
-rw-r--r--src/lib/ffmpeg_examiner.h55
-rw-r--r--src/lib/film.cc1417
-rw-r--r--src/lib/film.h486
-rw-r--r--src/lib/filter.cc78
-rw-r--r--src/lib/filter.h15
-rw-r--r--src/lib/filter_graph.cc154
-rw-r--r--src/lib/filter_graph.h19
-rw-r--r--src/lib/format.cc186
-rw-r--r--src/lib/format.h134
-rw-r--r--src/lib/i18n.h (renamed from src/lib/audio_sink.h)15
-rw-r--r--src/lib/image.cc505
-rw-r--r--src/lib/image.h122
-rw-r--r--src/lib/imagemagick_decoder.cc149
-rw-r--r--src/lib/imagemagick_decoder.h89
-rw-r--r--src/lib/job.cc173
-rw-r--r--src/lib/job.h48
-rw-r--r--src/lib/job_manager.cc36
-rw-r--r--src/lib/job_manager.h7
-rw-r--r--src/lib/log.cc14
-rw-r--r--src/lib/log.h6
-rw-r--r--src/lib/lut.h51
-rw-r--r--src/lib/matcher.cc113
-rw-r--r--src/lib/matcher.h40
-rw-r--r--src/lib/moving_image.h (renamed from src/lib/video_source.cc)23
-rw-r--r--src/lib/moving_image_content.cc118
-rw-r--r--src/lib/moving_image_content.h57
-rw-r--r--src/lib/moving_image_decoder.cc85
-rw-r--r--src/lib/moving_image_decoder.h40
-rw-r--r--src/lib/moving_image_examiner.cc110
-rw-r--r--src/lib/moving_image_examiner.h (renamed from src/lib/combiner.h)37
-rw-r--r--src/lib/options.h128
-rw-r--r--src/lib/player.cc654
-rw-r--r--src/lib/player.h146
-rw-r--r--src/lib/playlist.cc330
-rw-r--r--src/lib/playlist.h95
-rw-r--r--src/lib/po/es_ES.po658
-rw-r--r--src/lib/po/fr_FR.po664
-rw-r--r--src/lib/po/it_IT.po659
-rw-r--r--src/lib/po/sv_SE.po656
-rw-r--r--src/lib/position.h (renamed from src/lib/ffmpeg_compatibility.h)37
-rw-r--r--src/lib/processor.h98
-rw-r--r--src/lib/ratio.cc71
-rw-r--r--src/lib/ratio.h72
-rw-r--r--src/lib/rect.h79
-rw-r--r--src/lib/resampler.cc113
-rw-r--r--src/lib/resampler.h (renamed from src/lib/delay_line.h)32
-rw-r--r--src/lib/scaler.cc20
-rw-r--r--src/lib/scaler.h7
-rw-r--r--src/lib/scp_dcp_job.cc47
-rw-r--r--src/lib/scp_dcp_job.h4
-rw-r--r--src/lib/server.cc105
-rw-r--r--src/lib/server.h30
-rw-r--r--src/lib/sndfile_content.cc170
-rw-r--r--src/lib/sndfile_content.h83
-rw-r--r--src/lib/sndfile_decoder.cc120
-rw-r--r--src/lib/sndfile_decoder.h46
-rw-r--r--src/lib/sound_processor.h7
-rw-r--r--src/lib/stack.cpp461
-rw-r--r--src/lib/stack.hpp58
-rw-r--r--src/lib/still_image.h40
-rw-r--r--src/lib/still_image_content.cc113
-rw-r--r--src/lib/still_image_content.h52
-rw-r--r--src/lib/still_image_decoder.cc89
-rw-r--r--src/lib/still_image_decoder.h44
-rw-r--r--src/lib/still_image_examiner.cc63
-rw-r--r--src/lib/still_image_examiner.h (renamed from src/lib/check_hashes_job.h)32
-rw-r--r--src/lib/stream.cc90
-rw-r--r--src/lib/stream.h121
-rw-r--r--src/lib/subtitle.cc147
-rw-r--r--src/lib/subtitle.h81
-rw-r--r--src/lib/subtitle_content.cc72
-rw-r--r--src/lib/subtitle_content.h64
-rw-r--r--src/lib/subtitle_decoder.cc (renamed from src/lib/gain.cc)28
-rw-r--r--src/lib/subtitle_decoder.h (renamed from src/lib/audio_source.h)30
-rw-r--r--src/lib/timer.cc10
-rw-r--r--src/lib/timer.h6
-rw-r--r--src/lib/transcode_job.cc55
-rw-r--r--src/lib/transcode_job.h13
-rw-r--r--src/lib/transcoder.cc118
-rw-r--r--src/lib/transcoder.h51
-rw-r--r--src/lib/types.cc67
-rw-r--r--src/lib/types.h103
-rw-r--r--src/lib/ui_signaller.h11
-rw-r--r--src/lib/util.cc724
-rw-r--r--src/lib/util.h250
-rw-r--r--src/lib/version.h5
-rw-r--r--src/lib/video_content.cc278
-rw-r--r--src/lib/video_content.h121
-rw-r--r--src/lib/video_decoder.cc85
-rw-r--r--src/lib/video_decoder.h86
-rw-r--r--src/lib/video_examiner.h (renamed from src/lib/audio_source.cc)21
-rw-r--r--src/lib/video_source.h52
-rw-r--r--src/lib/writer.cc525
-rw-r--r--src/lib/writer.h133
-rw-r--r--src/lib/wscript141
151 files changed, 13753 insertions, 7202 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
deleted file mode 100644
index a6233c185..000000000
--- a/src/lib/ab_transcode_job.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <stdexcept>
-#include "ab_transcode_job.h"
-#include "film.h"
-#include "format.h"
-#include "filter.h"
-#include "ab_transcoder.h"
-#include "config.h"
-#include "encoder.h"
-
-using std::string;
-using boost::shared_ptr;
-
-/** @param f Film to compare.
- * @param o Options.
- */
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
- : Job (f, req)
- , _decode_opt (od)
- , _encode_opt (oe)
-{
- _film_b.reset (new Film (*_film));
- _film_b->set_scaler (Config::instance()->reference_scaler ());
- _film_b->set_filters (Config::instance()->reference_filters ());
-}
-
-string
-ABTranscodeJob::name () const
-{
- return String::compose ("A/B transcode %1", _film->name());
-}
-
-void
-ABTranscodeJob::run ()
-{
- try {
- /* _film_b is the one with reference filters */
- ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film, _encode_opt)));
- w.go ();
- set_progress (1);
- set_state (FINISHED_OK);
-
- } catch (std::exception& e) {
-
- set_state (FINISHED_ERROR);
-
- }
-}
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
deleted file mode 100644
index 86a2a81b8..000000000
--- a/src/lib/ab_transcode_job.h
+++ /dev/null
@@ -1,57 +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/ab_transcode_job.h
- * @brief Job to run a transcoder which produces output for A/B comparison of various settings.
- */
-
-#include <boost/shared_ptr.hpp>
-#include "job.h"
-
-class Film;
-class DecodeOptions;
-class EncodeOptions;
-
-/** @class ABTranscodeJob
- * @brief Job to run a transcoder which produces output for A/B comparison of various settings.
- *
- * The right half of the frame will be processed using the Film supplied;
- * the left half will be processed using the same state but with the reference
- * filters and scaler.
- */
-class ABTranscodeJob : public Job
-{
-public:
- ABTranscodeJob (
- boost::shared_ptr<Film> f,
- boost::shared_ptr<const DecodeOptions> od,
- boost::shared_ptr<const EncodeOptions> oe,
- boost::shared_ptr<Job> req
- );
-
- std::string name () const;
- void run ();
-
-private:
- boost::shared_ptr<const DecodeOptions> _decode_opt;
- boost::shared_ptr<const EncodeOptions> _encode_opt;
-
- /** Copy of our Film using the reference filters and scaler */
- boost::shared_ptr<Film> _film_b;
-};
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc
deleted file mode 100644
index 53af43b5d..000000000
--- a/src/lib/ab_transcoder.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <boost/shared_ptr.hpp>
-#include "ab_transcoder.h"
-#include "film.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "encoder.h"
-#include "job.h"
-#include "options.h"
-#include "image.h"
-#include "decoder_factory.h"
-#include "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
- * for the right half (to facilitate A/B comparisons of settings)
- */
-
-using std::string;
-using boost::shared_ptr;
-
-/** @param a Film to use for the left half of the screen.
- * @param b Film to use for the right half of the screen.
- * @param o Decoder options.
- * @param j Job that we are associated with.
- * @param e Encoder to use.
- */
-
-ABTranscoder::ABTranscoder (
- shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
- : _film_a (a)
- , _film_b (b)
- , _job (j)
- , _encoder (e)
-{
- _da = decoder_factory (_film_a, o, j);
- _db = decoder_factory (_film_b, o, j);
-
- 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()));
- }
-
- /* Set up the decoder to use the film's set streams */
- _da.video->set_subtitle_stream (_film_a->subtitle_stream ());
- _db.video->set_subtitle_stream (_film_a->subtitle_stream ());
- _da.audio->set_audio_stream (_film_a->audio_stream ());
-
- _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
- _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
-
- if (_matcher) {
- _combiner->connect_video (_matcher);
- _matcher->connect_video (_encoder);
- } else {
- _combiner->connect_video (_encoder);
- }
-
- if (_matcher && _delay_line) {
- _da.audio->connect_audio (_delay_line);
- _delay_line->connect_audio (_matcher);
- _matcher->connect_audio (_gain);
- _gain->connect_audio (_encoder);
- }
-}
-
-void
-ABTranscoder::go ()
-{
- _encoder->process_begin ();
-
- while (1) {
- bool const va = _da.video->pass ();
- bool const vb = _db.video->pass ();
- bool const a = _da.audio->pass ();
-
- _da.video->set_progress ();
-
- 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 ();
-}
-
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
deleted file mode 100644
index 7bfcb393c..000000000
--- a/src/lib/ab_transcoder.h
+++ /dev/null
@@ -1,73 +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/ab_transcoder.h
- * @brief A transcoder which uses one Film for the left half of the screen, and a different one
- * for the right half (to facilitate A/B comparisons of settings)
- */
-
-#include <boost/shared_ptr.hpp>
-#include <stdint.h>
-#include "util.h"
-#include "decoder_factory.h"
-
-class Job;
-class Encoder;
-class VideoDecoder;
-class AudioDecoder;
-class DecodeOptions;
-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
- * for the right half (to facilitate A/B comparisons of settings)
- */
-class ABTranscoder
-{
-public:
- ABTranscoder (
- boost::shared_ptr<Film> a,
- boost::shared_ptr<Film> b,
- boost::shared_ptr<const DecodeOptions> o,
- Job* j,
- boost::shared_ptr<Encoder> e
- );
-
- void go ();
-
-private:
- boost::shared_ptr<Film> _film_a;
- boost::shared_ptr<Film> _film_b;
- Job* _job;
- boost::shared_ptr<Encoder> _encoder;
- Decoders _da;
- Decoders _db;
- boost::shared_ptr<Combiner> _combiner;
- boost::shared_ptr<Matcher> _matcher;
- boost::shared_ptr<DelayLine> _delay_line;
- boost::shared_ptr<Gain> _gain;
- boost::shared_ptr<Image> _image;
-};
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
new file mode 100644
index 000000000..8186f9de4
--- /dev/null
+++ b/src/lib/analyse_audio_job.cc
@@ -0,0 +1,108 @@
+/*
+ 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_analysis.h"
+#include "analyse_audio_job.h"
+#include "compose.hpp"
+#include "film.h"
+#include "player.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::max;
+using std::min;
+using std::cout;
+using boost::shared_ptr;
+
+int const AnalyseAudioJob::_num_points = 1024;
+
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioContent> c)
+ : Job (f)
+ , _content (c)
+ , _done (0)
+ , _samples_per_point (1)
+{
+
+}
+
+string
+AnalyseAudioJob::name () const
+{
+ return _("Analyse audio");
+}
+
+void
+AnalyseAudioJob::run ()
+{
+ shared_ptr<AudioContent> content = _content.lock ();
+ if (!content) {
+ return;
+ }
+
+ shared_ptr<Playlist> playlist (new Playlist);
+ playlist->add (content);
+ shared_ptr<Player> player (new Player (_film, playlist));
+ player->disable_video ();
+
+ player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
+
+ _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
+
+ _current.resize (_film->audio_channels ());
+ _analysis.reset (new AudioAnalysis (_film->audio_channels ()));
+
+ _done = 0;
+ OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
+ while (!player->pass ()) {
+ set_progress (double (_done) / len);
+ }
+
+ _analysis->write (content->audio_analysis_path ());
+
+ set_progress (1);
+ set_state (FINISHED_OK);
+}
+
+void
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+{
+ for (int i = 0; i < b->frames(); ++i) {
+ for (int j = 0; j < b->channels(); ++j) {
+ float s = b->data(j)[i];
+ if (fabsf (s) < 10e-7) {
+ /* stringstream can't serialise and recover inf or -inf, so prevent such
+ values by replacing with this (140dB down) */
+ s = 10e-7;
+ }
+ _current[j][AudioPoint::RMS] += pow (s, 2);
+ _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s));
+
+ if ((_done % _samples_per_point) == 0) {
+ _current[j][AudioPoint::RMS] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point);
+ _analysis->add_point (j, _current[j]);
+
+ _current[j] = AudioPoint ();
+ }
+ }
+
+ ++_done;
+ }
+}
+
diff --git a/src/lib/make_dcp_job.h b/src/lib/analyse_audio_job.h
index 5e4f78a25..3d4881983 100644
--- a/src/lib/make_dcp_job.h
+++ b/src/lib/analyse_audio_job.h
@@ -17,30 +17,31 @@
*/
-/** @file src/make_dcp_job.h
- * @brief A job to create DCPs.
- */
-
#include "job.h"
+#include "audio_analysis.h"
+#include "types.h"
-class EncodeOptions;
+class AudioBuffers;
+class AudioContent;
-/** @class MakeDCPJob
- * @brief A job to create DCPs
- */
-class MakeDCPJob : public Job
+class AnalyseAudioJob : public Job
{
public:
- MakeDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<const EncodeOptions>, boost::shared_ptr<Job> req);
+ AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
std::string name () const;
void run ();
private:
- void dcp_progress (float);
- std::string j2c_path (int, int) const;
- std::string wav_path (libdcp::Channel) const;
+ void audio (boost::shared_ptr<const AudioBuffers>, Time);
+
+ boost::weak_ptr<AudioContent> _content;
+ OutputAudioFrame _done;
+ int64_t _samples_per_point;
+ std::vector<AudioPoint> _current;
+
+ boost::shared_ptr<AudioAnalysis> _analysis;
- boost::shared_ptr<const EncodeOptions> _opt;
+ static const int _num_points;
};
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
new file mode 100644
index 000000000..bc59bccca
--- /dev/null
+++ b/src/lib/audio_analysis.cc
@@ -0,0 +1,146 @@
+/*
+ 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 <cmath>
+#include <cassert>
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include "audio_analysis.h"
+
+using std::ostream;
+using std::istream;
+using std::string;
+using std::ofstream;
+using std::ifstream;
+using std::vector;
+using std::cout;
+using std::max;
+using std::list;
+
+AudioPoint::AudioPoint ()
+{
+ for (int i = 0; i < COUNT; ++i) {
+ _data[i] = 0;
+ }
+}
+
+AudioPoint::AudioPoint (istream& s)
+{
+ for (int i = 0; i < COUNT; ++i) {
+ s >> _data[i];
+ }
+}
+
+AudioPoint::AudioPoint (AudioPoint const & other)
+{
+ for (int i = 0; i < COUNT; ++i) {
+ _data[i] = other._data[i];
+ }
+}
+
+AudioPoint &
+AudioPoint::operator= (AudioPoint const & other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ for (int i = 0; i < COUNT; ++i) {
+ _data[i] = other._data[i];
+ }
+
+ return *this;
+}
+
+void
+AudioPoint::write (ostream& s) const
+{
+ for (int i = 0; i < COUNT; ++i) {
+ s << _data[i] << "\n";
+ }
+}
+
+
+AudioAnalysis::AudioAnalysis (int channels)
+{
+ _data.resize (channels);
+}
+
+AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
+{
+ ifstream f (filename.string().c_str ());
+
+ int channels;
+ f >> channels;
+ _data.resize (channels);
+
+ for (int i = 0; i < channels; ++i) {
+ int points;
+ f >> points;
+ for (int j = 0; j < points; ++j) {
+ _data[i].push_back (AudioPoint (f));
+ }
+ }
+}
+
+void
+AudioAnalysis::add_point (int c, AudioPoint const & p)
+{
+ assert (c < channels ());
+ _data[c].push_back (p);
+}
+
+AudioPoint
+AudioAnalysis::get_point (int c, int p) const
+{
+ assert (p < points (c));
+ return _data[c][p];
+}
+
+int
+AudioAnalysis::channels () const
+{
+ return _data.size ();
+}
+
+int
+AudioAnalysis::points (int c) const
+{
+ assert (c < channels ());
+ return _data[c].size ();
+}
+
+void
+AudioAnalysis::write (boost::filesystem::path filename)
+{
+ string tmp = filename.string() + ".tmp";
+
+ ofstream f (tmp.c_str ());
+ f << _data.size() << "\n";
+ for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) {
+ f << i->size () << "\n";
+ for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) {
+ j->write (f);
+ }
+ }
+
+ f.close ();
+ boost::filesystem::rename (tmp, filename);
+}
diff --git a/src/lib/external_audio_decoder.h b/src/lib/audio_analysis.h
index 2558955eb..cfc170c84 100644
--- a/src/lib/external_audio_decoder.h
+++ b/src/lib/audio_analysis.h
@@ -17,38 +17,54 @@
*/
-#include <sndfile.h>
-#include "decoder.h"
-#include "audio_decoder.h"
-#include "stream.h"
+#ifndef DCPOMATIC_AUDIO_ANALYSIS_H
+#define DCPOMATIC_AUDIO_ANALYSIS_H
-class ExternalAudioStream : public AudioStream
+#include <iostream>
+#include <vector>
+#include <list>
+#include <boost/filesystem.hpp>
+
+class AudioPoint
{
public:
- ExternalAudioStream (int sample_rate, int64_t layout)
- : AudioStream (sample_rate, layout)
- {}
-
- std::string to_string () const;
+ enum Type {
+ PEAK,
+ RMS,
+ COUNT
+ };
- static boost::shared_ptr<ExternalAudioStream> create ();
- static boost::shared_ptr<ExternalAudioStream> create (std::string t, boost::optional<int> v);
+ AudioPoint ();
+ AudioPoint (std::istream &);
+ AudioPoint (AudioPoint const &);
+ AudioPoint& operator= (AudioPoint const &);
-private:
- friend class stream_test;
+ void write (std::ostream &) const;
- ExternalAudioStream ();
- ExternalAudioStream (std::string t, boost::optional<int> v);
+ float& operator[] (int t) {
+ return _data[t];
+ }
+
+private:
+ float _data[COUNT];
};
-class ExternalAudioDecoder : public AudioDecoder
+class AudioAnalysis : public boost::noncopyable
{
public:
- ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+ AudioAnalysis (int c);
+ AudioAnalysis (boost::filesystem::path);
- bool pass ();
+ void add_point (int c, AudioPoint const & p);
+
+ AudioPoint get_point (int c, int p) const;
+ int points (int c) const;
+ int channels () const;
+
+ void write (boost::filesystem::path);
private:
- std::vector<SNDFILE*> open_files (sf_count_t &);
- void close_files (std::vector<SNDFILE*> const &);
+ std::vector<std::vector<AudioPoint> > _data;
};
+
+#endif
diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc
new file mode 100644
index 000000000..e80142b8e
--- /dev/null
+++ b/src/lib/audio_buffers.cc
@@ -0,0 +1,275 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cassert>
+#include <cstring>
+#include <cmath>
+#include <stdexcept>
+#include "audio_buffers.h"
+
+using std::bad_alloc;
+using boost::shared_ptr;
+
+/** 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)
+{
+ allocate (channels, frames);
+}
+
+/** Copy constructor.
+ * @param other Other AudioBuffers; data is copied.
+ */
+AudioBuffers::AudioBuffers (AudioBuffers const & other)
+{
+ allocate (other._channels, other._frames);
+ copy_from (&other, other._frames, 0, 0);
+}
+
+AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> other)
+{
+ allocate (other->_channels, other->_frames);
+ copy_from (other.get(), other->_frames, 0, 0);
+}
+
+AudioBuffers &
+AudioBuffers::operator= (AudioBuffers const & other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ deallocate ();
+ allocate (other._channels, other._frames);
+ copy_from (&other, other._frames, 0, 0);
+
+ return *this;
+}
+
+/** AudioBuffers destructor */
+AudioBuffers::~AudioBuffers ()
+{
+ deallocate ();
+}
+
+void
+AudioBuffers::allocate (int channels, int frames)
+{
+ _channels = channels;
+ _frames = frames;
+ _allocated_frames = frames;
+
+ _data = static_cast<float**> (malloc (_channels * sizeof (float *)));
+ if (!_data) {
+ throw bad_alloc ();
+ }
+
+ for (int i = 0; i < _channels; ++i) {
+ _data[i] = static_cast<float*> (malloc (frames * sizeof (float)));
+ if (!_data[i]) {
+ throw bad_alloc ();
+ }
+ }
+}
+
+void
+AudioBuffers::deallocate ()
+{
+ for (int i = 0; i < _channels; ++i) {
+ free (_data[i]);
+ }
+
+ free (_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. If we reduce the number of frames, the `lost' frames will
+ * be silenced.
+ * @param f Frames; must be less than or equal to the number of allocated frames.
+ */
+void
+AudioBuffers::set_frames (int f)
+{
+ assert (f <= _allocated_frames);
+
+ for (int c = 0; c < _channels; ++c) {
+ for (int i = f; i < _frames; ++i) {
+ _data[c][i] = 0;
+ }
+ }
+
+ _frames = f;
+}
+
+/** Make all samples on all channels silent */
+void
+AudioBuffers::make_silent ()
+{
+ for (int i = 0; i < _channels; ++i) {
+ 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;
+ }
+}
+
+void
+AudioBuffers::make_silent (int from, int frames)
+{
+ assert ((from + frames) <= _allocated_frames);
+
+ for (int c = 0; c < _channels; ++c) {
+ for (int i = from; i < (from + 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 const * 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)
+{
+ if (frames == 0) {
+ return;
+ }
+
+ assert (from >= 0);
+ assert (from < _frames);
+ assert (to >= 0);
+ assert (to < _frames);
+ assert (frames > 0);
+ assert (frames <= _frames);
+ assert ((from + frames) <= _frames);
+ assert ((to + frames) <= _allocated_frames);
+
+ for (int i = 0; i < _channels; ++i) {
+ memmove (_data[i] + to, _data[i] + from, frames * sizeof(float));
+ }
+}
+
+/** Add data from from `from', `from_channel' to our channel `to_channel' */
+void
+AudioBuffers::accumulate_channel (AudioBuffers const * from, int from_channel, int to_channel)
+{
+ int const N = frames ();
+ assert (from->frames() == N);
+ assert (to_channel <= _channels);
+
+ float* s = from->data (from_channel);
+ float* d = _data[to_channel];
+
+ for (int i = 0; i < N; ++i) {
+ *d++ += *s++;
+ }
+}
+
+/** Ensure we have space for at least a certain number of frames. If we extend
+ * the buffers, fill the new space with silence.
+ */
+void
+AudioBuffers::ensure_size (int frames)
+{
+ if (_allocated_frames >= frames) {
+ return;
+ }
+
+ for (int i = 0; i < _channels; ++i) {
+ _data[i] = static_cast<float*> (realloc (_data[i], frames * sizeof (float)));
+ if (!_data[i]) {
+ throw bad_alloc ();
+ }
+ for (int j = _allocated_frames; j < frames; ++j) {
+ _data[i][j] = 0;
+ }
+ }
+
+ _allocated_frames = frames;
+}
+
+void
+AudioBuffers::accumulate_frames (AudioBuffers const * from, int read_offset, int write_offset, int frames)
+{
+ assert (_channels == from->channels ());
+
+ for (int i = 0; i < _channels; ++i) {
+ for (int j = 0; j < frames; ++j) {
+ _data[i][j + write_offset] += from->data()[i][j + read_offset];
+ }
+ }
+}
+
+/** @param dB gain in dB */
+void
+AudioBuffers::apply_gain (float dB)
+{
+ float const linear = pow (10, dB / 20);
+
+ for (int i = 0; i < _channels; ++i) {
+ for (int j = 0; j < _frames; ++j) {
+ _data[i][j] *= linear;
+ }
+ }
+}
diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h
new file mode 100644
index 000000000..75bc686f8
--- /dev/null
+++ b/src/lib/audio_buffers.h
@@ -0,0 +1,81 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_BUFFERS_H
+#define DVDOMATIC_AUDIO_BUFFERS_H
+
+#include <boost/shared_ptr.hpp>
+
+/** @class AudioBuffers
+ * @brief A class to hold multi-channel audio data in float format.
+ */
+class AudioBuffers
+{
+public:
+ AudioBuffers (int channels, int frames);
+ AudioBuffers (AudioBuffers const &);
+ AudioBuffers (boost::shared_ptr<const AudioBuffers>);
+ ~AudioBuffers ();
+
+ AudioBuffers & operator= (AudioBuffers const &);
+
+ void ensure_size (int);
+
+ float** data () const {
+ return _data;
+ }
+
+ float* data (int) const;
+
+ int channels () const {
+ return _channels;
+ }
+
+ int frames () const {
+ return _frames;
+ }
+
+ void set_frames (int f);
+
+ void make_silent ();
+ void make_silent (int c);
+ void make_silent (int from, int frames);
+
+ void apply_gain (float);
+
+ void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset);
+ void move (int from, int to, int frames);
+ void accumulate_channel (AudioBuffers const *, int, int);
+ void accumulate_frames (AudioBuffers const *, int read_offset, int write_offset, int frames);
+
+private:
+ void allocate (int, int);
+ void deallocate ();
+
+ /** 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;
+};
+
+#endif
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
new file mode 100644
index 000000000..100264d44
--- /dev/null
+++ b/src/lib/audio_content.cc
@@ -0,0 +1,120 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "audio_content.h"
+#include "analyse_audio_job.h"
+#include "job_manager.h"
+#include "film.h"
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+int const AudioContentProperty::AUDIO_CHANNELS = 200;
+int const AudioContentProperty::AUDIO_LENGTH = 201;
+int const AudioContentProperty::AUDIO_FRAME_RATE = 202;
+int const AudioContentProperty::AUDIO_GAIN = 203;
+int const AudioContentProperty::AUDIO_DELAY = 204;
+int const AudioContentProperty::AUDIO_MAPPING = 205;
+
+AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+ : Content (f, s)
+ , _audio_gain (0)
+ , _audio_delay (0)
+{
+
+}
+
+AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , _audio_gain (0)
+ , _audio_delay (0)
+{
+
+}
+
+AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+{
+ _audio_gain = node->number_child<float> ("AudioGain");
+ _audio_delay = node->number_child<int> ("AudioDelay");
+}
+
+void
+AudioContent::as_xml (xmlpp::Node* node) const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ node->add_child("AudioGain")->add_child_text (lexical_cast<string> (_audio_gain));
+ node->add_child("AudioDelay")->add_child_text (lexical_cast<string> (_audio_delay));
+}
+
+
+void
+AudioContent::set_audio_gain (float g)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_gain = g;
+ }
+
+ signal_changed (AudioContentProperty::AUDIO_GAIN);
+}
+
+void
+AudioContent::set_audio_delay (int d)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_delay = d;
+ }
+
+ signal_changed (AudioContentProperty::AUDIO_DELAY);
+}
+
+void
+AudioContent::analyse_audio (boost::function<void()> finished)
+{
+ shared_ptr<const Film> film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, dynamic_pointer_cast<AudioContent> (shared_from_this())));
+ job->Finished.connect (finished);
+ JobManager::instance()->add (job);
+}
+
+boost::filesystem::path
+AudioContent::audio_analysis_path () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ if (!film) {
+ return boost::filesystem::path ();
+ }
+
+ return film->audio_analysis_path (dynamic_pointer_cast<const AudioContent> (shared_from_this ()));
+}
+
+string
+AudioContent::technical_summary () const
+{
+ return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
new file mode 100644
index 000000000..73919105d
--- /dev/null
+++ b/src/lib/audio_content.h
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_AUDIO_CONTENT_H
+#define DCPOMATIC_AUDIO_CONTENT_H
+
+#include "content.h"
+#include "audio_mapping.h"
+
+namespace cxml {
+ class Node;
+}
+
+class AudioContentProperty
+{
+public:
+ static int const AUDIO_CHANNELS;
+ static int const AUDIO_LENGTH;
+ static int const AUDIO_FRAME_RATE;
+ static int const AUDIO_GAIN;
+ static int const AUDIO_DELAY;
+ static int const AUDIO_MAPPING;
+};
+
+class AudioContent : public virtual Content
+{
+public:
+ typedef int64_t Frame;
+
+ AudioContent (boost::shared_ptr<const Film>, Time);
+ AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+ std::string technical_summary () const;
+
+ virtual int audio_channels () const = 0;
+ virtual AudioContent::Frame audio_length () const = 0;
+ virtual int content_audio_frame_rate () const = 0;
+ virtual int output_audio_frame_rate () const = 0;
+ virtual AudioMapping audio_mapping () const = 0;
+ virtual void set_audio_mapping (AudioMapping) = 0;
+
+ void analyse_audio (boost::function<void()>);
+ boost::filesystem::path audio_analysis_path () const;
+
+ void set_audio_gain (float);
+ void set_audio_delay (int);
+
+ float audio_gain () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_gain;
+ }
+
+ int audio_delay () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_delay;
+ }
+
+private:
+ /** Gain to apply to audio in dB */
+ float _audio_gain;
+ /** Delay to apply to audio (positive moves audio later) in milliseconds */
+ int _audio_delay;
+};
+
+#endif
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index 9d8de971c..1f5868583 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -18,19 +18,30 @@
*/
#include "audio_decoder.h"
-#include "stream.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+#include "log.h"
+#include "resampler.h"
+#include "i18n.h"
+
+using std::stringstream;
+using std::list;
+using std::pair;
+using std::cout;
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
- : Decoder (f, o, j)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> film)
+ : Decoder (film)
+ , _audio_position (0)
{
}
void
-AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
{
- _audio_stream = s;
+ Audio (data, frame);
+ _audio_position = frame + data->frames ();
}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index 013a6327f..2ad53da8b 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -21,38 +21,30 @@
* @brief Parent class for audio decoders.
*/
-#ifndef DVDOMATIC_AUDIO_DECODER_H
-#define DVDOMATIC_AUDIO_DECODER_H
+#ifndef DCPOMATIC_AUDIO_DECODER_H
+#define DCPOMATIC_AUDIO_DECODER_H
-#include "audio_source.h"
-#include "stream.h"
#include "decoder.h"
+#include "content.h"
+#include "audio_content.h"
+
+class AudioBuffers;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
*/
-class AudioDecoder : public AudioSource, public virtual Decoder
+class AudioDecoder : public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
-
- virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
+ AudioDecoder (boost::shared_ptr<const Film>);
- /** @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;
- }
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
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;
+
+ void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ AudioContent::Frame _audio_position;
};
#endif
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
new file mode 100644
index 000000000..7a5da7d2a
--- /dev/null
+++ b/src/lib/audio_mapping.cc
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/lexical_cast.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "audio_mapping.h"
+
+using std::list;
+using std::cout;
+using std::make_pair;
+using std::pair;
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+AudioMapping::AudioMapping ()
+ : _content_channels (0)
+{
+
+}
+
+/** Create a default AudioMapping for a given channel count.
+ * @param c Number of channels.
+ */
+AudioMapping::AudioMapping (int c)
+ : _content_channels (c)
+{
+
+}
+
+void
+AudioMapping::make_default ()
+{
+ if (_content_channels == 1) {
+ /* Mono -> Centre */
+ add (0, libdcp::CENTRE);
+ } else {
+ /* 1:1 mapping */
+ for (int i = 0; i < _content_channels; ++i) {
+ add (i, static_cast<libdcp::Channel> (i));
+ }
+ }
+}
+
+AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node)
+{
+ _content_channels = node->number_child<int> ("ContentChannels");
+
+ list<shared_ptr<cxml::Node> > const c = node->node_children ("Map");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ add ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")));
+ }
+}
+
+void
+AudioMapping::add (int c, libdcp::Channel d)
+{
+ _content_to_dcp.push_back (make_pair (c, d));
+}
+
+list<int>
+AudioMapping::dcp_to_content (libdcp::Channel d) const
+{
+ list<int> c;
+ for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (i->second == d) {
+ c.push_back (i->first);
+ }
+ }
+
+ return c;
+}
+
+list<libdcp::Channel>
+AudioMapping::content_to_dcp (int c) const
+{
+ assert (c < _content_channels);
+
+ list<libdcp::Channel> d;
+ for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (i->first == c) {
+ d.push_back (i->second);
+ }
+ }
+
+ return d;
+}
+
+void
+AudioMapping::as_xml (xmlpp::Node* node) const
+{
+ node->add_child ("ContentChannels")->add_child_text (lexical_cast<string> (_content_channels));
+
+ for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ xmlpp::Node* t = node->add_child ("Map");
+ t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first));
+ t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second));
+ }
+}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
new file mode 100644
index 000000000..9a507b550
--- /dev/null
+++ b/src/lib/audio_mapping.h
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_AUDIO_MAPPING_H
+#define DCPOMATIC_AUDIO_MAPPING_H
+
+#include <list>
+#include <libdcp/types.h>
+#include <boost/shared_ptr.hpp>
+
+namespace xmlpp {
+ class Node;
+}
+
+namespace cxml {
+ class Node;
+}
+
+/** A many-to-many mapping from some content channels to DCP channels.
+ * The number of content channels is set on construction and fixed,
+ * and then each of those content channels can be mapped to zero or
+ * more DCP channels.
+ */
+class AudioMapping
+{
+public:
+ AudioMapping ();
+ AudioMapping (int);
+ AudioMapping (boost::shared_ptr<const cxml::Node>);
+
+ /* Default copy constructor is fine */
+
+ void as_xml (xmlpp::Node *) const;
+
+ void add (int, libdcp::Channel);
+ void make_default ();
+
+ std::list<int> dcp_to_content (libdcp::Channel) const;
+ std::list<std::pair<int, libdcp::Channel> > content_to_dcp () const {
+ return _content_to_dcp;
+ }
+
+ int content_channels () const {
+ return _content_channels;
+ }
+
+ std::list<libdcp::Channel> content_to_dcp (int) const;
+
+private:
+ int _content_channels;
+ std::list<std::pair<int, libdcp::Channel> > _content_to_dcp;
+};
+
+#endif
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
new file mode 100644
index 000000000..226601e0e
--- /dev/null
+++ b/src/lib/audio_merger.h
@@ -0,0 +1,109 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_buffers.h"
+#include "util.h"
+
+template <class T, class F>
+class AudioMerger
+{
+public:
+ AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t)
+ : _buffers (new AudioBuffers (channels, 0))
+ , _last_pull (0)
+ , _t_to_f (t_to_f)
+ , _f_to_t (f_to_t)
+ {}
+
+ /** Pull audio up to a given time; after this call, no more data can be pushed
+ * before the specified time.
+ */
+ TimedAudioBuffers<T>
+ pull (T time)
+ {
+ TimedAudioBuffers<T> out;
+
+ F const to_return = _t_to_f (time - _last_pull);
+ out.audio.reset (new AudioBuffers (_buffers->channels(), to_return));
+ /* And this is how many we will get from our buffer */
+ F const to_return_from_buffers = min (to_return, _buffers->frames ());
+
+ /* Copy the data that we have to the back end of the return buffer */
+ out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers);
+ /* Silence any gap at the start */
+ out.audio->make_silent (0, to_return - to_return_from_buffers);
+
+ out.time = _last_pull;
+ _last_pull = time;
+
+ /* And remove the data we're returning from our buffers */
+ if (_buffers->frames() > to_return_from_buffers) {
+ _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers);
+ }
+ _buffers->set_frames (_buffers->frames() - to_return_from_buffers);
+
+ return out;
+ }
+
+ void
+ push (boost::shared_ptr<const AudioBuffers> audio, T time)
+ {
+ assert (time >= _last_pull);
+
+ F frame = _t_to_f (time);
+ F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull));
+ _buffers->ensure_size (after);
+ _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ());
+ _buffers->set_frames (after);
+ }
+
+ F min (F a, int b)
+ {
+ if (a < b) {
+ return a;
+ }
+
+ return b;
+ }
+
+ F max (int a, F b)
+ {
+ if (a > b) {
+ return a;
+ }
+
+ return b;
+ }
+
+ TimedAudioBuffers<T>
+ flush ()
+ {
+ if (_buffers->frames() == 0) {
+ return TimedAudioBuffers<T> ();
+ }
+
+ return TimedAudioBuffers<T> (_buffers, _last_pull);
+ }
+
+private:
+ boost::shared_ptr<AudioBuffers> _buffers;
+ T _last_pull;
+ boost::function<F (T)> _t_to_f;
+ boost::function<T (F)> _f_to_t;
+};
diff --git a/src/lib/check_hashes_job.cc b/src/lib/check_hashes_job.cc
deleted file mode 100644
index 701584c74..000000000
--- a/src/lib/check_hashes_job.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <fstream>
-#include <boost/lexical_cast.hpp>
-#include <boost/filesystem.hpp>
-#include "check_hashes_job.h"
-#include "options.h"
-#include "log.h"
-#include "job_manager.h"
-#include "ab_transcode_job.h"
-#include "transcode_job.h"
-#include "film.h"
-#include "exceptions.h"
-
-using std::string;
-using std::stringstream;
-using std::ifstream;
-using boost::shared_ptr;
-
-CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
- : Job (f, req)
- , _decode_opt (od)
- , _encode_opt (oe)
- , _bad (0)
-{
-
-}
-
-string
-CheckHashesJob::name () const
-{
- return String::compose ("Check hashes of %1", _film->name());
-}
-
-void
-CheckHashesJob::run ()
-{
- _bad = 0;
-
- if (!_film->dcp_length()) {
- throw EncodeError ("cannot check hashes of a DCP with unknown length");
- }
-
- SourceFrame const N = _film->dcp_trim_start() + _film->dcp_length().get();
- DCPFrameRate const dfr = dcp_frame_rate (_film->frames_per_second ());
-
- for (SourceFrame i = _film->dcp_trim_start(); i < N; i += dfr.skip) {
- string const j2k_file = _encode_opt->frame_out_path (i, false);
- string const hash_file = _encode_opt->hash_out_path (i, false);
-
- if (!boost::filesystem::exists (j2k_file)) {
- _film->log()->log (String::compose ("Frame %1 has a missing J2K file.", i));
- boost::filesystem::remove (hash_file);
- ++_bad;
- } else if (!boost::filesystem::exists (hash_file)) {
- _film->log()->log (String::compose ("Frame %1 has a missing hash file.", i));
- boost::filesystem::remove (j2k_file);
- ++_bad;
- } else {
- ifstream ref (hash_file.c_str ());
- string hash;
- ref >> hash;
- if (hash != md5_digest (j2k_file)) {
- _film->log()->log (String::compose ("Frame %1 has wrong hash; deleting.", i));
- boost::filesystem::remove (j2k_file);
- boost::filesystem::remove (hash_file);
- ++_bad;
- }
- }
-
- set_progress (float (i) / N);
- }
-
- if (_bad) {
- shared_ptr<Job> tc;
-
- if (_film->dcp_ab()) {
- tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
- } else {
- tc.reset (new TranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
- }
-
- JobManager::instance()->add_after (shared_from_this(), tc);
- JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, _encode_opt, tc)));
- }
-
- set_progress (1);
- set_state (FINISHED_OK);
-}
-
-string
-CheckHashesJob::status () const
-{
- stringstream s;
- s << Job::status ();
- if (overall_progress() > 0) {
- if (_bad == 0) {
- s << "; no bad frames found";
- } else if (_bad == 1) {
- s << "; 1 bad frame found";
- } else {
- s << "; " << _bad << " bad frames found";
- }
- }
- return s.str ();
-}
diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc
new file mode 100644
index 000000000..ceb302971
--- /dev/null
+++ b/src/lib/colour_conversion.cc
@@ -0,0 +1,198 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/lexical_cast.hpp>
+#include <libxml++/libxml++.h>
+#include <libdcp/colour_matrix.h>
+#include <libcxml/cxml.h>
+#include "config.h"
+#include "colour_conversion.h"
+#include "util.h"
+
+#include "i18n.h"
+
+using std::list;
+using std::string;
+using std::cout;
+using std::vector;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
+
+ColourConversion::ColourConversion ()
+ : input_gamma (2.4)
+ , input_gamma_linearised (true)
+ , matrix (3, 3)
+ , output_gamma (2.6)
+{
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+ }
+ }
+}
+
+ColourConversion::ColourConversion (double i, bool il, double const m[3][3], double o)
+ : input_gamma (i)
+ , input_gamma_linearised (il)
+ , matrix (3, 3)
+ , output_gamma (o)
+{
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ matrix (i, j) = m[i][j];
+ }
+ }
+}
+
+ColourConversion::ColourConversion (shared_ptr<cxml::Node> node)
+ : matrix (3, 3)
+{
+ input_gamma = node->number_child<double> ("InputGamma");
+ input_gamma_linearised = node->bool_child ("InputGammaLinearised");
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ matrix (i, j) = 0;
+ }
+ }
+
+ list<shared_ptr<cxml::Node> > m = node->node_children ("Matrix");
+ for (list<shared_ptr<cxml::Node> >::iterator i = m.begin(); i != m.end(); ++i) {
+ int const ti = (*i)->number_attribute<int> ("i");
+ int const tj = (*i)->number_attribute<int> ("j");
+ matrix(ti, tj) = lexical_cast<double> ((*i)->content ());
+ }
+
+ output_gamma = node->number_child<double> ("OutputGamma");
+}
+
+void
+ColourConversion::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("InputGamma")->add_child_text (lexical_cast<string> (input_gamma));
+ node->add_child("InputGammaLinearised")->add_child_text (input_gamma_linearised ? "1" : "0");
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ xmlpp::Element* m = node->add_child("Matrix");
+ m->set_attribute ("i", lexical_cast<string> (i));
+ m->set_attribute ("j", lexical_cast<string> (j));
+ m->add_child_text (lexical_cast<string> (matrix (i, j)));
+ }
+ }
+
+ node->add_child("OutputGamma")->add_child_text (lexical_cast<string> (output_gamma));
+}
+
+optional<size_t>
+ColourConversion::preset () const
+{
+ vector<PresetColourConversion> presets = Config::instance()->colour_conversions ();
+ size_t i = 0;
+ while (i < presets.size() && (presets[i].conversion != *this)) {
+ ++i;
+ }
+
+ if (i >= presets.size ()) {
+ return optional<size_t> ();
+ }
+
+ return i;
+}
+
+string
+ColourConversion::identifier () const
+{
+ double numbers[12];
+
+ int n = 0;
+ numbers[n++] = input_gamma;
+ numbers[n++] = input_gamma_linearised;
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ numbers[n++] = matrix (i, j);
+ }
+ }
+ numbers[n++] = output_gamma;
+
+ assert (n == 12);
+
+ return md5_digest (numbers, 12 * sizeof (double));
+}
+
+PresetColourConversion::PresetColourConversion ()
+ : name (_("Untitled"))
+{
+
+}
+
+PresetColourConversion::PresetColourConversion (string n, double i, bool il, double const m[3][3], double o)
+ : name (n)
+ , conversion (i, il, m, o)
+{
+
+}
+
+PresetColourConversion::PresetColourConversion (shared_ptr<cxml::Node> node)
+ : conversion (node)
+{
+ name = node->string_child ("Name");
+}
+
+void
+PresetColourConversion::as_xml (xmlpp::Node* node) const
+{
+ conversion.as_xml (node);
+ node->add_child("Name")->add_child_text (name);
+}
+
+static bool
+about_equal (double a, double b)
+{
+ static const double eps = 1e-6;
+ return fabs (a - b) < eps;
+}
+
+bool
+operator== (ColourConversion const & a, ColourConversion const & b)
+{
+ if (
+ !about_equal (a.input_gamma, b.input_gamma) ||
+ a.input_gamma_linearised != b.input_gamma_linearised ||
+ !about_equal (a.output_gamma, b.output_gamma)) {
+ return false;
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ if (!about_equal (a.matrix (i, j), b.matrix (i, j))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+operator!= (ColourConversion const & a, ColourConversion const & b)
+{
+ return !(a == b);
+}
diff --git a/src/lib/colour_conversion.h b/src/lib/colour_conversion.h
new file mode 100644
index 000000000..893148466
--- /dev/null
+++ b/src/lib/colour_conversion.h
@@ -0,0 +1,74 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_COLOUR_CONVERSION_H
+#define DCPOMATIC_COLOUR_CONVERSION_H
+
+/* Hack for OS X compile failure; see https://bugs.launchpad.net/hugin/+bug/910160 */
+#ifdef check
+#undef check
+#endif
+
+#include <boost/utility.hpp>
+#include <boost/optional.hpp>
+#include <boost/numeric/ublas/matrix.hpp>
+
+namespace cxml {
+ class Node;
+}
+
+namespace xmlpp {
+ class Node;
+}
+
+class ColourConversion
+{
+public:
+ ColourConversion ();
+ ColourConversion (double, bool, double const matrix[3][3], double);
+ ColourConversion (boost::shared_ptr<cxml::Node>);
+
+ virtual void as_xml (xmlpp::Node *) const;
+ std::string identifier () const;
+
+ boost::optional<size_t> preset () const;
+
+ double input_gamma;
+ bool input_gamma_linearised;
+ boost::numeric::ublas::matrix<double> matrix;
+ double output_gamma;
+};
+
+class PresetColourConversion
+{
+public:
+ PresetColourConversion ();
+ PresetColourConversion (std::string, double, bool, double const matrix[3][3], double);
+ PresetColourConversion (boost::shared_ptr<cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ std::string name;
+ ColourConversion conversion;
+};
+
+bool operator== (ColourConversion const &, ColourConversion const &);
+bool operator!= (ColourConversion const &, ColourConversion const &);
+
+#endif
diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc
deleted file mode 100644
index 68aafd2a2..000000000
--- a/src/lib/combiner.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "combiner.h"
-#include "image.h"
-
-using boost::shared_ptr;
-
-Combiner::Combiner (Log* log)
- : VideoProcessor (log)
-{
-
-}
-
-/** Process video for the left half of the frame.
- * Subtitle parameter will be ignored.
- * @param image Frame image.
- */
-void
-Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>)
-{
- _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, bool, 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, false, sub);
- _image.reset ();
-}
diff --git a/src/lib/config.cc b/src/lib/config.cc
index a74c36f73..5b96d108c 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -22,36 +22,120 @@
#include <fstream>
#include <glib.h>
#include <boost/filesystem.hpp>
+#include <libdcp/colour_matrix.h>
+#include <libcxml/cxml.h>
#include "config.h"
#include "server.h"
#include "scaler.h"
#include "filter.h"
+#include "ratio.h"
+#include "dcp_content_type.h"
#include "sound_processor.h"
-#include "cinema.h"
+#include "colour_conversion.h"
+
+#include "i18n.h"
using std::vector;
using std::ifstream;
using std::string;
using std::ofstream;
using std::list;
+using std::max;
using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
Config* Config::_instance = 0;
/** Construct default configuration */
Config::Config ()
- : _num_local_encoding_threads (2)
+ : _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency()))
, _server_port (6192)
- , _reference_scaler (Scaler::from_id ("bicubic"))
- , _tms_path (".")
- , _sound_processor (SoundProcessor::from_id ("dolby_cp750"))
+ , _tms_path (N_("."))
+ , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
+ , _default_still_length (10)
+ , _default_container (Ratio::from_id ("185"))
+ , _default_dcp_content_type (DCPContentType::from_dci_name ("TST"))
+ , _default_j2k_bandwidth (200000000)
{
- ifstream f (read_file().c_str ());
- string line;
+ _allowed_dcp_frame_rates.push_back (24);
+ _allowed_dcp_frame_rates.push_back (25);
+ _allowed_dcp_frame_rates.push_back (30);
+ _allowed_dcp_frame_rates.push_back (48);
+ _allowed_dcp_frame_rates.push_back (50);
+ _allowed_dcp_frame_rates.push_back (60);
+
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
+}
+
+void
+Config::read ()
+{
+ if (!boost::filesystem::exists (file (false))) {
+ read_old_metadata ();
+ return;
+ }
- shared_ptr<Cinema> cinema;
- shared_ptr<Screen> screen;
+ cxml::Document f ("Config");
+ f.read_file (file (false));
+ optional<string> c;
+
+ _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads");
+ _default_directory = f.string_child ("DefaultDirectory");
+ _server_port = f.number_child<int> ("ServerPort");
+ list<shared_ptr<cxml::Node> > servers = f.node_children ("Server");
+ for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) {
+ _servers.push_back (ServerDescription (*i));
+ }
+
+ _tms_ip = f.string_child ("TMSIP");
+ _tms_path = f.string_child ("TMSPath");
+ _tms_user = f.string_child ("TMSUser");
+ _tms_password = f.string_child ("TMSPassword");
+
+ c = f.optional_string_child ("SoundProcessor");
+ if (c) {
+ _sound_processor = SoundProcessor::from_id (c.get ());
+ }
+
+ _language = f.optional_string_child ("Language");
+
+ c = f.optional_string_child ("DefaultContainer");
+ if (c) {
+ _default_container = Ratio::from_id (c.get ());
+ }
+
+ c = f.optional_string_child ("DefaultDCPContentType");
+ if (c) {
+ _default_dcp_content_type = DCPContentType::from_dci_name (c.get ());
+ }
+
+ _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or ("");
+ _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or ("");
+
+ _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+ _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
+ _default_j2k_bandwidth = f.optional_number_child<int>("DefaultJ2KBandwidth").get_value_or (200000000);
+
+ list<shared_ptr<cxml::Node> > cc = f.node_children ("ColourConversion");
+
+ if (!cc.empty ()) {
+ _colour_conversions.clear ();
+ }
+
+ for (list<shared_ptr<cxml::Node> >::iterator i = cc.begin(); i != cc.end(); ++i) {
+ _colour_conversions.push_back (PresetColourConversion (*i));
+ }
+}
+
+void
+Config::read_old_metadata ()
+{
+ ifstream f (file(true).c_str ());
+ string line;
+
while (getline (f, line)) {
if (line.empty ()) {
continue;
@@ -69,76 +153,58 @@ Config::Config ()
string const k = line.substr (0, s);
string const v = line.substr (s + 1);
- if (k == "num_local_encoding_threads") {
+ if (k == N_("num_local_encoding_threads")) {
_num_local_encoding_threads = atoi (v.c_str ());
- } else if (k == "default_directory") {
+ } else if (k == N_("default_directory")) {
_default_directory = v;
- } else if (k == "server_port") {
+ } else if (k == N_("server_port")) {
_server_port = atoi (v.c_str ());
- } else if (k == "reference_scaler") {
- _reference_scaler = Scaler::from_id (v);
- } else if (k == "reference_filter") {
- _reference_filters.push_back (Filter::from_id (v));
- } else if (k == "server") {
- _servers.push_back (ServerDescription::create_from_metadata (v));
- } else if (k == "tms_ip") {
+ } else if (k == N_("server")) {
+ optional<ServerDescription> server = ServerDescription::create_from_metadata (v);
+ if (server) {
+ _servers.push_back (server.get ());
+ }
+ } else if (k == N_("tms_ip")) {
_tms_ip = v;
- } else if (k == "tms_path") {
+ } else if (k == N_("tms_path")) {
_tms_path = v;
- } else if (k == "tms_user") {
+ } else if (k == N_("tms_user")) {
_tms_user = v;
- } else if (k == "tms_password") {
+ } else if (k == N_("tms_password")) {
_tms_password = v;
- } else if (k == "sound_processor") {
+ } else if (k == N_("sound_processor")) {
_sound_processor = SoundProcessor::from_id (v);
- } else if (k == "cinema") {
- if (cinema) {
- _cinemas.push_back (cinema);
- }
- cinema.reset (new Cinema (v, ""));
- } else if (k == "cinema_email") {
- assert (cinema);
- cinema->email = v;
- } else if (k == "screen") {
- assert (cinema);
- if (screen) {
- cinema->screens.push_back (screen);
- }
- screen.reset (new Screen (v, shared_ptr<libdcp::Certificate> ()));
- } else if (k == "screen_certificate") {
- assert (screen);
- shared_ptr<Certificate> c (new libdcp::Certificate);
- c->set_from_string (v);
+ } else if (k == "language") {
+ _language = v;
+ } else if (k == "default_container") {
+ _default_container = Ratio::from_id (v);
+ } else if (k == "default_dcp_content_type") {
+ _default_dcp_content_type = DCPContentType::from_dci_name (v);
+ } else if (k == "dcp_metadata_issuer") {
+ _dcp_metadata.issuer = v;
+ } else if (k == "dcp_metadata_creator") {
+ _dcp_metadata.creator = v;
+ } else if (k == "dcp_metadata_issue_date") {
+ _dcp_metadata.issue_date = v;
}
- }
- if (cinema) {
- _cinemas.push_back (cinema);
+ _default_dci_metadata.read_old_metadata (k, v);
}
}
/** @return Filename to write configuration to */
string
-Config::write_file () const
+Config::file (bool old) const
{
boost::filesystem::path p;
p /= g_get_user_config_dir ();
- p /= "dvdomatic";
- boost::filesystem::create_directory (p);
- p /= "config";
- return p.string ();
-}
-
-string
-Config::read_file () const
-{
- if (boost::filesystem::exists (write_file ())) {
- return write_file ();
+ boost::system::error_code ec;
+ boost::filesystem::create_directory (p, ec);
+ if (old) {
+ p /= ".dvdomatic";
+ } else {
+ p /= "dcpomatic.xml";
}
-
- boost::filesystem::path p;
- p /= g_get_user_config_dir ();
- p /= ".dvdomatic";
return p.string ();
}
@@ -159,6 +225,13 @@ Config::instance ()
{
if (_instance == 0) {
_instance = new Config;
+ try {
+ _instance->read ();
+ } catch (...) {
+ /* configuration load failed; never mind, just
+ stick with the default.
+ */
+ }
}
return _instance;
@@ -168,33 +241,46 @@ Config::instance ()
void
Config::write () const
{
- ofstream f (write_file().c_str ());
- f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
- << "default_directory " << _default_directory << "\n"
- << "server_port " << _server_port << "\n"
- << "reference_scaler " << _reference_scaler->id () << "\n";
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("Config");
- for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
- f << "reference_filter " << (*i)->id () << "\n";
- }
+ root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast<string> (_num_local_encoding_threads));
+ root->add_child("DefaultDirectory")->add_child_text (_default_directory);
+ root->add_child("ServerPort")->add_child_text (lexical_cast<string> (_server_port));
- for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
- f << "server " << (*i)->as_metadata () << "\n";
+ for (vector<ServerDescription>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
+ i->as_xml (root->add_child ("Server"));
}
- f << "tms_ip " << _tms_ip << "\n";
- f << "tms_path " << _tms_path << "\n";
- f << "tms_user " << _tms_user << "\n";
- f << "tms_password " << _tms_password << "\n";
- f << "sound_processor " << _sound_processor->id () << "\n";
+ root->add_child("TMSIP")->add_child_text (_tms_ip);
+ root->add_child("TMSPath")->add_child_text (_tms_path);
+ root->add_child("TMSUser")->add_child_text (_tms_user);
+ root->add_child("TMSPassword")->add_child_text (_tms_password);
+ if (_sound_processor) {
+ root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
+ }
+ if (_language) {
+ root->add_child("Language")->add_child_text (_language.get());
+ }
+ if (_default_container) {
+ root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
+ }
+ if (_default_dcp_content_type) {
+ root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
+ }
+ root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
+ root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
- for (list<shared_ptr<Cinema> >::const_iterator i = _cinemas.begin(); i != _cinemas.end(); ++i) {
- f << "cinema " << (*i)->name << "\n";
- f << "cinema_email " << (*i)->email << "\n";
- for (list<shared_ptr<Screen> >::iterator j = (*i)->screens.begin(); j != (*i)->screens.end(); ++j) {
- f << "screen " << (*j)->name << "\n";
- }
+ _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+
+ root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
+ root->add_child("DefaultJ2KBandwidth")->add_child_text (lexical_cast<string> (_default_j2k_bandwidth));
+
+ for (vector<PresetColourConversion>::const_iterator i = _colour_conversions.begin(); i != _colour_conversions.end(); ++i) {
+ i->as_xml (root->add_child ("ColourConversion"));
}
+
+ doc.write_to_file_formatted (file (false));
}
string
@@ -206,3 +292,10 @@ Config::default_directory_or (string a) const
return _default_directory;
}
+
+void
+Config::drop ()
+{
+ delete _instance;
+ _instance = 0;
+}
diff --git a/src/lib/config.h b/src/lib/config.h
index ee4e4eaec..48eabd54c 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -21,23 +21,29 @@
* @brief Class holding configuration.
*/
-#ifndef DVDOMATIC_CONFIG_H
-#define DVDOMATIC_CONFIG_H
+#ifndef DCPOMATIC_CONFIG_H
+#define DCPOMATIC_CONFIG_H
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
+#include <libdcp/metadata.h>
+#include "dci_metadata.h"
+#include "colour_conversion.h"
+#include "server.h"
class ServerDescription;
class Scaler;
class Filter;
class SoundProcessor;
+class DCPContentType;
+class Ratio;
class Cinema;
/** @class Config
* @brief A singleton class holding configuration.
*/
-class Config
+class Config : public boost::noncopyable
{
public:
@@ -58,18 +64,10 @@ public:
}
/** @return J2K encoding servers to use */
- std::vector<ServerDescription*> servers () const {
+ std::vector<ServerDescription> servers () const {
return _servers;
}
- Scaler const * reference_scaler () const {
- return _reference_scaler;
- }
-
- std::vector<Filter const *> reference_filters () const {
- return _reference_filters;
- }
-
/** @return The IP address of a TMS that we can copy DCPs to */
std::string tms_ip () const {
return _tms_ip;
@@ -98,6 +96,42 @@ public:
std::list<boost::shared_ptr<Cinema> > cinemas () const {
return _cinemas;
}
+
+ std::list<int> allowed_dcp_frame_rates () const {
+ return _allowed_dcp_frame_rates;
+ }
+
+ DCIMetadata default_dci_metadata () const {
+ return _default_dci_metadata;
+ }
+
+ boost::optional<std::string> language () const {
+ return _language;
+ }
+
+ int default_still_length () const {
+ return _default_still_length;
+ }
+
+ Ratio const * default_container () const {
+ return _default_container;
+ }
+
+ DCPContentType const * default_dcp_content_type () const {
+ return _default_dcp_content_type;
+ }
+
+ libdcp::XMLMetadata dcp_metadata () const {
+ return _dcp_metadata;
+ }
+
+ int default_j2k_bandwidth () const {
+ return _default_j2k_bandwidth;
+ }
+
+ std::vector<PresetColourConversion> colour_conversions () const {
+ return _colour_conversions;
+ }
/** @param n New number of local encoding threads */
void set_num_local_encoding_threads (int n) {
@@ -114,7 +148,7 @@ public:
}
/** @param s New list of servers */
- void set_servers (std::vector<ServerDescription*> s) {
+ void set_servers (std::vector<ServerDescription> s) {
_servers = s;
}
@@ -153,17 +187,59 @@ public:
void remove_cinema (boost::shared_ptr<Cinema> c) {
_cinemas.remove (c);
}
+
+ void set_allowed_dcp_frame_rates (std::list<int> const & r) {
+ _allowed_dcp_frame_rates = r;
+ }
+
+ void set_default_dci_metadata (DCIMetadata d) {
+ _default_dci_metadata = d;
+ }
+
+ void set_language (std::string l) {
+ _language = l;
+ }
+
+ void unset_language () {
+ _language = boost::none;
+ }
+
+ void set_default_still_length (int s) {
+ _default_still_length = s;
+ }
+
+ void set_default_container (Ratio const * c) {
+ _default_container = c;
+ }
+
+ void set_default_dcp_content_type (DCPContentType const * t) {
+ _default_dcp_content_type = t;
+ }
+
+ void set_dcp_metadata (libdcp::XMLMetadata m) {
+ _dcp_metadata = m;
+ }
+
+ void set_default_j2k_bandwidth (int b) {
+ _default_j2k_bandwidth = b;
+ }
+
+ void set_colour_conversions (std::vector<PresetColourConversion> const & c) {
+ _colour_conversions = c;
+ }
void write () const;
std::string crypt_chain_directory () const;
static Config* instance ();
+ static void drop ();
private:
Config ();
- std::string read_file () const;
- std::string write_file () const;
+ std::string file (bool) const;
+ void read ();
+ void read_old_metadata ();
/** number of threads to use for J2K encoding on the local machine */
int _num_local_encoding_threads;
@@ -173,7 +249,7 @@ private:
int _server_port;
/** J2K encoding servers to use */
- std::vector<ServerDescription *> _servers;
+ std::vector<ServerDescription> _servers;
/** Scaler to use for the "A" part of A/B comparisons */
Scaler const * _reference_scaler;
/** Filters to use for the "A" part of A/B comparisons */
@@ -188,6 +264,16 @@ private:
std::string _tms_password;
/** Our sound processor */
SoundProcessor const * _sound_processor;
+ std::list<int> _allowed_dcp_frame_rates;
+ /** Default DCI metadata for newly-created Films */
+ DCIMetadata _default_dci_metadata;
+ boost::optional<std::string> _language;
+ int _default_still_length;
+ Ratio const * _default_container;
+ DCPContentType const * _default_dcp_content_type;
+ libdcp::XMLMetadata _dcp_metadata;
+ int _default_j2k_bandwidth;
+ std::vector<PresetColourConversion> _colour_conversions;
std::list<boost::shared_ptr<Cinema> > _cinemas;
diff --git a/src/lib/content.cc b/src/lib/content.cc
new file mode 100644
index 000000000..d2a07f795
--- /dev/null
+++ b/src/lib/content.cc
@@ -0,0 +1,176 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/thread/mutex.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "content.h"
+#include "util.h"
+#include "content_factory.h"
+#include "ui_signaller.h"
+
+using std::string;
+using std::set;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const ContentProperty::POSITION = 400;
+int const ContentProperty::LENGTH = 401;
+int const ContentProperty::TRIM_START = 402;
+int const ContentProperty::TRIM_END = 403;
+
+Content::Content (shared_ptr<const Film> f, Time p)
+ : _film (f)
+ , _position (p)
+ , _trim_start (0)
+ , _trim_end (0)
+ , _change_signals_frequent (false)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
+ : _film (f)
+ , _path (p)
+ , _position (0)
+ , _trim_start (0)
+ , _trim_end (0)
+ , _change_signals_frequent (false)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : _film (f)
+ , _change_signals_frequent (false)
+{
+ _path = node->string_child ("Path");
+ _digest = node->string_child ("Digest");
+ _position = node->number_child<Time> ("Position");
+ _trim_start = node->number_child<Time> ("TrimStart");
+ _trim_end = node->number_child<Time> ("TrimEnd");
+}
+
+void
+Content::as_xml (xmlpp::Node* node) const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ node->add_child("Path")->add_child_text (_path.string());
+ node->add_child("Digest")->add_child_text (_digest);
+ node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
+ node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
+ node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end));
+}
+
+void
+Content::examine (shared_ptr<Job> job)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ boost::filesystem::path p = _path;
+ lm.unlock ();
+
+ string d;
+ if (boost::filesystem::is_regular_file (p)) {
+ d = md5_digest (p);
+ } else {
+ d = md5_digest_directory (p, job);
+ }
+
+ lm.lock ();
+ _digest = d;
+}
+
+void
+Content::signal_changed (int p)
+{
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (Changed), shared_from_this (), p, _change_signals_frequent));
+ }
+}
+
+void
+Content::set_position (Time p)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _position = p;
+ }
+
+ signal_changed (ContentProperty::POSITION);
+}
+
+void
+Content::set_trim_start (Time t)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _trim_start = t;
+ }
+
+ signal_changed (ContentProperty::TRIM_START);
+}
+
+void
+Content::set_trim_end (Time t)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _trim_end = t;
+ }
+
+ signal_changed (ContentProperty::TRIM_END);
+}
+
+
+shared_ptr<Content>
+Content::clone () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ if (!film) {
+ return shared_ptr<Content> ();
+ }
+
+ /* This is a bit naughty, but I can't think of a compelling reason not to do it ... */
+ xmlpp::Document doc;
+ xmlpp::Node* node = doc.create_root_node ("Content");
+ as_xml (node);
+ return content_factory (film, shared_ptr<cxml::Node> (new cxml::Node (node)));
+}
+
+string
+Content::technical_summary () const
+{
+ return String::compose ("%1 %2 %3", path(), digest(), position());
+}
+
+Time
+Content::length_after_trim () const
+{
+ return full_length() - trim_start() - trim_end();
+}
+
+/** @param t A time relative to the start of this content (not the position).
+ * @return true if this time is trimmed by our trim settings.
+ */
+bool
+Content::trimmed (Time t) const
+{
+ return (t < trim_start() || t > (full_length() - trim_end ()));
+}
diff --git a/src/lib/content.h b/src/lib/content.h
new file mode 100644
index 000000000..3c57dddbe
--- /dev/null
+++ b/src/lib/content.h
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_H
+#define DCPOMATIC_CONTENT_H
+
+#include <set>
+#include <boost/filesystem.hpp>
+#include <boost/signals2.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <libxml++/libxml++.h>
+#include "types.h"
+
+namespace cxml {
+ class Node;
+}
+
+class Job;
+class Film;
+
+class ContentProperty
+{
+public:
+ static int const POSITION;
+ static int const LENGTH;
+ static int const TRIM_START;
+ static int const TRIM_END;
+};
+
+class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
+{
+public:
+ Content (boost::shared_ptr<const Film>, Time);
+ Content (boost::shared_ptr<const Film>, boost::filesystem::path);
+ Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+ virtual ~Content () {}
+
+ virtual void examine (boost::shared_ptr<Job>);
+ virtual std::string summary () const = 0;
+ virtual std::string technical_summary () const;
+ virtual std::string information () const = 0;
+ virtual void as_xml (xmlpp::Node *) const;
+ virtual Time full_length () const = 0;
+
+ boost::shared_ptr<Content> clone () const;
+
+ boost::filesystem::path path () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _path;
+ }
+
+ /** @return MD5 digest of the content's file(s) */
+ std::string digest () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _digest;
+ }
+
+ void set_position (Time);
+
+ /** Time that this content starts; i.e. the time that the first
+ * bit of the content (trimmed or not) will happen.
+ */
+ Time position () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _position;
+ }
+
+ void set_trim_start (Time);
+
+ Time trim_start () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _trim_start;
+ }
+
+ void set_trim_end (Time);
+
+ Time trim_end () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _trim_end;
+ }
+
+ Time end () const {
+ return position() + length_after_trim();
+ }
+
+ Time length_after_trim () const;
+
+ void set_change_signals_frequent (bool f) {
+ _change_signals_frequent = f;
+ }
+
+ bool trimmed (Time) const;
+
+ boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
+
+protected:
+ void signal_changed (int);
+
+ boost::weak_ptr<const Film> _film;
+
+ /** _mutex which should be used to protect accesses, as examine
+ jobs can update content state in threads other than the main one.
+ */
+ mutable boost::mutex _mutex;
+
+private:
+ /** Path of a file or a directory containing files */
+ boost::filesystem::path _path;
+ std::string _digest;
+ Time _position;
+ Time _trim_start;
+ Time _trim_end;
+ bool _change_signals_frequent;
+};
+
+#endif
diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc
new file mode 100644
index 000000000..6ed01f174
--- /dev/null
+++ b/src/lib/content_factory.cc
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "ffmpeg_content.h"
+#include "still_image_content.h"
+#include "moving_image_content.h"
+#include "sndfile_content.h"
+
+using std::string;
+using boost::shared_ptr;
+
+shared_ptr<Content>
+content_factory (shared_ptr<const Film> film, shared_ptr<cxml::Node> node)
+{
+ string const type = node->string_child ("Type");
+
+ boost::shared_ptr<Content> content;
+
+ if (type == "FFmpeg") {
+ content.reset (new FFmpegContent (film, node));
+ } else if (type == "StillImage") {
+ content.reset (new StillImageContent (film, node));
+ } else if (type == "MovingImage") {
+ content.reset (new MovingImageContent (film, node));
+ } else if (type == "Sndfile") {
+ content.reset (new SndfileContent (film, node));
+ }
+
+ return content;
+}
diff --git a/src/lib/gain.h b/src/lib/content_factory.h
index 716ee9b51..27cd36024 100644
--- a/src/lib/gain.h
+++ b/src/lib/content_factory.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,15 +17,6 @@
*/
-#include "processor.h"
+class Film;
-class Gain : public AudioProcessor
-{
-public:
- Gain (Log* log, float gain);
-
- void process_audio (boost::shared_ptr<AudioBuffers>);
-
-private:
- float _gain;
-};
+extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, boost::shared_ptr<cxml::Node>);
diff --git a/src/lib/cross.cc b/src/lib/cross.cc
index 2c66ab53a..61ec8de5e 100644
--- a/src/lib/cross.cc
+++ b/src/lib/cross.cc
@@ -17,21 +17,217 @@
*/
+#include <fstream>
+#include <boost/algorithm/string.hpp>
#include "cross.h"
-#ifdef DVDOMATIC_POSIX
+#include "compose.hpp"
+#include "log.h"
+#ifdef DCPOMATIC_LINUX
#include <unistd.h>
+#include <mntent.h>
#endif
-#ifdef DVDOMATIC_WINDOWS
-#include "windows.h"
+#ifdef DCPOMATIC_WINDOWS
+#include <windows.h>
+#undef DATADIR
+#include <shlwapi.h>
#endif
+#ifdef DCPOMATIC_OSX
+#include <sys/sysctl.h>
+#include <mach-o/dyld.h>
+#endif
+
+using std::pair;
+using std::list;
+using std::ifstream;
+using std::string;
+using std::wstring;
+using std::make_pair;
+using boost::shared_ptr;
void
-dvdomatic_sleep (int s)
+dcpomatic_sleep (int s)
{
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
sleep (s);
#endif
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
Sleep (s * 1000);
#endif
}
+
+/** @return A string of CPU information (model name etc.) */
+string
+cpu_info ()
+{
+ string info;
+
+#ifdef DCPOMATIC_LINUX
+ ifstream f ("/proc/cpuinfo");
+ while (f.good ()) {
+ string l;
+ getline (f, l);
+ if (boost::algorithm::starts_with (l, "model name")) {
+ string::size_type const c = l.find (':');
+ if (c != string::npos) {
+ info = l.substr (c + 2);
+ }
+ }
+ }
+#endif
+
+#ifdef DCPOMATIC_OSX
+ char buffer[64];
+ size_t N = sizeof (buffer);
+ if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
+ info = buffer;
+ }
+#endif
+
+#ifdef DCPOMATIC_WINDOWS
+ HKEY key;
+ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
+ return info;
+ }
+
+ DWORD type;
+ DWORD data;
+ if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
+ return info;
+ }
+
+ if (type != REG_SZ) {
+ return info;
+ }
+
+ wstring value (data / sizeof (wchar_t), L'\0');
+ if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
+ RegCloseKey (key);
+ return info;
+ }
+
+ info = string (value.begin(), value.end());
+
+ RegCloseKey (key);
+
+#endif
+
+ return info;
+}
+
+void
+run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
+{
+#ifdef DCPOMATIC_WINDOWS
+ SECURITY_ATTRIBUTES security;
+ security.nLength = sizeof (security);
+ security.bInheritHandle = TRUE;
+ security.lpSecurityDescriptor = 0;
+
+ HANDLE child_stderr_read;
+ HANDLE child_stderr_write;
+ if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
+ log->log ("ffprobe call failed (could not CreatePipe)");
+ return;
+ }
+
+ wchar_t dir[512];
+ GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
+ PathRemoveFileSpec (dir);
+ SetCurrentDirectory (dir);
+
+ STARTUPINFO startup_info;
+ ZeroMemory (&startup_info, sizeof (startup_info));
+ startup_info.cb = sizeof (startup_info);
+ startup_info.hStdError = child_stderr_write;
+ startup_info.dwFlags |= STARTF_USESTDHANDLES;
+
+ wchar_t command[512];
+ wcscpy (command, L"ffprobe.exe \"");
+
+ wchar_t file[512];
+ MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
+ wcscat (command, file);
+
+ wcscat (command, L"\"");
+
+ PROCESS_INFORMATION process_info;
+ ZeroMemory (&process_info, sizeof (process_info));
+ if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
+ log->log ("ffprobe call failed (could not CreateProcess)");
+ return;
+ }
+
+ FILE* o = fopen (out.string().c_str(), "w");
+ if (!o) {
+ log->log ("ffprobe call failed (could not create output file)");
+ return;
+ }
+
+ CloseHandle (child_stderr_write);
+
+ while (1) {
+ char buffer[512];
+ DWORD read;
+ if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
+ break;
+ }
+ fwrite (buffer, read, 1, o);
+ }
+
+ fclose (o);
+
+ WaitForSingleObject (process_info.hProcess, INFINITE);
+ CloseHandle (process_info.hProcess);
+ CloseHandle (process_info.hThread);
+ CloseHandle (child_stderr_read);
+#endif
+
+#ifdef DCPOMATIC_LINUX
+ string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+ log->log (String::compose ("Probing with %1", ffprobe));
+ system (ffprobe.c_str ());
+#endif
+
+#ifdef DCPOMATIC_OSX
+ uint32_t size = 1024;
+ char buffer[size];
+ if (_NSGetExecutablePath (buffer, &size)) {
+ log->log ("_NSGetExecutablePath failed");
+ return;
+ }
+
+ boost::filesystem::path path (buffer);
+ path.remove_filename ();
+ path /= "ffprobe";
+
+ string ffprobe = path.string() + " \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+ log->log (String::compose ("Probing with %1", ffprobe));
+ system (ffprobe.c_str ());
+#endif
+}
+
+list<pair<string, string> >
+mount_info ()
+{
+ list<pair<string, string> > m;
+
+#ifdef DCPOMATIC_LINUX
+ FILE* f = setmntent ("/etc/mtab", "r");
+ if (!f) {
+ return m;
+ }
+
+ while (1) {
+ struct mntent* mnt = getmntent (f);
+ if (!mnt) {
+ break;
+ }
+
+ m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
+ }
+
+ endmntent (f);
+#endif
+
+ return m;
+}
diff --git a/src/lib/cross.h b/src/lib/cross.h
index 110660b16..58fa821c7 100644
--- a/src/lib/cross.h
+++ b/src/lib/cross.h
@@ -17,8 +17,15 @@
*/
-#ifdef DVDOMATIC_WINDOWS
+#include <boost/filesystem.hpp>
+
+#ifdef DCPOMATIC_WINDOWS
#define WEXITSTATUS(w) (w)
#endif
-void dvdomatic_sleep (int);
+class Log;
+
+void dcpomatic_sleep (int);
+extern std::string cpu_info ();
+extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path, boost::shared_ptr<Log>);
+extern std::list<std::pair<std::string, std::string> > mount_info ();
diff --git a/src/lib/dci_metadata.cc b/src/lib/dci_metadata.cc
new file mode 100644
index 000000000..27306a15e
--- /dev/null
+++ b/src/lib/dci_metadata.cc
@@ -0,0 +1,73 @@
+/*
+ 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 <iostream>
+#include <libcxml/cxml.h>
+#include "dci_metadata.h"
+
+#include "i18n.h"
+
+using std::string;
+using boost::lexical_cast;
+using boost::shared_ptr;
+
+DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+{
+ content_version = node->number_child<int> ("ContentVersion");
+ audio_language = node->string_child ("AudioLanguage");
+ subtitle_language = node->string_child ("SubtitleLanguage");
+ territory = node->string_child ("Territory");
+ rating = node->string_child ("Rating");
+ studio = node->string_child ("Studio");
+ facility = node->string_child ("Facility");
+ package_type = node->string_child ("PackageType");
+}
+
+void
+DCIMetadata::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("ContentVersion")->add_child_text (lexical_cast<string> (content_version));
+ root->add_child("AudioLanguage")->add_child_text (audio_language);
+ root->add_child("SubtitleLanguage")->add_child_text (subtitle_language);
+ root->add_child("Territory")->add_child_text (territory);
+ root->add_child("Rating")->add_child_text (rating);
+ root->add_child("Studio")->add_child_text (studio);
+ root->add_child("Facility")->add_child_text (facility);
+ root->add_child("PackageType")->add_child_text (package_type);
+}
+
+void
+DCIMetadata::read_old_metadata (string k, string v)
+{
+ if (k == N_("audio_language")) {
+ audio_language = v;
+ } else if (k == N_("subtitle_language")) {
+ subtitle_language = v;
+ } else if (k == N_("territory")) {
+ territory = v;
+ } else if (k == N_("rating")) {
+ rating = v;
+ } else if (k == N_("studio")) {
+ studio = v;
+ } else if (k == N_("facility")) {
+ facility = v;
+ } else if (k == N_("package_type")) {
+ package_type = v;
+ }
+}
diff --git a/src/lib/video_sink.h b/src/lib/dci_metadata.h
index 7c128cf73..738e439de 100644
--- a/src/lib/video_sink.h
+++ b/src/lib/dci_metadata.h
@@ -17,24 +17,36 @@
*/
-#ifndef DVDOMATIC_VIDEO_SINK_H
-#define DVDOMATIC_VIDEO_SINK_H
+#ifndef DCPOMATIC_DCI_METADATA_H
+#define DCPOMATIC_DCI_METADATA_H
-#include <boost/shared_ptr.hpp>
-#include "util.h"
+#include <string>
+#include <libxml++/libxml++.h>
-class Subtitle;
-class Image;
+namespace cxml {
+ class Node;
+}
-class VideoSink
+class DCIMetadata
{
public:
- /** Call with a frame of video.
- * @param i Video frame image.
- * @param same true if i is the same as last time we were called.
- * @param s A subtitle that should be on this frame, or 0.
- */
- virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0;
+ DCIMetadata ()
+ : content_version (1)
+ {}
+
+ DCIMetadata (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+ void read_old_metadata (std::string, std::string);
+
+ int content_version;
+ std::string audio_language;
+ std::string subtitle_language;
+ std::string territory;
+ std::string rating;
+ std::string studio;
+ std::string facility;
+ std::string package_type;
};
#endif
diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc
index aae805308..82bd5fa01 100644
--- a/src/lib/dcp_content_type.cc
+++ b/src/lib/dcp_content_type.cc
@@ -24,6 +24,8 @@
#include <cassert>
#include "dcp_content_type.h"
+#include "i18n.h"
+
using namespace std;
vector<DCPContentType const *> DCPContentType::_dcp_content_types;
@@ -39,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
void
DCPContentType::setup_dcp_content_types ()
{
- _dcp_content_types.push_back (new DCPContentType ("Feature", libdcp::FEATURE, "FTR"));
- _dcp_content_types.push_back (new DCPContentType ("Short", libdcp::SHORT, "SHR"));
- _dcp_content_types.push_back (new DCPContentType ("Trailer", libdcp::TRAILER, "TLR"));
- _dcp_content_types.push_back (new DCPContentType ("Test", libdcp::TEST, "TST"));
- _dcp_content_types.push_back (new DCPContentType ("Transitional", libdcp::TRANSITIONAL, "XSN"));
- _dcp_content_types.push_back (new DCPContentType ("Rating", libdcp::RATING, "RTG"));
- _dcp_content_types.push_back (new DCPContentType ("Teaser", libdcp::TEASER, "TSR"));
- _dcp_content_types.push_back (new DCPContentType ("Policy", libdcp::POLICY, "POL"));
- _dcp_content_types.push_back (new DCPContentType ("Public Service Announcement", libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, "PSA"));
- _dcp_content_types.push_back (new DCPContentType ("Advertisement", libdcp::ADVERTISEMENT, "ADV"));
+ _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
+ _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
+ _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
+ _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
+ _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
+ _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
}
DCPContentType const *
@@ -64,6 +66,18 @@ DCPContentType::from_pretty_name (string n)
}
DCPContentType const *
+DCPContentType::from_dci_name (string n)
+{
+ for (vector<DCPContentType const *>::const_iterator i = _dcp_content_types.begin(); i != _dcp_content_types.end(); ++i) {
+ if ((*i)->dci_name() == n) {
+ return *i;
+ }
+ }
+
+ return 0;
+}
+
+DCPContentType const *
DCPContentType::from_index (int n)
{
assert (n >= 0 && n < int (_dcp_content_types.size ()));
diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h
index 2b6e60a19..965c16347 100644
--- a/src/lib/dcp_content_type.h
+++ b/src/lib/dcp_content_type.h
@@ -17,8 +17,8 @@
*/
-#ifndef DVDOMATIC_DCP_CONTENT_TYPE_H
-#define DVDOMATIC_DCP_CONTENT_TYPE_H
+#ifndef DCPOMATIC_DCP_CONTENT_TYPE_H
+#define DCPOMATIC_DCP_CONTENT_TYPE_H
/** @file src/content_type.h
* @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
@@ -31,7 +31,7 @@
/** @class DCPContentType
* @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
*/
-class DCPContentType
+class DCPContentType : public boost::noncopyable
{
public:
DCPContentType (std::string, libdcp::ContentKind, std::string);
@@ -50,6 +50,7 @@ public:
}
static DCPContentType const * from_pretty_name (std::string);
+ static DCPContentType const * from_dci_name (std::string);
static DCPContentType const * from_index (int);
static int as_index (DCPContentType const *);
static std::vector<DCPContentType const *> all ();
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index c6b29ba41..1c812cbf2 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -43,108 +43,73 @@
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
+#include <libdcp/rec709_linearised_gamma_lut.h>
+#include <libdcp/srgb_linearised_gamma_lut.h>
+#include <libdcp/gamma_lut.h>
+#include <libdcp/xyz_frame.h>
+#include <libdcp/rgb_xyz.h>
+#include <libdcp/colour_matrix.h>
+#include <libcxml/cxml.h>
#include "film.h"
#include "dcp_video_frame.h"
-#include "lut.h"
#include "config.h"
-#include "options.h"
#include "exceptions.h"
#include "server.h"
#include "util.h"
#include "scaler.h"
#include "image.h"
#include "log.h"
-#include "subtitle.h"
+
+#include "i18n.h"
using std::string;
using std::stringstream;
using std::ofstream;
+using std::cout;
using boost::shared_ptr;
+using boost::lexical_cast;
+using libdcp::Size;
+
+#define DCI_COEFFICENT (48.0 / 52.37)
/** Construct a DCP video frame.
* @param input Input image.
- * @param out Required size of output, in pixels (including any padding).
- * @param s Scaler to use.
- * @param p Number of pixels of padding either side of the image.
- * @param f Index of the frame within the Film.
- * @param fps Frames per second of the Film.
- * @param pp FFmpeg post-processing string to use.
- * @param clut Colour look-up table to use (see Config::colour_lut_index ())
+ * @param f Index of the frame within the DCP.
* @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
* @param l Log to write to.
*/
DCPVideoFrame::DCPVideoFrame (
- shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub,
- Size out, int p, int subtitle_offset, float subtitle_scale,
- Scaler const * s, SourceFrame f, float fps, string pp, int clut, int bw, Log* l
+ shared_ptr<const Image> image, int f, Eyes eyes, ColourConversion c, int dcp_fps, int bw, shared_ptr<Log> l
)
- : _input (yuv)
- , _subtitle (sub)
- , _out_size (out)
- , _padding (p)
- , _subtitle_offset (subtitle_offset)
- , _subtitle_scale (subtitle_scale)
- , _scaler (s)
+ : _image (image)
, _frame (f)
- , _frames_per_second (dcp_frame_rate(fps).frames_per_second)
- , _post_process (pp)
- , _colour_lut (clut)
+ , _eyes (eyes)
+ , _conversion (c)
+ , _frames_per_second (dcp_fps)
, _j2k_bandwidth (bw)
, _log (l)
- , _image (0)
- , _parameters (0)
- , _cinfo (0)
- , _cio (0)
{
}
-/** Create a libopenjpeg container suitable for our output image */
-void
-DCPVideoFrame::create_openjpeg_container ()
-{
- for (int i = 0; i < 3; ++i) {
- _cmptparm[i].dx = 1;
- _cmptparm[i].dy = 1;
- _cmptparm[i].w = _out_size.width;
- _cmptparm[i].h = _out_size.height;
- _cmptparm[i].x0 = 0;
- _cmptparm[i].y0 = 0;
- _cmptparm[i].prec = 12;
- _cmptparm[i].bpp = 12;
- _cmptparm[i].sgnd = 0;
- }
-
- _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
- if (_image == 0) {
- throw EncodeError ("could not create libopenjpeg image");
- }
-
- _image->x0 = 0;
- _image->y0 = 0;
- _image->x1 = _out_size.width;
- _image->y1 = _out_size.height;
-}
-
-DCPVideoFrame::~DCPVideoFrame ()
+DCPVideoFrame::DCPVideoFrame (shared_ptr<const Image> image, shared_ptr<const cxml::Node> node, shared_ptr<Log> log)
+ : _image (image)
+ , _log (log)
{
- if (_image) {
- opj_image_destroy (_image);
- }
-
- if (_cio) {
- opj_cio_close (_cio);
- }
-
- if (_cinfo) {
- opj_destroy_compress (_cinfo);
+ _frame = node->number_child<int> ("Frame");
+ string const eyes = node->string_child ("Eyes");
+ if (eyes == "Both") {
+ _eyes = EYES_BOTH;
+ } else if (eyes == "Left") {
+ _eyes = EYES_LEFT;
+ } else if (eyes == "Right") {
+ _eyes = EYES_RIGHT;
+ } else {
+ assert (false);
}
-
- if (_parameters) {
- free (_parameters->cp_comment);
- }
-
- delete _parameters;
+ _conversion = ColourConversion (node->node_child ("ColourConversion"));
+ _frames_per_second = node->number_child<int> ("FramesPerSecond");
+ _j2k_bandwidth = node->number_child<int> ("J2KBandwidth");
}
/** J2K-encode this frame on the local host.
@@ -153,151 +118,133 @@ DCPVideoFrame::~DCPVideoFrame ()
shared_ptr<EncodedData>
DCPVideoFrame::encode_locally ()
{
- if (!_post_process.empty ()) {
- _input = _input->post_process (_post_process, true);
- }
-
- shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true);
-
- if (_subtitle) {
- Rect tx = subtitle_transformed_area (
- float (_out_size.width) / _input->size().width,
- float (_out_size.height) / _input->size().height,
- _subtitle->area(), _subtitle_offset, _subtitle_scale
- );
-
- shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler, true);
- prepared->alpha_blend (im, tx.position());
+ shared_ptr<libdcp::LUT> in_lut;
+ if (_conversion.input_gamma_linearised) {
+ in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma);
+ } else {
+ in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma);
}
- create_openjpeg_container ();
-
- struct {
- double r, g, b;
- } s;
-
- struct {
- double x, y, z;
- } d;
-
- /* Copy our RGB into the openjpeg container, converting to XYZ in the process */
-
- int jn = 0;
- for (int y = 0; y < _out_size.height; ++y) {
- uint8_t* p = prepared->data()[0] + y * prepared->stride()[0];
- for (int x = 0; x < _out_size.width; ++x) {
-
- /* In gamma LUT (converting 8-bit input to 12-bit) */
- s.r = lut_in[_colour_lut][*p++ << 4];
- s.g = lut_in[_colour_lut][*p++ << 4];
- s.b = lut_in[_colour_lut][*p++ << 4];
-
- /* RGB to XYZ Matrix */
- d.x = ((s.r * color_matrix[_colour_lut][0][0]) +
- (s.g * color_matrix[_colour_lut][0][1]) +
- (s.b * color_matrix[_colour_lut][0][2]));
-
- d.y = ((s.r * color_matrix[_colour_lut][1][0]) +
- (s.g * color_matrix[_colour_lut][1][1]) +
- (s.b * color_matrix[_colour_lut][1][2]));
-
- d.z = ((s.r * color_matrix[_colour_lut][2][0]) +
- (s.g * color_matrix[_colour_lut][2][1]) +
- (s.b * color_matrix[_colour_lut][2][2]));
-
- /* DCI companding */
- d.x = d.x * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
- d.y = d.y * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
- d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
-
- /* Out gamma LUT */
- _image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x];
- _image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y];
- _image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z];
-
- ++jn;
+ /* XXX: libdcp should probably use boost */
+
+ double matrix[3][3];
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ matrix[i][j] = _conversion.matrix (i, j);
}
}
-
+
+ shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+ _image,
+ in_lut,
+ libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma),
+ matrix
+ );
+
/* Set the max image and component sizes based on frame_rate */
- int const max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+ int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+ if (_eyes == EYES_LEFT || _eyes == EYES_RIGHT) {
+ /* In 3D we have only half the normal bandwidth per eye */
+ max_cs_len /= 2;
+ }
int const max_comp_size = max_cs_len / 1.25;
+ /* get a J2K compressor handle */
+ opj_cinfo_t* cinfo = opj_create_compress (CODEC_J2K);
+ if (cinfo == 0) {
+ throw EncodeError (N_("could not create JPEG2000 encoder"));
+ }
+
/* Set encoding parameters to default values */
- _parameters = new opj_cparameters_t;
- opj_set_default_encoder_parameters (_parameters);
+ opj_cparameters_t parameters;
+ opj_set_default_encoder_parameters (&parameters);
/* Set default cinema parameters */
- _parameters->tile_size_on = false;
- _parameters->cp_tdx = 1;
- _parameters->cp_tdy = 1;
+ parameters.tile_size_on = false;
+ parameters.cp_tdx = 1;
+ parameters.cp_tdy = 1;
/* Tile part */
- _parameters->tp_flag = 'C';
- _parameters->tp_on = 1;
+ parameters.tp_flag = 'C';
+ parameters.tp_on = 1;
/* Tile and Image shall be at (0,0) */
- _parameters->cp_tx0 = 0;
- _parameters->cp_ty0 = 0;
- _parameters->image_offset_x0 = 0;
- _parameters->image_offset_y0 = 0;
+ parameters.cp_tx0 = 0;
+ parameters.cp_ty0 = 0;
+ parameters.image_offset_x0 = 0;
+ parameters.image_offset_y0 = 0;
/* Codeblock size = 32x32 */
- _parameters->cblockw_init = 32;
- _parameters->cblockh_init = 32;
- _parameters->csty |= 0x01;
+ parameters.cblockw_init = 32;
+ parameters.cblockh_init = 32;
+ parameters.csty |= 0x01;
/* The progression order shall be CPRL */
- _parameters->prog_order = CPRL;
+ parameters.prog_order = CPRL;
/* No ROI */
- _parameters->roi_compno = -1;
+ parameters.roi_compno = -1;
- _parameters->subsampling_dx = 1;
- _parameters->subsampling_dy = 1;
+ parameters.subsampling_dx = 1;
+ parameters.subsampling_dy = 1;
/* 9-7 transform */
- _parameters->irreversible = 1;
+ parameters.irreversible = 1;
- _parameters->tcp_rates[0] = 0;
- _parameters->tcp_numlayers++;
- _parameters->cp_disto_alloc = 1;
- _parameters->cp_rsiz = CINEMA2K;
- _parameters->cp_comment = strdup ("DVD-o-matic");
- _parameters->cp_cinema = CINEMA2K_24;
+ parameters.tcp_rates[0] = 0;
+ parameters.tcp_numlayers++;
+ parameters.cp_disto_alloc = 1;
+ parameters.cp_rsiz = CINEMA2K;
+ parameters.cp_comment = strdup (N_("DCP-o-matic"));
+ parameters.cp_cinema = CINEMA2K_24;
/* 3 components, so use MCT */
- _parameters->tcp_mct = 1;
+ parameters.tcp_mct = 1;
/* set max image */
- _parameters->max_comp_size = max_comp_size;
- _parameters->tcp_rates[0] = ((float) (3 * _image->comps[0].w * _image->comps[0].h * _image->comps[0].prec)) / (max_cs_len * 8);
-
- /* get a J2K compressor handle */
- _cinfo = opj_create_compress (CODEC_J2K);
- if (_cinfo == 0) {
- throw EncodeError ("could not create JPEG2000 encoder");
- }
+ parameters.max_comp_size = max_comp_size;
+ parameters.tcp_rates[0] = ((float) (3 * xyz->size().width * xyz->size().height * 12)) / (max_cs_len * 8);
/* Set event manager to null (openjpeg 1.3 bug) */
- _cinfo->event_mgr = 0;
+ cinfo->event_mgr = 0;
/* Setup the encoder parameters using the current image and user parameters */
- opj_setup_encoder (_cinfo, _parameters, _image);
+ opj_setup_encoder (cinfo, &parameters, xyz->opj_image ());
- _cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0);
- if (_cio == 0) {
- throw EncodeError ("could not open JPEG2000 stream");
+ opj_cio_t* cio = opj_cio_open ((opj_common_ptr) cinfo, 0, 0);
+ if (cio == 0) {
+ opj_destroy_compress (cinfo);
+ throw EncodeError (N_("could not open JPEG2000 stream"));
}
- int const r = opj_encode (_cinfo, _cio, _image, 0);
+ int const r = opj_encode (cinfo, cio, xyz->opj_image(), 0);
if (r == 0) {
- throw EncodeError ("JPEG2000 encoding failed");
+ opj_cio_close (cio);
+ opj_destroy_compress (cinfo);
+ throw EncodeError (N_("JPEG2000 encoding failed"));
}
- _log->log (String::compose ("Finished locally-encoded frame %1", _frame));
-
- return shared_ptr<EncodedData> (new LocallyEncodedData (_cio->buffer, cio_tell (_cio)));
+ switch (_eyes) {
+ case EYES_BOTH:
+ _log->log (String::compose (N_("Finished locally-encoded frame %1 for mono"), _frame));
+ break;
+ case EYES_LEFT:
+ _log->log (String::compose (N_("Finished locally-encoded frame %1 for L"), _frame));
+ break;
+ case EYES_RIGHT:
+ _log->log (String::compose (N_("Finished locally-encoded frame %1 for R"), _frame));
+ break;
+ default:
+ break;
+ }
+
+ shared_ptr<EncodedData> enc (new LocallyEncodedData (cio->buffer, cio_tell (cio)));
+
+ opj_cio_close (cio);
+ free (parameters.cp_comment);
+ opj_destroy_compress (cinfo);
+
+ return enc;
}
/** Send this frame to a remote server for J2K encoding, then read the result.
@@ -305,100 +252,135 @@ DCPVideoFrame::encode_locally ()
* @return Encoded data.
*/
shared_ptr<EncodedData>
-DCPVideoFrame::encode_remotely (ServerDescription const * serv)
+DCPVideoFrame::encode_remotely (ServerDescription serv)
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver (io_service);
- boost::asio::ip::tcp::resolver::query query (serv->host_name(), boost::lexical_cast<string> (Config::instance()->server_port ()));
+ boost::asio::ip::tcp::resolver::query query (serv.host_name(), boost::lexical_cast<string> (Config::instance()->server_port ()));
boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
shared_ptr<Socket> socket (new Socket);
- socket->connect (*endpoint_iterator, 30);
-
- stringstream s;
- s << "encode please\n"
- << "input_width " << _input->size().width << "\n"
- << "input_height " << _input->size().height << "\n"
- << "input_pixel_format " << _input->pixel_format() << "\n"
- << "output_width " << _out_size.width << "\n"
- << "output_height " << _out_size.height << "\n"
- << "padding " << _padding << "\n"
- << "subtitle_offset " << _subtitle_offset << "\n"
- << "subtitle_scale " << _subtitle_scale << "\n"
- << "scaler " << _scaler->id () << "\n"
- << "frame " << _frame << "\n"
- << "frames_per_second " << _frames_per_second << "\n";
-
- if (!_post_process.empty()) {
- s << "post_process " << _post_process << "\n";
- }
-
- s << "colour_lut " << _colour_lut << "\n"
- << "j2k_bandwidth " << _j2k_bandwidth << "\n";
-
- if (_subtitle) {
- s << "subtitle_x " << _subtitle->position().x << "\n"
- << "subtitle_y " << _subtitle->position().y << "\n"
- << "subtitle_width " << _subtitle->image()->size().width << "\n"
- << "subtitle_height " << _subtitle->image()->size().height << "\n";
- }
+ socket->connect (*endpoint_iterator);
+
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("EncodingRequest");
+
+ root->add_child("Version")->add_child_text (lexical_cast<string> (SERVER_LINK_VERSION));
+ root->add_child("Width")->add_child_text (lexical_cast<string> (_image->size().width));
+ root->add_child("Height")->add_child_text (lexical_cast<string> (_image->size().height));
+ add_metadata (root);
+
+ stringstream xml;
+ doc.write_to_stream (xml, "UTF-8");
_log->log (String::compose (
- "Sending to remote; pixel format %1, components %2, lines (%3,%4,%5), line sizes (%6,%7,%8)",
- _input->pixel_format(), _input->components(),
- _input->lines(0), _input->lines(1), _input->lines(2),
- _input->line_size()[0], _input->line_size()[1], _input->line_size()[2]
+ N_("Sending to remote; pixel format %1, components %2, lines (%3,%4,%5), line sizes (%6,%7,%8)"),
+ _image->pixel_format(), _image->components(),
+ _image->lines(0), _image->lines(1), _image->lines(2),
+ _image->line_size()[0], _image->line_size()[1], _image->line_size()[2]
));
+
+ socket->write (xml.str().length() + 1);
+ socket->write ((uint8_t *) xml.str().c_str(), xml.str().length() + 1);
+
+ _image->write_to_socket (socket);
+
+ shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
+ socket->read (e->data(), e->size());
+
+ _log->log (String::compose (N_("Finished remotely-encoded frame %1"), _frame));
- socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
+ return e;
+}
- _input->write_to_socket (socket);
- if (_subtitle) {
- _subtitle->image()->write_to_socket (socket);
+void
+DCPVideoFrame::add_metadata (xmlpp::Element* el) const
+{
+ el->add_child("Frame")->add_child_text (lexical_cast<string> (_frame));
+
+ switch (_eyes) {
+ case EYES_BOTH:
+ el->add_child("Eyes")->add_child_text ("Both");
+ break;
+ case EYES_LEFT:
+ el->add_child("Eyes")->add_child_text ("Left");
+ break;
+ case EYES_RIGHT:
+ el->add_child("Eyes")->add_child_text ("Right");
+ break;
+ default:
+ assert (false);
}
+
+ _conversion.as_xml (el->add_child("ColourConversion"));
+
+ el->add_child("FramesPerSecond")->add_child_text (lexical_cast<string> (_frames_per_second));
+ el->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
+}
- char buffer[32];
- socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
- socket->consume (strlen (buffer) + 1);
- shared_ptr<EncodedData> e (new RemotelyEncodedData (atoi (buffer)));
+EncodedData::EncodedData (int s)
+ : _data (new uint8_t[s])
+ , _size (s)
+{
- /* now read the rest */
- socket->read_definite_and_consume (e->data(), e->size(), 30);
+}
- _log->log (String::compose ("Finished remotely-encoded frame %1", _frame));
+EncodedData::EncodedData (string file)
+{
+ _size = boost::filesystem::file_size (file);
+ _data = new uint8_t[_size];
+
+ FILE* f = fopen (file.c_str(), N_("rb"));
+ if (!f) {
+ throw FileError (_("could not open file for reading"), file);
+ }
- return e;
+ size_t const r = fread (_data, 1, _size, f);
+ if (r != size_t (_size)) {
+ fclose (f);
+ throw FileError (_("could not read encoded data"), file);
+ }
+
+ fclose (f);
+}
+
+
+EncodedData::~EncodedData ()
+{
+ delete[] _data;
}
/** Write this data to a J2K file.
- * @param opt Options.
- * @param frame Frame index.
+ * @param Film Film.
+ * @param frame DCP frame index.
*/
void
-EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
+EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const
{
- string const tmp_j2k = opt->frame_out_path (frame, true);
+ string const tmp_j2c = film->j2c_path (frame, eyes, true);
- FILE* f = fopen (tmp_j2k.c_str (), "wb");
+ FILE* f = fopen (tmp_j2c.c_str (), N_("wb"));
if (!f) {
- throw WriteFileError (tmp_j2k, errno);
+ throw WriteFileError (tmp_j2c, errno);
}
fwrite (_data, 1, _size, f);
fclose (f);
- string const real_j2k = opt->frame_out_path (frame, false);
+ string const real_j2c = film->j2c_path (frame, eyes, false);
/* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
- boost::filesystem::rename (tmp_j2k, real_j2k);
+ boost::filesystem::rename (tmp_j2c, real_j2c);
+}
- /* Write a file containing the hash */
- string const hash = opt->hash_out_path (frame, false);
- ofstream h (hash.c_str());
- h << md5_digest (_data, _size) << "\n";
- h.close ();
+void
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+{
+ string const info = film->info_path (frame, eyes);
+ ofstream h (info.c_str());
+ fin.write (h);
}
/** Send this data to a socket.
@@ -407,20 +389,19 @@ EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
void
EncodedData::send (shared_ptr<Socket> socket)
{
- stringstream s;
- s << _size;
- socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
- socket->write (_data, _size, 30);
+ socket->write (_size);
+ socket->write (_data, _size);
}
-/** @param s Size of data in bytes */
-RemotelyEncodedData::RemotelyEncodedData (int s)
- : EncodedData (new uint8_t[s], s)
+LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s)
+ : EncodedData (s)
{
-
+ memcpy (_data, d, s);
}
-RemotelyEncodedData::~RemotelyEncodedData ()
+/** @param s Size of data in bytes */
+RemotelyEncodedData::RemotelyEncodedData (int s)
+ : EncodedData (s)
{
- delete[] _data;
+
}
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 134720da8..bf0b7f8c4 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -19,14 +19,15 @@
*/
#include <openjpeg.h>
+#include <libdcp/picture_asset.h>
+#include <libdcp/picture_asset_writer.h>
#include "util.h"
/** @file src/dcp_video_frame.h
* @brief A single frame of video destined for a DCP.
*/
-class FilmState;
-class EncodeOptions;
+class Film;
class ServerDescription;
class Scaler;
class Image;
@@ -36,21 +37,19 @@ class Subtitle;
/** @class EncodedData
* @brief Container for J2K-encoded data.
*/
-class EncodedData
+class EncodedData : public boost::noncopyable
{
public:
- /** @param d Data (will not be freed by this class, but may be by subclasses)
- * @param s Size of data, in bytes.
- */
- EncodedData (uint8_t* d, int s)
- : _data (d)
- , _size (s)
- {}
+ /** @param s Size of data, in bytes */
+ EncodedData (int s);
- virtual ~EncodedData () {}
+ EncodedData (std::string f);
+
+ virtual ~EncodedData ();
void send (boost::shared_ptr<Socket> socket);
- void write (boost::shared_ptr<const EncodeOptions>, SourceFrame);
+ void write (boost::shared_ptr<const Film>, int, Eyes) const;
+ void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
/** @return data */
uint8_t* data () const {
@@ -64,7 +63,7 @@ public:
protected:
uint8_t* _data; ///< data
- int _size; ///< data size in bytes
+ int _size; ///< data size in bytes
};
/** @class LocallyEncodedData
@@ -75,12 +74,10 @@ protected:
class LocallyEncodedData : public EncodedData
{
public:
- /** @param d Data (which will not be freed by this class)
+ /** @param d Data (which will be copied by this class)
* @param s Size of data, in bytes.
*/
- LocallyEncodedData (uint8_t* d, int s)
- : EncodedData (d, s)
- {}
+ LocallyEncodedData (uint8_t* d, int s);
};
/** @class RemotelyEncodedData
@@ -91,7 +88,6 @@ class RemotelyEncodedData : public EncodedData
{
public:
RemotelyEncodedData (int s);
- ~RemotelyEncodedData ();
};
/** @class DCPVideoFrame
@@ -103,44 +99,33 @@ public:
* Objects of this class are used for the queue that we keep
* of images that require encoding.
*/
-class DCPVideoFrame
+class DCPVideoFrame : public boost::noncopyable
{
public:
- DCPVideoFrame (
- boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, Size,
- int, int, float, Scaler const *, SourceFrame, float, std::string, int, int, Log *
- );
-
- virtual ~DCPVideoFrame ();
+ DCPVideoFrame (boost::shared_ptr<const Image>, int, Eyes, ColourConversion, int, int, boost::shared_ptr<Log>);
+ DCPVideoFrame (boost::shared_ptr<const Image>, boost::shared_ptr<const cxml::Node>, boost::shared_ptr<Log>);
boost::shared_ptr<EncodedData> encode_locally ();
- boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
+ boost::shared_ptr<EncodedData> encode_remotely (ServerDescription);
- SourceFrame frame () const {
+ Eyes eyes () const {
+ return _eyes;
+ }
+
+ int frame () const {
return _frame;
}
private:
- void create_openjpeg_container ();
-
- boost::shared_ptr<const Image> _input; ///< the input image
- boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image
- Size _out_size; ///< the required size of the output, in pixels
- int _padding;
- int _subtitle_offset;
- float _subtitle_scale;
- Scaler const * _scaler; ///< scaler to use
- SourceFrame _frame; ///< frame index within the Film's source
- int _frames_per_second; ///< Frames per second that we will use for the DCP (rounded)
- std::string _post_process; ///< FFmpeg post-processing string to use
- int _colour_lut; ///< Colour look-up table to use
- int _j2k_bandwidth; ///< J2K bandwidth to use
-
- Log* _log; ///< log
-
- opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t
- opj_image* _image; ///< libopenjpeg's image container
- opj_cparameters_t* _parameters; ///< libopenjpeg's parameters
- opj_cinfo_t* _cinfo; ///< libopenjpeg's opj_cinfo_t
- opj_cio_t* _cio; ///< libopenjpeg's opj_cio_t
+
+ void add_metadata (xmlpp::Element *) const;
+
+ boost::shared_ptr<const Image> _image;
+ int _frame; ///< frame index within the DCP's intrinsic duration
+ Eyes _eyes;
+ ColourConversion _conversion;
+ int _frames_per_second; ///< Frames per second that we will use for the DCP
+ int _j2k_bandwidth; ///< J2K bandwidth to use
+
+ boost::shared_ptr<Log> _log; ///< log
};
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 7066b488e..3f4cda6eb 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -21,56 +21,18 @@
* @brief Parent class for decoders of content.
*/
-#include <iostream>
-#include <stdint.h>
-#include <boost/lexical_cast.hpp>
#include "film.h"
-#include "format.h"
-#include "job.h"
-#include "options.h"
-#include "exceptions.h"
-#include "image.h"
-#include "util.h"
-#include "log.h"
#include "decoder.h"
-#include "delay_line.h"
-#include "subtitle.h"
-#include "filter_graph.h"
-using std::string;
-using std::stringstream;
-using std::min;
-using std::pair;
-using std::list;
+#include "i18n.h"
+
using boost::shared_ptr;
-using boost::optional;
/** @param f Film.
- * @param o Options.
- * @param j Job that we are running within, or 0
+ * @param o Decode options.
*/
-Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
+Decoder::Decoder (shared_ptr<const Film> f)
: _film (f)
- , _opt (o)
- , _job (j)
-{
- _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1));
-}
-
-/** Seek to a position as a source timestamp in seconds.
- * @return true on error.
- */
-bool
-Decoder::seek (double)
{
- throw DecodeError ("decoder does not support seek");
-}
-/** Seek so that the next frame we will produce is the same as the last one.
- * @return true on error.
- */
-bool
-Decoder::seek_to_last ()
-{
- throw DecodeError ("decoder does not support seek");
}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index 3908afa2f..d67592ed8 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,60 +21,36 @@
* @brief Parent class for decoders of content.
*/
-#ifndef DVDOMATIC_DECODER_H
-#define DVDOMATIC_DECODER_H
+#ifndef DCPOMATIC_DECODER_H
+#define DCPOMATIC_DECODER_H
-#include <vector>
-#include <string>
-#include <stdint.h>
#include <boost/shared_ptr.hpp>
-#include <boost/signals2.hpp>
-#include "util.h"
-#include "stream.h"
-#include "video_source.h"
-#include "audio_source.h"
-#include "film.h"
+#include <boost/weak_ptr.hpp>
+#include <boost/utility.hpp>
-class Job;
-class DecodeOptions;
-class Image;
-class Log;
-class DelayLine;
-class TimedSubtitle;
-class Subtitle;
-class FilterGraph;
+class Film;
/** @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.
*/
-class Decoder
+class Decoder : public boost::noncopyable
{
public:
- Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+ Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- virtual bool pass () = 0;
- virtual bool seek (double);
- virtual bool seek_to_last ();
-
- boost::signals2::signal<void()> OutputChanged;
+ /** Perform one decode pass of the content, which may or may not
+ * cause the object to emit some data.
+ */
+ virtual void pass () = 0;
+ virtual bool done () const = 0;
protected:
- /** our Film */
- boost::shared_ptr<Film> _film;
- /** our options */
- boost::shared_ptr<const DecodeOptions> _opt;
- /** associated Job, or 0 */
- Job* _job;
-private:
- virtual void film_changed (Film::Property) {}
+ virtual void flush () {};
- boost::signals2::scoped_connection _film_connection;
+ /** The Film that we are decoding in */
+ boost::weak_ptr<const Film> _film;
};
#endif
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
deleted file mode 100644
index 2a0d828e2..000000000
--- a/src/lib/decoder_factory.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/decoder_factory.cc
- * @brief A method to create an appropriate decoder for some content.
- */
-
-#include <boost/filesystem.hpp>
-#include "ffmpeg_decoder.h"
-#include "imagemagick_decoder.h"
-#include "film.h"
-#include "external_audio_decoder.h"
-#include "decoder_factory.h"
-
-using std::string;
-using std::pair;
-using std::make_pair;
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-
-Decoders
-decoder_factory (
- shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j
- )
-{
- if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
- /* A single image file, or a directory of them */
- return Decoders (
- shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o, j)),
- shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j))
- );
- }
-
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o, j));
- if (f->use_content_audio()) {
- return Decoders (fd, fd);
- }
-
- return Decoders (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j)));
-}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
deleted file mode 100644
index 47d977ce7..000000000
--- a/src/lib/decoder_factory.h
+++ /dev/null
@@ -1,49 +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.
-
-*/
-
-#ifndef DVDOMATIC_DECODER_FACTORY_H
-#define DVDOMATIC_DECODER_FACTORY_H
-
-/** @file src/decoder_factory.h
- * @brief A method to create appropriate decoders for some content.
- */
-
-class Film;
-class DecodeOptions;
-class Job;
-class VideoDecoder;
-class AudioDecoder;
-
-struct Decoders {
- Decoders () {}
-
- Decoders (boost::shared_ptr<VideoDecoder> v, boost::shared_ptr<AudioDecoder> a)
- : video (v)
- , audio (a)
- {}
-
- boost::shared_ptr<VideoDecoder> video;
- boost::shared_ptr<AudioDecoder> audio;
-};
-
-extern Decoders decoder_factory (
- boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *
- );
-
-#endif
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
deleted file mode 100644
index 45d8e9d9d..000000000
--- a/src/lib/delay_line.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <stdint.h>
-#include <cstring>
-#include <algorithm>
-#include <iostream>
-#include "delay_line.h"
-#include "util.h"
-
-using std::min;
-using boost::shared_ptr;
-
-/** @param channels Number of channels of audio.
- * @param frames Delay in frames, +ve to move audio later.
- */
-DelayLine::DelayLine (Log* log, int channels, int frames)
- : AudioProcessor (log)
- , _negative_delay_remaining (0)
- , _frames (frames)
-{
- if (_frames > 0) {
- /* We need a buffer to keep some data in */
- _buffers.reset (new AudioBuffers (channels, _frames));
- _buffers->make_silent ();
- } else if (_frames < 0) {
- /* We can do -ve delays just by chopping off
- the start, so no buffer needed.
- */
- _negative_delay_remaining = -_frames;
- }
-}
-
-void
-DelayLine::process_audio (shared_ptr<AudioBuffers> data)
-{
- if (_buffers) {
- /* We have some buffers, so we are moving the audio later */
-
- /* Copy the input data */
- AudioBuffers input (*data.get ());
-
- int to_do = data->frames ();
-
- /* Write some of our buffer to the output */
- int const from_buffer = min (to_do, _buffers->frames());
- data->copy_from (_buffers.get(), from_buffer, 0, 0);
- to_do -= from_buffer;
-
- /* Write some of the input to the output */
- int const from_input = to_do;
- data->copy_from (&input, from_input, 0, from_buffer);
-
- int const left_in_buffer = _buffers->frames() - from_buffer;
-
- /* Shuffle our buffer down */
- _buffers->move (from_buffer, 0, left_in_buffer);
-
- /* Copy remaining input data to our buffer */
- _buffers->copy_from (&input, input.frames() - from_input, from_input, left_in_buffer);
-
- } else {
-
- /* Chop the initial data off until _negative_delay_remaining
- is zero, then just pass data.
- */
-
- int const to_do = min (data->frames(), _negative_delay_remaining);
- if (to_do) {
- data->move (to_do, 0, data->frames() - to_do);
- data->set_frames (data->frames() - to_do);
- _negative_delay_remaining -= to_do;
- }
- }
-
- Audio (data);
-}
-
-void
-DelayLine::process_end ()
-{
- if (_frames < 0) {
- _buffers->make_silent ();
- Audio (_buffers);
- }
-}
diff --git a/src/lib/dolby_cp750.cc b/src/lib/dolby_cp750.cc
index 262e57bc7..162626ff0 100644
--- a/src/lib/dolby_cp750.cc
+++ b/src/lib/dolby_cp750.cc
@@ -19,10 +19,12 @@
#include "dolby_cp750.h"
+#include "i18n.h"
+
using namespace std;
DolbyCP750::DolbyCP750 ()
- : SoundProcessor ("dolby_cp750", "Dolby CP750")
+ : SoundProcessor ("dolby_cp750", _("Dolby CP750"))
{
}
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index efedfcfef..35ebfb52e 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -22,19 +22,17 @@
*/
#include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
#include "encoder.h"
#include "util.h"
-#include "options.h"
#include "film.h"
#include "log.h"
-#include "exceptions.h"
-#include "filter.h"
#include "config.h"
#include "dcp_video_frame.h"
#include "server.h"
#include "cross.h"
+#include "writer.h"
+
+#include "i18n.h"
using std::pair;
using std::string;
@@ -42,187 +40,108 @@ using std::stringstream;
using std::vector;
using std::list;
using std::cout;
+using std::min;
using std::make_pair;
-using namespace boost;
+using boost::shared_ptr;
+using boost::optional;
int const Encoder::_history_size = 25;
-/** @param f Film that we are encoding.
- * @param o Options.
- */
-Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
+/** @param f Film that we are encoding */
+Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<Job> j)
: _film (f)
- , _opt (o)
- , _just_skipped (false)
- , _video_frame (0)
- , _audio_frame (0)
-#ifdef HAVE_SWRESAMPLE
- , _swr_context (0)
-#endif
- , _audio_frames_written (0)
- , _process_end (false)
+ , _job (j)
+ , _video_frames_out (0)
+ , _state (TRANSCODING)
+ , _terminate (false)
{
- 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 < dcp_audio_channels (_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);
- }
- }
+ _have_a_real_frame[EYES_BOTH] = false;
+ _have_a_real_frame[EYES_LEFT] = false;
+ _have_a_real_frame[EYES_RIGHT] = false;
}
Encoder::~Encoder ()
{
- close_sound_files ();
- terminate_worker_threads ();
+ terminate_threads ();
+ if (_writer) {
+ _writer->finish ();
+ }
}
void
Encoder::process_begin ()
{
- 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_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,
- _film->audio_stream()->channel_layout(),
- AV_SAMPLE_FMT_FLTP,
- _film->target_audio_sample_rate(),
- _film->audio_stream()->channel_layout(),
- AV_SAMPLE_FMT_FLTP,
- _film->audio_stream()->sample_rate(),
- 0, 0
- );
-
- swr_init (_swr_context);
-#else
- throw EncodeError ("Cannot resample audio as libswresample is not present");
-#endif
- } else {
-#ifdef HAVE_SWRESAMPLE
- _swr_context = 0;
-#endif
- }
-
for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) {
- _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0)));
+ _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, optional<ServerDescription> ())));
}
- vector<ServerDescription*> servers = Config::instance()->servers ();
+ vector<ServerDescription> servers = Config::instance()->servers ();
- for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) {
- for (int j = 0; j < (*i)->threads (); ++j) {
- _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
+ for (vector<ServerDescription>::iterator i = servers.begin(); i != servers.end(); ++i) {
+ for (int j = 0; j < i->threads (); ++j) {
+ _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
}
}
+
+ _writer.reset (new Writer (_film, _job));
}
void
Encoder::process_end ()
{
-#if HAVE_SWRESAMPLE
- if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) {
-
- 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);
-
- if (frames < 0) {
- throw EncodeError ("could not run sample-rate converter");
- }
-
- if (frames == 0) {
- break;
- }
-
- out->set_frames (frames);
- write_audio (out);
- }
-
- swr_free (&_swr_context);
- }
-#endif
+ boost::mutex::scoped_lock lock (_mutex);
- if (_film->audio_stream()) {
- close_sound_files ();
-
- /* Rename .wav.tmp files to .wav */
- for (int i = 0; i < dcp_audio_channels (_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::mutex::scoped_lock lock (_worker_mutex);
-
- _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ()));
+ _film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ()));
/* Keep waking workers until the queue is empty */
while (!_queue.empty ()) {
- _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE);
- _worker_condition.notify_all ();
- _worker_condition.wait (lock);
+ _film->log()->log (String::compose (N_("Waking with %1"), _queue.size ()), Log::VERBOSE);
+ _condition.notify_all ();
+ _condition.wait (lock);
}
lock.unlock ();
- terminate_worker_threads ();
+ terminate_threads ();
- _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size()));
+ _film->log()->log (String::compose (N_("Mopping up %1"), _queue.size()));
/* The following sequence of events can occur in the above code:
1. a remote worker takes the last image off the queue
2. the loop above terminates
3. the remote worker fails to encode the image and puts it back on the queue
- 4. the remote worker is then terminated by terminate_worker_threads
+ 4. the remote worker is then terminated by terminate_threads
So just mop up anything left in the queue here.
*/
for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
- _film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ()));
+ _film->log()->log (String::compose (N_("Encode left-over frame %1"), (*i)->frame ()));
try {
- shared_ptr<EncodedData> e = (*i)->encode_locally ();
- e->write (_opt, (*i)->frame ());
+ _writer->write ((*i)->encode_locally(), (*i)->frame (), (*i)->eyes ());
frame_done ();
} catch (std::exception& e) {
- _film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
+ _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
}
}
- /* Now do links (or copies on windows) to duplicate frames */
- for (list<pair<int, int> >::iterator i = _links_required.begin(); i != _links_required.end(); ++i) {
- link (_opt->frame_out_path (i->first, false), _opt->frame_out_path (i->second, false));
- link (_opt->hash_out_path (i->first, false), _opt->hash_out_path (i->second, false));
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _state = HASHING;
}
+
+ _writer->finish ();
+ _writer.reset ();
}
/** @return an estimate of the current number of frames we are encoding per second,
* or 0 if not known.
*/
float
-Encoder::current_frames_per_second () const
+Encoder::current_encoding_rate () const
{
- boost::mutex::scoped_lock lock (_history_mutex);
+ boost::mutex::scoped_lock lock (_state_mutex);
if (int (_time_history.size()) < _history_size) {
return 0;
}
@@ -233,20 +152,12 @@ Encoder::current_frames_per_second () const
return _history_size / (seconds (now) - seconds (_time_history.back ()));
}
-/** @return true if the last frame to be processed was skipped as it already existed */
-bool
-Encoder::skipping () const
-{
- boost::mutex::scoped_lock (_history_mutex);
- return _just_skipped;
-}
-
-/** @return Number of video frames that have been received */
-SourceFrame
-Encoder::video_frame () const
+/** @return Number of video frames that have been sent out */
+int
+Encoder::video_frames_out () const
{
- boost::mutex::scoped_lock (_history_mutex);
- return _video_frame;
+ boost::mutex::scoped_lock (_state_mutex);
+ return _video_frames_out;
}
/** Should be called when a frame has been encoded successfully.
@@ -255,8 +166,7 @@ Encoder::video_frame () const
void
Encoder::frame_done ()
{
- boost::mutex::scoped_lock lock (_history_mutex);
- _just_skipped = false;
+ boost::mutex::scoped_lock lock (_state_mutex);
struct timeval tv;
gettimeofday (&tv, 0);
@@ -266,186 +176,81 @@ Encoder::frame_done ()
}
}
-/** Called by a subclass when it has just skipped the processing
- of a frame because it has already been done.
-*/
-void
-Encoder::frame_skipped ()
-{
- boost::mutex::scoped_lock lock (_history_mutex);
- _just_skipped = true;
-}
-
void
-Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub)
+Encoder::process_video (shared_ptr<const Image> image, Eyes eyes, ColourConversion conversion, bool same)
{
- if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) {
- ++_video_frame;
- return;
- }
+ boost::mutex::scoped_lock lock (_mutex);
- if (_opt->video_range) {
- pair<SourceFrame, SourceFrame> const r = _opt->video_range.get();
- if (_video_frame < r.first || _video_frame >= r.second) {
- ++_video_frame;
- return;
- }
- }
-
- boost::mutex::scoped_lock lock (_worker_mutex);
+ /* XXX: discard 3D here if required */
/* Wait until the queue has gone down a bit */
- while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) {
+ while (_queue.size() >= _threads.size() * 2 && !_terminate) {
TIMING ("decoder sleeps with queue of %1", _queue.size());
- _worker_condition.wait (lock);
+ _condition.wait (lock);
TIMING ("decoder wakes with queue of %1", _queue.size());
}
- if (_process_end) {
+ if (_terminate) {
return;
}
- /* Only do the processing if we don't already have a file for this frame */
- if (boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) {
- frame_skipped ();
- return;
+ if (_writer->thrown ()) {
+ _writer->rethrow ();
}
- if (same && _last_real_frame) {
- /* Use the last frame that we encoded. We need to postpone doing the actual link,
- as on windows the link is really a copy and the reference frame might not have
- finished encoding yet.
- */
- _links_required.push_back (make_pair (_last_real_frame.get(), _video_frame));
+ if (_writer->can_fake_write (_video_frames_out)) {
+ _writer->fake_write (_video_frames_out, eyes);
+ _have_a_real_frame[eyes] = false;
+ frame_done ();
+ } else if (same && _have_a_real_frame[eyes]) {
+ /* Use the last frame that we encoded. */
+ _writer->repeat (_video_frames_out, eyes);
+ frame_done ();
} else {
/* Queue this new frame for encoding */
- pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
TIMING ("adding to queue of %1", _queue.size ());
- _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+ _queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- image, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(),
- _film->scaler(), _video_frame, _film->frames_per_second(), s.second,
- _film->colour_lut(), _film->j2k_bandwidth(),
- _film->log()
+ image, _video_frames_out, eyes, conversion, _film->video_frame_rate(),
+ _film->j2k_bandwidth(), _film->log()
)
));
- _worker_condition.notify_all ();
- _last_real_frame = _video_frame;
- }
-
- ++_video_frame;
-}
-
-void
-Encoder::process_audio (shared_ptr<AudioBuffers> data)
-{
- if (_opt->audio_range) {
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (*data.get ()));
-
- /* Range that we are encoding */
- pair<int64_t, int64_t> required_range = _opt->audio_range.get();
- /* Range of this block of data */
- 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 */
- return;
- } else if (required_range.first >= this_range.first && required_range.first < this_range.second) {
- /* Trim start */
- int64_t const shift = required_range.first - this_range.first;
- trimmed->move (shift, 0, trimmed->frames() - shift);
- trimmed->set_frames (trimmed->frames() - shift);
- } else if (required_range.second >= this_range.first && required_range.second < this_range.second) {
- /* Trim end */
- trimmed->set_frames (required_range.second - this_range.first);
- }
-
- data = trimmed;
+ _condition.notify_all ();
+ _have_a_real_frame[eyes] = true;
}
-#if HAVE_SWRESAMPLE
- /* Maybe sample-rate convert */
- if (_swr_context) {
-
- /* Compute the resampled frames count and add 32 for luck */
- int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32;
-
- shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames));
-
- /* Resample audio */
- int const resampled_frames = swr_convert (
- _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames()
- );
-
- if (resampled_frames < 0) {
- throw EncodeError ("could not run sample-rate converter");
- }
-
- resampled->set_frames (resampled_frames);
-
- /* And point our variables at the resampled audio */
- data = resampled;
+ if (eyes != EYES_LEFT) {
+ ++_video_frames_out;
}
-#endif
-
- if (_film->audio_channels() == 1) {
- /* We need to switch things around so that the mono channel is on
- the centre channel of a 5.1 set (with other channels silent).
- */
-
- shared_ptr<AudioBuffers> b (new AudioBuffers (6, data->frames ()));
- b->make_silent (libdcp::LEFT);
- b->make_silent (libdcp::RIGHT);
- memcpy (b->data()[libdcp::CENTRE], data->data()[0], data->frames() * sizeof(float));
- b->make_silent (libdcp::LFE);
- b->make_silent (libdcp::LS);
- b->make_silent (libdcp::RS);
-
- data = b;
- }
-
- write_audio (data);
-
- _audio_frame += data->frames ();
}
void
-Encoder::write_audio (shared_ptr<const AudioBuffers> audio)
+Encoder::process_audio (shared_ptr<const AudioBuffers> data)
{
- for (int i = 0; i < audio->channels(); ++i) {
- sf_write_float (_sound_files[i], audio->data(i), audio->frames());
- }
-
- _audio_frames_written += audio->frames ();
+ _writer->write (data);
}
void
-Encoder::close_sound_files ()
+Encoder::terminate_threads ()
{
- for (vector<SNDFILE*>::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) {
- sf_close (*i);
- }
-
- _sound_files.clear ();
-}
-
-void
-Encoder::terminate_worker_threads ()
-{
- boost::mutex::scoped_lock lock (_worker_mutex);
- _process_end = true;
- _worker_condition.notify_all ();
+ boost::mutex::scoped_lock lock (_mutex);
+ _terminate = true;
+ _condition.notify_all ();
lock.unlock ();
- for (list<boost::thread *>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) {
- (*i)->join ();
+ for (list<boost::thread *>::iterator i = _threads.begin(); i != _threads.end(); ++i) {
+ if ((*i)->joinable ()) {
+ (*i)->join ();
+ }
delete *i;
}
+
+ _threads.clear ();
}
void
-Encoder::encoder_thread (ServerDescription* server)
+Encoder::encoder_thread (optional<ServerDescription> server)
{
/* Number of seconds that we currently wait between attempts
to connect to the server; not relevant for localhost
@@ -456,18 +261,18 @@ Encoder::encoder_thread (ServerDescription* server)
while (1) {
TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id());
- boost::mutex::scoped_lock lock (_worker_mutex);
- while (_queue.empty () && !_process_end) {
- _worker_condition.wait (lock);
+ boost::mutex::scoped_lock lock (_mutex);
+ while (_queue.empty () && !_terminate) {
+ _condition.wait (lock);
}
- if (_process_end) {
+ if (_terminate) {
return;
}
TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
- boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
- _film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
+ shared_ptr<DCPVideoFrame> vf = _queue.front ();
+ _film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 (%3) from queue"), boost::this_thread::get_id(), vf->frame(), vf->eyes ()));
_queue.pop_front ();
lock.unlock ();
@@ -476,10 +281,10 @@ Encoder::encoder_thread (ServerDescription* server)
if (server) {
try {
- encoded = vf->encode_remotely (server);
+ encoded = vf->encode_remotely (server.get ());
if (remote_backoff > 0) {
- _film->log()->log (String::compose ("%1 was lost, but now she is found; removing backoff", server->host_name ()));
+ _film->log()->log (String::compose (N_("%1 was lost, but now she is found; removing backoff"), server->host_name ()));
}
/* This job succeeded, so remove any backoff */
@@ -492,7 +297,7 @@ Encoder::encoder_thread (ServerDescription* server)
}
_film->log()->log (
String::compose (
- "Remote encode of %1 on %2 failed (%3); thread sleeping for %4s",
+ N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"),
vf->frame(), server->host_name(), e.what(), remote_backoff)
);
}
@@ -503,42 +308,27 @@ Encoder::encoder_thread (ServerDescription* server)
encoded = vf->encode_locally ();
TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->frame());
} catch (std::exception& e) {
- _film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
+ _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
}
}
if (encoded) {
- encoded->write (_opt, vf->frame ());
+ _writer->write (encoded, vf->frame (), vf->eyes ());
frame_done ();
} else {
lock.lock ();
_film->log()->log (
- String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame())
+ String::compose (N_("Encoder thread %1 pushes frame %2 back onto queue after failure"), boost::this_thread::get_id(), vf->frame())
);
_queue.push_front (vf);
lock.unlock ();
}
if (remote_backoff > 0) {
- dvdomatic_sleep (remote_backoff);
+ dcpomatic_sleep (remote_backoff);
}
lock.lock ();
- _worker_condition.notify_all ();
+ _condition.notify_all ();
}
}
-
-void
-Encoder::link (string a, string b) const
-{
-#ifdef DVDOMATIC_POSIX
- int const r = symlink (a.c_str(), b.c_str());
- if (r) {
- throw EncodeError (String::compose ("could not create symlink from %1 to %2", a, b));
- }
-#endif
-
-#ifdef DVDOMATIC_WINDOWS
- boost::filesystem::copy_file (a, b);
-#endif
-}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index 52ccfc166..e9b30df9e 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -17,11 +17,11 @@
*/
-#ifndef DVDOMATIC_ENCODER_H
-#define DVDOMATIC_ENCODER_H
+#ifndef DCPOMATIC_ENCODER_H
+#define DCPOMATIC_ENCODER_H
/** @file src/encoder.h
- * @brief Parent class for classes which can encode video and audio frames.
+ * @brief Encoder to J2K and WAV for DCP.
*/
#include <boost/shared_ptr.hpp>
@@ -33,111 +33,92 @@
#include <stdint.h>
extern "C" {
#include <libavutil/samplefmt.h>
-}
-#ifdef HAVE_SWRESAMPLE
-extern "C" {
#include <libswresample/swresample.h>
}
-#endif
-#include <sndfile.h>
#include "util.h"
-#include "video_sink.h"
-#include "audio_sink.h"
-class EncodeOptions;
class Image;
-class Subtitle;
class AudioBuffers;
class Film;
class ServerDescription;
class DCPVideoFrame;
+class EncodedData;
+class Writer;
+class Job;
/** @class Encoder
* @brief Encoder to J2K and WAV for DCP.
*
- * Video is supplied to process_video as YUV frames, and audio
+ * Video is supplied to process_video as RGB frames, and audio
* is supplied as uncompressed PCM in blocks of various sizes.
*/
-class Encoder : public VideoSink, public AudioSink
+class Encoder : public boost::noncopyable
{
public:
- Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o);
+ Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<Job>);
virtual ~Encoder ();
/** Called to indicate that a processing run is about to begin */
- virtual void process_begin ();
+ void process_begin ();
/** Call with a frame of video.
* @param i Video frame image.
* @param same true if i is the same as the last time we were called.
- * @param s A subtitle that should be on this frame, or 0.
*/
- void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s);
+ void process_video (boost::shared_ptr<const Image> i, Eyes eyes, ColourConversion, bool same);
/** Call with some audio data */
- void process_audio (boost::shared_ptr<AudioBuffers>);
+ void process_audio (boost::shared_ptr<const AudioBuffers>);
/** Called when a processing run has finished */
- virtual void process_end ();
+ void process_end ();
+
+ float current_encoding_rate () const;
+ int video_frames_out () const;
- float current_frames_per_second () const;
- bool skipping () const;
- SourceFrame video_frame () const;
+ enum State {
+ TRANSCODING,
+ HASHING
+ };
-protected:
+ State state () const {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state;
+ }
+
+private:
void frame_done ();
- void frame_skipped ();
+ void encoder_thread (boost::optional<ServerDescription>);
+ void terminate_threads ();
+
/** Film that we are encoding */
boost::shared_ptr<const Film> _film;
- /** Options */
- boost::shared_ptr<const EncodeOptions> _opt;
+ boost::shared_ptr<Job> _job;
- /** Mutex for _time_history, _just_skipped and _last_frame */
- mutable boost::mutex _history_mutex;
+ /** Mutex for _time_history, _last_frame and _state */
+ mutable boost::mutex _state_mutex;
/** List of the times of completion of the last _history_size frames;
first is the most recently completed.
*/
std::list<struct timeval> _time_history;
/** Number of frames that we should keep history for */
static int const _history_size;
- /** true if the last frame we processed was skipped (because it was already done) */
- bool _just_skipped;
- /** Number of video frames received so far */
- SourceFrame _video_frame;
- /** Number of audio frames received so far */
- int64_t _audio_frame;
+ /** Number of video frames written for the DCP so far */
+ int _video_frames_out;
+ State _state;
-private:
- void close_sound_files ();
- void write_audio (boost::shared_ptr<const AudioBuffers> audio);
-
- void encoder_thread (ServerDescription *);
- void terminate_worker_threads ();
- void link (std::string, std::string) const;
-
-#if HAVE_SWRESAMPLE
- SwrContext* _swr_context;
-#endif
-
- /** List of links that we need to create when all frames have been processed;
- * such that we need to call link (first, second) for each member of this list.
- * In other words, `first' is a `real' frame and `second' should be a link to `first'.
- */
- std::list<std::pair<int, int> > _links_required;
-
- std::vector<SNDFILE*> _sound_files;
- int64_t _audio_frames_written;
-
- boost::optional<int> _last_real_frame;
- bool _process_end;
+ bool _have_a_real_frame[EYES_COUNT];
+ bool _terminate;
std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
- std::list<boost::thread *> _worker_threads;
- mutable boost::mutex _worker_mutex;
- boost::condition _worker_condition;
+ std::list<boost::thread *> _threads;
+ mutable boost::mutex _mutex;
+ boost::condition _condition;
+
+ boost::shared_ptr<Writer> _writer;
};
#endif
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
index a783cde33..cbf180ffc 100644
--- a/src/lib/examine_content_job.cc
+++ b/src/lib/examine_content_job.cc
@@ -17,27 +17,21 @@
*/
-/** @file src/examine_content_job.cc
- * @brief A class to run through content at high speed to find its length.
- */
-
#include <boost/filesystem.hpp>
#include "examine_content_job.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "decoder.h"
-#include "transcoder.h"
#include "log.h"
+#include "content.h"
#include "film.h"
-#include "video_decoder.h"
+
+#include "i18n.h"
using std::string;
-using std::vector;
-using std::pair;
+using std::cout;
using boost::shared_ptr;
-ExamineContentJob::ExamineContentJob (shared_ptr<Film> f, shared_ptr<Job> req)
- : Job (f, req)
+ExamineContentJob::ExamineContentJob (shared_ptr<const Film> f, shared_ptr<Content> c)
+ : Job (f)
+ , _content (c)
{
}
@@ -49,61 +43,13 @@ ExamineContentJob::~ExamineContentJob ()
string
ExamineContentJob::name () const
{
- if (_film->name().empty ()) {
- return "Examine content";
- }
-
- return String::compose ("Examine content of %1", _film->name());
+ return _("Examine content");
}
void
ExamineContentJob::run ()
{
- descend (0.5);
- _film->set_content_digest (md5_digest (_film->content_path ()));
- ascend ();
-
- descend (0.5);
-
- /* Set the film's length to either
- a) a length judged by running through the content or
- b) the length from a decoder's header.
- */
- if (!_film->trust_content_header()) {
- /* Decode the content to get an accurate length */
-
- /* We don't want to use any existing length here, as progress
- will be messed up.
- */
- _film->unset_length ();
- _film->set_crop (Crop ());
-
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- o->decode_audio = false;
-
- Decoders decoders = decoder_factory (_film, o, this);
-
- set_progress_unknown ();
- while (!decoders.video->pass()) {
- /* keep going */
- }
-
- _film->set_length (decoders.video->video_frame());
-
- _film->log()->log (String::compose ("Video length examined as %1 frames", _film->length().get()));
-
- } else {
-
- /* Get a quick decoder to get the content's length from its header */
-
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- Decoders d = decoder_factory (_film, o, 0);
- _film->set_length (d.video->length());
-
- _film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get()));
- }
-
- ascend ();
+ _content->examine (shared_from_this ());
set_progress (1);
set_state (FINISHED_OK);
}
diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h
index 729c287b5..b6903b86b 100644
--- a/src/lib/examine_content_job.h
+++ b/src/lib/examine_content_job.h
@@ -17,22 +17,22 @@
*/
-/** @file src/examine_content_job.h
- * @brief A class to obtain the length and MD5 digest of a content file.
- */
-
+#include <boost/shared_ptr.hpp>
#include "job.h"
-/** @class ExamineContentJob
- * @brief A class to obtain the length and MD5 digest of a content file.
- */
+class Content;
+class Log;
+
class ExamineContentJob : public Job
{
public:
- ExamineContentJob (boost::shared_ptr<Film>, boost::shared_ptr<Job> req);
+ ExamineContentJob (boost::shared_ptr<const Film>, boost::shared_ptr<Content>);
~ExamineContentJob ();
std::string name () const;
void run ();
+
+private:
+ boost::shared_ptr<Content> _content;
};
diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc
new file mode 100644
index 000000000..8144f41b9
--- /dev/null
+++ b/src/lib/exceptions.cc
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "exceptions.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+using std::string;
+
+/** @param f File that we were trying to open */
+OpenFileError::OpenFileError (boost::filesystem::path f)
+ : FileError (String::compose (_("could not open file %1"), f.string()), f)
+{
+
+}
+
+/** @param f File that we were trying to create */
+CreateFileError::CreateFileError (boost::filesystem::path f)
+ : FileError (String::compose (_("could not create file %1"), f.string()), f)
+{
+
+}
+
+ReadFileError::ReadFileError (boost::filesystem::path f, int e)
+ : FileError (String::compose (_("could not read from file %1 (%2)"), f.string(), strerror (e)), f)
+{
+
+}
+
+WriteFileError::WriteFileError (boost::filesystem::path f, int e)
+ : FileError (String::compose (_("could not write to file %1 (%2)"), f.string(), strerror (e)), f)
+{
+
+}
+
+MissingSettingError::MissingSettingError (string s)
+ : SettingError (s, String::compose (_("missing required setting %1"), s))
+{
+
+}
+
+PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+ : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
+{
+
+}
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index 06177863a..b04d973dc 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -17,13 +17,21 @@
*/
+#ifndef DCPOMATIC_EXCEPTIONS_H
+#define DCPOMATIC_EXCEPTIONS_H
+
/** @file src/exceptions.h
* @brief Our exceptions.
*/
#include <stdexcept>
-#include <sstream>
#include <cstring>
+#include <boost/exception/all.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/thread.hpp>
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
/** @class StringError
* @brief A parent class for exceptions using messages held in a std::string
@@ -80,7 +88,7 @@ public:
/** @param m Error message.
* @param f Name of the file that this exception concerns.
*/
- FileError (std::string m, std::string f)
+ FileError (std::string m, boost::filesystem::path f)
: StringError (m)
, _file (f)
{}
@@ -88,13 +96,13 @@ public:
virtual ~FileError () throw () {}
/** @return name of the file that this exception concerns */
- std::string file () const {
+ boost::filesystem::path file () const {
return _file;
}
private:
/** name of the file that this exception concerns */
- std::string _file;
+ boost::filesystem::path _file;
};
@@ -105,9 +113,7 @@ class OpenFileError : public FileError
{
public:
/** @param f File that we were trying to open */
- OpenFileError (std::string f)
- : FileError ("could not open file " + f, f)
- {}
+ OpenFileError (boost::filesystem::path f);
};
/** @class CreateFileError.
@@ -117,9 +123,7 @@ class CreateFileError : public FileError
{
public:
/** @param f File that we were trying to create */
- CreateFileError (std::string f)
- : FileError ("could not create file " + f, f)
- {}
+ CreateFileError (boost::filesystem::path f);
};
@@ -132,16 +136,7 @@ public:
/** @param f File that we were trying to read from.
* @param e errno value, or 0.
*/
- ReadFileError (std::string f, int e = 0)
- : FileError ("", f)
- {
- std::stringstream s;
- s << "could not read from file " << f;
- if (e) {
- s << " (" << strerror (e) << ")";
- }
- _what = s.str ();
- }
+ ReadFileError (boost::filesystem::path f, int e = 0);
};
/** @class WriteFileError.
@@ -153,16 +148,7 @@ public:
/** @param f File that we were trying to write to.
* @param e errno value, or 0.
*/
- WriteFileError (std::string f, int e)
- : FileError ("", f)
- {
- std::stringstream s;
- s << "could not write to file " << f;
- if (e) {
- s << " (" << strerror (e) << ")";
- }
- _what = s.str ();
- }
+ WriteFileError (boost::filesystem::path f, int e);
};
/** @class SettingError.
@@ -197,9 +183,7 @@ class MissingSettingError : public SettingError
{
public:
/** @param s Name of setting that was required */
- MissingSettingError (std::string s)
- : SettingError (s, "missing required setting " + s)
- {}
+ MissingSettingError (std::string s);
};
/** @class BadSettingError
@@ -232,3 +216,38 @@ public:
: StringError (s)
{}
};
+
+class PixelFormatError : public StringError
+{
+public:
+ PixelFormatError (std::string o, AVPixelFormat f);
+};
+
+class ExceptionStore
+{
+public:
+ bool thrown () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _exception;
+ }
+
+ void rethrow () {
+ boost::mutex::scoped_lock lm (_mutex);
+ boost::rethrow_exception (_exception);
+ }
+
+protected:
+
+ void store_current () {
+ boost::mutex::scoped_lock lm (_mutex);
+ _exception = boost::current_exception ();
+ }
+
+private:
+ boost::exception_ptr _exception;
+ mutable boost::mutex _mutex;
+};
+
+
+
+#endif
diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc
deleted file mode 100644
index 25c8068b6..000000000
--- a/src/lib/external_audio_decoder.cc
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#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 DecodeOptions> 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/ffmpeg.cc b/src/lib/ffmpeg.cc
new file mode 100644
index 000000000..c97b79a71
--- /dev/null
+++ b/src/lib/ffmpeg.cc
@@ -0,0 +1,148 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libpostproc/postprocess.h>
+}
+#include "ffmpeg.h"
+#include "ffmpeg_content.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+
+boost::mutex FFmpeg::_mutex;
+
+FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
+ : _ffmpeg_content (c)
+ , _format_context (0)
+ , _frame (0)
+ , _video_stream (-1)
+{
+ setup_general ();
+ setup_video ();
+ setup_audio ();
+}
+
+FFmpeg::~FFmpeg ()
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+ AVCodecContext* context = _format_context->streams[i]->codec;
+ if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) {
+ avcodec_close (context);
+ }
+ }
+
+ av_free (_frame);
+
+ avformat_close_input (&_format_context);
+}
+
+void
+FFmpeg::setup_general ()
+{
+ av_register_all ();
+
+ if (avformat_open_input (&_format_context, _ffmpeg_content->path().string().c_str(), 0, 0) < 0) {
+ throw OpenFileError (_ffmpeg_content->path().string ());
+ }
+
+ if (avformat_find_stream_info (_format_context, 0) < 0) {
+ throw DecodeError (_("could not find stream information"));
+ }
+
+ /* Find video stream */
+
+ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+ AVStream* s = _format_context->streams[i];
+ if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+ _video_stream = i;
+ }
+ }
+
+ if (_video_stream < 0) {
+ throw DecodeError (N_("could not find video stream"));
+ }
+
+ _frame = avcodec_alloc_frame ();
+ if (_frame == 0) {
+ throw DecodeError (N_("could not allocate frame"));
+ }
+}
+
+void
+FFmpeg::setup_video ()
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ AVCodecContext* context = _format_context->streams[_video_stream]->codec;
+ AVCodec* codec = avcodec_find_decoder (context->codec_id);
+
+ if (codec == 0) {
+ throw DecodeError (_("could not find video decoder"));
+ }
+
+ if (avcodec_open2 (context, codec, 0) < 0) {
+ throw DecodeError (N_("could not open video decoder"));
+ }
+}
+
+void
+FFmpeg::setup_audio ()
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+ AVCodecContext* context = _format_context->streams[i]->codec;
+ if (context->codec_type != AVMEDIA_TYPE_AUDIO) {
+ continue;
+ }
+
+ AVCodec* codec = avcodec_find_decoder (context->codec_id);
+ if (codec == 0) {
+ throw DecodeError (_("could not find audio decoder"));
+ }
+
+ if (avcodec_open2 (context, codec, 0) < 0) {
+ throw DecodeError (N_("could not open audio decoder"));
+ }
+ }
+}
+
+
+AVCodecContext *
+FFmpeg::video_codec_context () const
+{
+ return _format_context->streams[_video_stream]->codec;
+}
+
+AVCodecContext *
+FFmpeg::audio_codec_context () const
+{
+ return _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
+}
diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h
new file mode 100644
index 000000000..4d1a45da3
--- /dev/null
+++ b/src/lib/ffmpeg.h
@@ -0,0 +1,75 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_FFMPEG_H
+#define DCPOMATIC_FFMPEG_H
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+struct AVFilterGraph;
+struct AVCodecContext;
+struct AVFilterContext;
+struct AVFormatContext;
+struct AVFrame;
+struct AVBufferContext;
+struct AVCodec;
+struct AVStream;
+
+class FFmpegContent;
+
+class FFmpeg
+{
+public:
+ FFmpeg (boost::shared_ptr<const FFmpegContent>);
+ virtual ~FFmpeg ();
+
+ boost::shared_ptr<const FFmpegContent> ffmpeg_content () const {
+ return _ffmpeg_content;
+ }
+
+protected:
+ AVCodecContext* video_codec_context () const;
+ AVCodecContext* audio_codec_context () const;
+
+ boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
+ AVFormatContext* _format_context;
+ AVPacket _packet;
+ AVFrame* _frame;
+
+ int _video_stream;
+
+ /* It would appear (though not completely verified) that one must have
+ a mutex around calls to avcodec_open* and avcodec_close... and here
+ it is.
+ */
+ static boost::mutex _mutex;
+
+private:
+ void setup_general ();
+ void setup_video ();
+ void setup_audio ();
+};
+
+#endif
diff --git a/src/lib/ffmpeg_compatibility.cc b/src/lib/ffmpeg_compatibility.cc
deleted file mode 100644
index 09f9276ac..000000000
--- a/src/lib/ffmpeg_compatibility.cc
+++ /dev/null
@@ -1,117 +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.
-
-*/
-
-extern "C" {
-#include <libavfilter/avfiltergraph.h>
-}
-#include "exceptions.h"
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-
-typedef struct {
- enum PixelFormat pix_fmt;
-} AVSinkContext;
-
-static int
-avsink_init (AVFilterContext* ctx, const char* args, void* opaque)
-{
- AVSinkContext* priv = (AVSinkContext *) ctx->priv;
- if (!opaque) {
- return AVERROR (EINVAL);
- }
-
- *priv = *(AVSinkContext *) opaque;
- return 0;
-}
-
-static void
-null_end_frame (AVFilterLink *)
-{
-
-}
-
-static int
-avsink_query_formats (AVFilterContext* ctx)
-{
- AVSinkContext* priv = (AVSinkContext *) ctx->priv;
- enum PixelFormat pix_fmts[] = {
- priv->pix_fmt,
- PIX_FMT_NONE
- };
-
- avfilter_set_common_formats (ctx, avfilter_make_format_list ((int *) pix_fmts));
- return 0;
-}
-
-#endif
-
-AVFilter*
-get_sink ()
-{
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
- /* XXX does this leak stuff? */
- AVFilter* buffer_sink = new AVFilter;
- buffer_sink->name = av_strdup ("avsink");
- buffer_sink->priv_size = sizeof (AVSinkContext);
- buffer_sink->init = avsink_init;
- buffer_sink->query_formats = avsink_query_formats;
- buffer_sink->inputs = new AVFilterPad[2];
- AVFilterPad* i0 = const_cast<AVFilterPad*> (&buffer_sink->inputs[0]);
- i0->name = "default";
- i0->type = AVMEDIA_TYPE_VIDEO;
- i0->min_perms = AV_PERM_READ;
- i0->rej_perms = 0;
- i0->start_frame = 0;
- i0->get_video_buffer = 0;
- i0->get_audio_buffer = 0;
- i0->end_frame = null_end_frame;
- i0->draw_slice = 0;
- i0->filter_samples = 0;
- i0->poll_frame = 0;
- i0->request_frame = 0;
- i0->config_props = 0;
- const_cast<AVFilterPad*> (&buffer_sink->inputs[1])->name = 0;
- buffer_sink->outputs = new AVFilterPad[1];
- const_cast<AVFilterPad*> (&buffer_sink->outputs[0])->name = 0;
- return buffer_sink;
-#else
- AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
- if (buffer_sink == 0) {
- throw DecodeError ("Could not create buffer sink filter");
- }
-
- return buffer_sink;
-#endif
-}
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-AVFilterInOut *
-avfilter_inout_alloc ()
-{
- return (AVFilterInOut *) av_malloc (sizeof (AVFilterInOut));
-}
-#endif
-
-#ifndef HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP
-int64_t av_frame_get_best_effort_timestamp (AVFrame const * f)
-{
- return f->best_effort_timestamp;
-}
-
-#endif
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
new file mode 100644
index 000000000..de967c045
--- /dev/null
+++ b/src/lib/ffmpeg_content.cc
@@ -0,0 +1,407 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "ffmpeg_content.h"
+#include "ffmpeg_examiner.h"
+#include "compose.hpp"
+#include "job.h"
+#include "util.h"
+#include "filter.h"
+#include "film.h"
+#include "log.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::vector;
+using std::list;
+using std::cout;
+using std::pair;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
+int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
+int const FFmpegContentProperty::AUDIO_STREAMS = 102;
+int const FFmpegContentProperty::AUDIO_STREAM = 103;
+int const FFmpegContentProperty::FILTERS = 104;
+
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , VideoContent (f, p)
+ , AudioContent (f, p)
+ , SubtitleContent (f, p)
+{
+
+}
+
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , VideoContent (f, node)
+ , AudioContent (f, node)
+ , SubtitleContent (f, node)
+{
+ list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
+ if ((*i)->optional_number_child<int> ("Selected")) {
+ _subtitle_stream = _subtitle_streams.back ();
+ }
+ }
+
+ c = node->node_children ("AudioStream");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i)));
+ if ((*i)->optional_number_child<int> ("Selected")) {
+ _audio_stream = _audio_streams.back ();
+ }
+ }
+
+ c = node->node_children ("Filter");
+ for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+ _filters.push_back (Filter::from_id ((*i)->content ()));
+ }
+
+ _first_video = node->optional_number_child<double> ("FirstVideo");
+}
+
+void
+FFmpegContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("FFmpeg");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+ AudioContent::as_xml (node);
+ SubtitleContent::as_xml (node);
+
+ boost::mutex::scoped_lock lm (_mutex);
+
+ for (vector<shared_ptr<FFmpegSubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
+ xmlpp::Node* t = node->add_child("SubtitleStream");
+ if (_subtitle_stream && *i == _subtitle_stream) {
+ t->add_child("Selected")->add_child_text("1");
+ }
+ (*i)->as_xml (t);
+ }
+
+ for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
+ xmlpp::Node* t = node->add_child("AudioStream");
+ if (_audio_stream && *i == _audio_stream) {
+ t->add_child("Selected")->add_child_text("1");
+ }
+ (*i)->as_xml (t);
+ }
+
+ for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+ node->add_child("Filter")->add_child_text ((*i)->id ());
+ }
+
+ if (_first_video) {
+ node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+ }
+}
+
+void
+FFmpegContent::examine (shared_ptr<Job> job)
+{
+ job->set_progress_unknown ();
+
+ Content::examine (job);
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
+
+ VideoContent::Frame video_length = 0;
+ video_length = examiner->video_length ();
+ film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ _video_length = video_length;
+
+ _subtitle_streams = examiner->subtitle_streams ();
+ if (!_subtitle_streams.empty ()) {
+ _subtitle_stream = _subtitle_streams.front ();
+ }
+
+ _audio_streams = examiner->audio_streams ();
+ if (!_audio_streams.empty ()) {
+ _audio_stream = _audio_streams.front ();
+ }
+
+ _first_video = examiner->first_video ();
+ }
+
+ take_from_video_examiner (examiner);
+
+ signal_changed (ContentProperty::LENGTH);
+ signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
+ signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+ signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
+ signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+ signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+}
+
+string
+FFmpegContent::summary () const
+{
+ /* Get the string() here so that the name does not have quotes around it */
+ return String::compose (_("%1 [movie]"), path().filename().string());
+}
+
+string
+FFmpegContent::technical_summary () const
+{
+ string as = "none";
+ if (_audio_stream) {
+ as = String::compose ("id %1", _audio_stream->id);
+ }
+
+ string ss = "none";
+ if (_subtitle_stream) {
+ ss = String::compose ("id %1", _subtitle_stream->id);
+ }
+
+ pair<string, string> filt = Filter::ffmpeg_strings (_filters);
+
+ return Content::technical_summary() + " - "
+ + VideoContent::technical_summary() + " - "
+ + AudioContent::technical_summary() + " - "
+ + String::compose (
+ "ffmpeg: audio %1, subtitle %2, filters %3 %4", as, ss, filt.first, filt.second
+ );
+}
+
+string
+FFmpegContent::information () const
+{
+ if (video_length() == 0 || video_frame_rate() == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
+ s << VideoContent::information ();
+
+ return s.str ();
+}
+
+void
+FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _subtitle_stream = s;
+ }
+
+ signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+}
+
+void
+FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_stream = s;
+ }
+
+ signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+}
+
+AudioContent::Frame
+FFmpegContent::audio_length () const
+{
+ int const cafr = content_audio_frame_rate ();
+ int const vfr = video_frame_rate ();
+ VideoContent::Frame const vl = video_length ();
+
+ boost::mutex::scoped_lock lm (_mutex);
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return video_frames_to_audio_frames (vl, cafr, vfr);
+}
+
+int
+FFmpegContent::audio_channels () const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return _audio_stream->channels;
+}
+
+int
+FFmpegContent::content_audio_frame_rate () const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (!_audio_stream) {
+ return 0;
+ }
+
+ return _audio_stream->frame_rate;
+}
+
+int
+FFmpegContent::output_audio_frame_rate () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ /* Resample to a DCI-approved sample rate */
+ double t = dcp_audio_frame_rate (content_audio_frame_rate ());
+
+ FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+
+ /* Compensate if the DCP is being run at a different frame rate
+ to the source; that is, if the video is run such that it will
+ look different in the DCP compared to the source (slower or faster).
+ skip/repeat doesn't come into effect here.
+ */
+
+ if (frc.change_speed) {
+ t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+ }
+
+ return rint (t);
+}
+
+bool
+operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+{
+ return a.id == b.id;
+}
+
+bool
+operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+{
+ return a.id == b.id;
+}
+
+FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
+ : mapping (node->node_child ("Mapping"))
+{
+ name = node->string_child ("Name");
+ id = node->number_child<int> ("Id");
+ frame_rate = node->number_child<int> ("FrameRate");
+ channels = node->number_child<int64_t> ("Channels");
+ first_audio = node->optional_number_child<double> ("FirstAudio");
+}
+
+void
+FFmpegAudioStream::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("Name")->add_child_text (name);
+ root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+ root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
+ root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
+ if (first_audio) {
+ root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get ()));
+ }
+ mapping.as_xml (root->add_child("Mapping"));
+}
+
+/** Construct a SubtitleStream from a value returned from to_string().
+ * @param t String returned from to_string().
+ * @param v State file version.
+ */
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+{
+ name = node->string_child ("Name");
+ id = node->number_child<int> ("Id");
+}
+
+void
+FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("Name")->add_child_text (name);
+ root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+}
+
+Time
+FFmpegContent::full_length () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+ return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
+}
+
+AudioMapping
+FFmpegContent::audio_mapping () const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (!_audio_stream) {
+ return AudioMapping ();
+ }
+
+ return _audio_stream->mapping;
+}
+
+void
+FFmpegContent::set_filters (vector<Filter const *> const & filters)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _filters = filters;
+ }
+
+ signal_changed (FFmpegContentProperty::FILTERS);
+}
+
+void
+FFmpegContent::set_audio_mapping (AudioMapping m)
+{
+ audio_stream()->mapping = m;
+ signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
+
+string
+FFmpegContent::identifier () const
+{
+ stringstream s;
+
+ s << VideoContent::identifier();
+
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (_subtitle_stream) {
+ s << "_" << _subtitle_stream->id;
+ }
+
+ for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+ s << "_" << (*i)->id ();
+ }
+
+ return s.str ();
+}
+
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
new file mode 100644
index 000000000..775cb9220
--- /dev/null
+++ b/src/lib/ffmpeg_content.h
@@ -0,0 +1,169 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_FFMPEG_CONTENT_H
+#define DCPOMATIC_FFMPEG_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+#include "audio_content.h"
+#include "subtitle_content.h"
+#include "audio_mapping.h"
+
+class Filter;
+class ffmpeg_pts_offset_test;
+
+class FFmpegAudioStream
+{
+public:
+ FFmpegAudioStream (std::string n, int i, int f, int c)
+ : name (n)
+ , id (i)
+ , frame_rate (f)
+ , channels (c)
+ , mapping (c)
+ {
+ mapping.make_default ();
+ }
+
+ FFmpegAudioStream (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ std::string name;
+ int id;
+ int frame_rate;
+ int channels;
+ AudioMapping mapping;
+ boost::optional<double> first_audio;
+
+private:
+ friend class ffmpeg_pts_offset_test;
+
+ /* Constructor for tests */
+ FFmpegAudioStream ()
+ : mapping (1)
+ {}
+};
+
+extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
+
+class FFmpegSubtitleStream
+{
+public:
+ FFmpegSubtitleStream (std::string n, int i)
+ : name (n)
+ , id (i)
+ {}
+
+ FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ std::string name;
+ int id;
+};
+
+extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
+
+class FFmpegContentProperty : public VideoContentProperty
+{
+public:
+ static int const SUBTITLE_STREAMS;
+ static int const SUBTITLE_STREAM;
+ static int const AUDIO_STREAMS;
+ static int const AUDIO_STREAM;
+ static int const FILTERS;
+};
+
+class FFmpegContent : public VideoContent, public AudioContent, public SubtitleContent
+{
+public:
+ FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<FFmpegContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *) const;
+ Time full_length () const;
+
+ std::string identifier () const;
+
+ /* AudioContent */
+ int audio_channels () const;
+ AudioContent::Frame audio_length () const;
+ int content_audio_frame_rate () const;
+ int output_audio_frame_rate () const;
+ AudioMapping audio_mapping () const;
+ void set_audio_mapping (AudioMapping);
+
+ void set_filters (std::vector<Filter const *> const &);
+
+ std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _subtitle_streams;
+ }
+
+ boost::shared_ptr<FFmpegSubtitleStream> subtitle_stream () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _subtitle_stream;
+ }
+
+ std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_streams;
+ }
+
+ boost::shared_ptr<FFmpegAudioStream> audio_stream () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_stream;
+ }
+
+ std::vector<Filter const *> filters () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _filters;
+ }
+
+ void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
+ void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
+
+ boost::optional<double> first_video () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _first_video;
+ }
+
+private:
+ friend class ffmpeg_pts_offset_test;
+
+ std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
+ boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
+ std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
+ boost::shared_ptr<FFmpegAudioStream> _audio_stream;
+ boost::optional<double> _first_video;
+ /** Video filters that should be used when generating DCPs */
+ std::vector<Filter const *> _filters;
+};
+
+#endif
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index a19f26ad7..8da607e7e 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -28,355 +28,189 @@
#include <iostream>
#include <stdint.h>
#include <boost/lexical_cast.hpp>
+#include <sndfile.h>
extern "C" {
-#include <tiffio.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
-#include <libswscale/swscale.h>
-#include <libpostproc/postprocess.h>
}
-#include <sndfile.h>
#include "film.h"
-#include "format.h"
-#include "transcoder.h"
-#include "job.h"
#include "filter.h"
-#include "options.h"
#include "exceptions.h"
#include "image.h"
#include "util.h"
#include "log.h"
#include "ffmpeg_decoder.h"
#include "filter_graph.h"
-#include "subtitle.h"
+#include "audio_buffers.h"
+#include "ffmpeg_content.h"
+
+#include "i18n.h"
using std::cout;
using std::string;
using std::vector;
using std::stringstream;
using std::list;
+using std::min;
+using std::pair;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
- : Decoder (f, o, j)
- , VideoDecoder (f, o, j)
- , AudioDecoder (f, o, j)
- , _format_context (0)
- , _video_stream (-1)
- , _frame (0)
- , _video_codec_context (0)
- , _video_codec (0)
- , _audio_codec_context (0)
- , _audio_codec (0)
+using libdcp::Size;
+
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
+ : Decoder (f)
+ , VideoDecoder (f, c)
+ , AudioDecoder (f)
+ , SubtitleDecoder (f)
+ , FFmpeg (c)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
+ , _decode_video (video)
+ , _decode_audio (audio)
+ , _video_pts_offset (0)
+ , _audio_pts_offset (0)
+ , _just_sought (false)
{
- setup_general ();
- setup_video ();
- setup_audio ();
setup_subtitle ();
- if (!o->video_sync) {
- _first_video = 0;
- }
-}
+ /* Audio and video frame PTS values may not start with 0. We want
+ to fiddle them so that:
-FFmpegDecoder::~FFmpegDecoder ()
-{
- if (_audio_codec_context) {
- avcodec_close (_audio_codec_context);
- }
-
- if (_video_codec_context) {
- avcodec_close (_video_codec_context);
- }
+ 1. One of them starts at time 0.
+ 2. The first video PTS value ends up on a frame boundary.
- if (_subtitle_codec_context) {
- avcodec_close (_subtitle_codec_context);
- }
+ Then we remove big initial gaps in PTS and we allow our
+ insertion of black frames to work.
- av_free (_frame);
-
- avformat_close_input (&_format_context);
-}
+ We will do:
+ audio_pts_to_use = audio_pts_from_ffmpeg + audio_pts_offset;
+ video_pts_to_use = video_pts_from_ffmpeg + video_pts_offset;
+ */
-void
-FFmpegDecoder::setup_general ()
-{
- av_register_all ();
+ bool const have_video = video && c->first_video();
+ bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
- if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
- throw OpenFileError (_film->content_path ());
- }
+ /* First, make one of them start at 0 */
- if (avformat_find_stream_info (_format_context, 0) < 0) {
- throw DecodeError ("could not find stream information");
+ if (have_audio && have_video) {
+ _video_pts_offset = _audio_pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
+ } else if (have_video) {
+ _video_pts_offset = - c->first_video().get();
+ } else if (have_audio) {
+ _audio_pts_offset = - c->audio_stream()->first_audio.get();
}
- /* Find video, audio and subtitle streams and choose the first of each */
-
- for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
- AVStream* s = _format_context->streams[i];
- if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
- _video_stream = i;
- } else if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
-
- /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
- so bodge it here. No idea why we should have to do this.
- */
-
- if (s->codec->channel_layout == 0) {
- s->codec->channel_layout = av_get_default_channel_layout (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) {
- _subtitle_streams.push_back (
- shared_ptr<SubtitleStream> (
- new SubtitleStream (stream_name (s), i)
- )
- );
+ /* Now adjust both so that the video pts starts on a frame */
+ if (have_video && have_audio) {
+ double first_video = c->first_video().get() + _video_pts_offset;
+ double const old_first_video = first_video;
+
+ /* Round the first video up to a frame boundary */
+ if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
+ first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
}
- }
-
- if (_video_stream < 0) {
- throw DecodeError ("could not find video stream");
- }
- _frame = avcodec_alloc_frame ();
- if (_frame == 0) {
- throw DecodeError ("could not allocate frame");
+ _video_pts_offset += first_video - old_first_video;
+ _audio_pts_offset += first_video - old_first_video;
}
}
-void
-FFmpegDecoder::setup_video ()
+FFmpegDecoder::~FFmpegDecoder ()
{
- _video_codec_context = _format_context->streams[_video_stream]->codec;
- _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
-
- if (_video_codec == 0) {
- throw DecodeError ("could not find video decoder");
- }
+ boost::mutex::scoped_lock lm (_mutex);
- if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
- throw DecodeError ("could not open video decoder");
+ if (_subtitle_codec_context) {
+ avcodec_close (_subtitle_codec_context);
}
}
void
-FFmpegDecoder::setup_audio ()
+FFmpegDecoder::flush ()
{
- if (!_audio_stream) {
- return;
+ /* Get any remaining frames */
+
+ _packet.data = 0;
+ _packet.size = 0;
+
+ /* XXX: should we reset _packet.data and size after each *_decode_* call? */
+
+ if (_decode_video) {
+ while (decode_video_packet ()) {}
}
-
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
- _audio_codec_context = _format_context->streams[ffa->id()]->codec;
- _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
-
- if (_audio_codec == 0) {
- throw DecodeError ("could not find audio decoder");
+ if (_ffmpeg_content->audio_stream() && _decode_audio) {
+ decode_audio_packet ();
}
- if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
- throw DecodeError ("could not open audio decoder");
- }
+ /* Stop us being asked for any more data */
+ _video_position = _ffmpeg_content->video_length ();
+ _audio_position = _ffmpeg_content->audio_length ();
}
void
-FFmpegDecoder::setup_subtitle ()
-{
- if (!_subtitle_stream) {
- return;
- }
-
- _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
- _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
-
- if (_subtitle_codec == 0) {
- throw DecodeError ("could not find subtitle decoder");
- }
-
- if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
- throw DecodeError ("could not open subtitle decoder");
- }
-}
-
-
-bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
-
+
if (r < 0) {
if (r != AVERROR_EOF) {
/* Maybe we should fail here, but for now we'll just finish off instead */
char buf[256];
av_strerror (r, buf, sizeof(buf));
- _film->log()->log (String::compose ("error on av_read_frame (%1) (%2)", buf, r));
- }
-
- /* Get any remaining frames */
-
- _packet.data = 0;
- _packet.size = 0;
-
- /* XXX: should we reset _packet.data and size after each *_decode_* call? */
-
- int frame_finished;
-
- while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- filter_and_emit_video (_frame);
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
}
- 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());
- Audio (deinterleave_audio (_frame->data[0], data_size));
- }
- }
-
- return true;
+ flush ();
+ return;
}
avcodec_get_frame_defaults (_frame);
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
- if (_packet.stream_index == _video_stream) {
-
- int frame_finished;
- int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
- if (r >= 0 && frame_finished) {
-
- if (r != _packet.size) {
- _film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size));
- }
-
- if (_opt->video_sync) {
- out_with_sync ();
- } else {
- filter_and_emit_video (_frame);
- }
- }
-
- } 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) {
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
-
- /* We only decode audio if we've had our first video packet through, and if it
- was before this packet. Until then audio is thrown away.
- */
-
- if (_first_video && _first_video.get() <= source_pts_seconds) {
-
- if (!_first_audio) {
- _first_audio = source_pts_seconds;
-
- /* This is our first audio frame, and if we've arrived here we must have had our
- first video frame. Push some silence to make up any gap between our first
- video frame and our first audio.
- */
-
- /* frames of silence that we must push */
- int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ());
-
- _film->log()->log (
- String::compose (
- "First video at %1, first audio at %2, pushing %3 audio frames of silence for %4 channels (%5 bytes per sample)",
- _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample()
- )
- );
-
- if (s) {
- shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s));
- audio->make_silent ();
- Audio (audio);
- }
- }
-
- 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());
- Audio (deinterleave_audio (_frame->data[0], data_size));
- }
- }
-
- } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) {
-
- int got_subtitle;
- AVSubtitle sub;
- if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) && got_subtitle) {
- /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
- indicate that the previous subtitle should stop.
- */
- if (sub.num_rects > 0) {
- shared_ptr<TimedSubtitle> ts;
- try {
- emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
- } catch (...) {
- /* some problem with the subtitle; we probably didn't understand it */
- }
- } else {
- emit_subtitle (shared_ptr<TimedSubtitle> ());
- }
- avsubtitle_free (&sub);
- }
- }
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ if (_packet.stream_index == _video_stream && _decode_video) {
+ decode_video_packet ();
+ } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
+ decode_audio_packet ();
+ } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && film->with_subtitles ()) {
+ decode_subtitle_packet ();
+ }
+
av_free_packet (&_packet);
- return false;
}
+/** @param data pointer to array of pointers to buffers.
+ * Only the first buffer will be used for non-planar data, otherwise there will be one per channel.
+ */
shared_ptr<AudioBuffers>
-FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
+FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
{
- assert (_film->audio_channels());
+ assert (_ffmpeg_content->audio_channels());
assert (bytes_per_audio_sample());
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
/* Deinterleave and convert to float */
- assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
+ assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
int const total_samples = size / bytes_per_audio_sample();
- int const frames = total_samples / _film->audio_channels();
- shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
+ int const frames = total_samples / _ffmpeg_content->audio_channels();
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
switch (audio_sample_format()) {
case AV_SAMPLE_FMT_S16:
{
- int16_t* p = reinterpret_cast<int16_t *> (data);
+ int16_t* p = reinterpret_cast<int16_t *> (data[0]);
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()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -386,10 +220,10 @@ FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
case AV_SAMPLE_FMT_S16P:
{
- int16_t* p = reinterpret_cast<int16_t *> (data);
- for (int i = 0; i < _film->audio_channels(); ++i) {
+ int16_t** p = reinterpret_cast<int16_t **> (data);
+ for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
for (int j = 0; j < frames; ++j) {
- audio->data(i)[j] = static_cast<float>(*p++) / (1 << 15);
+ audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
}
}
}
@@ -397,14 +231,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
case AV_SAMPLE_FMT_S32:
{
- int32_t* p = reinterpret_cast<int32_t *> (data);
+ int32_t* p = reinterpret_cast<int32_t *> (data[0]);
int sample = 0;
int channel = 0;
for (int i = 0; i < total_samples; ++i) {
audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -414,14 +248,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
case AV_SAMPLE_FMT_FLT:
{
- float* p = reinterpret_cast<float*> (data);
+ float* p = reinterpret_cast<float*> (data[0]);
int sample = 0;
int channel = 0;
for (int i = 0; i < total_samples; ++i) {
audio->data(channel)[sample] = *p++;
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -431,309 +265,342 @@ FFmpegDecoder::deinterleave_audio (uint8_t* data, int size)
case AV_SAMPLE_FMT_FLTP:
{
- float* p = reinterpret_cast<float*> (data);
- for (int i = 0; i < _film->audio_channels(); ++i) {
- memcpy (audio->data(i), p, frames * sizeof(float));
- p += frames;
+ float** p = reinterpret_cast<float**> (data);
+ for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
+ memcpy (audio->data(i), p[i], frames * sizeof(float));
}
}
break;
default:
- throw DecodeError (String::compose ("Unrecognised audio sample format (%1)", static_cast<int> (audio_sample_format())));
+ throw DecodeError (String::compose (_("Unrecognised audio sample format (%1)"), static_cast<int> (audio_sample_format())));
}
return audio;
}
-float
-FFmpegDecoder::frames_per_second () const
-{
- 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 av_q2d (s->r_frame_rate);
-}
-
AVSampleFormat
FFmpegDecoder::audio_sample_format () const
{
- if (_audio_codec_context == 0) {
+ if (!_ffmpeg_content->audio_stream()) {
return (AVSampleFormat) 0;
}
- return _audio_codec_context->sample_fmt;
+ return audio_codec_context()->sample_fmt;
}
-Size
-FFmpegDecoder::native_size () const
+int
+FFmpegDecoder::bytes_per_audio_sample () const
{
- return Size (_video_codec_context->width, _video_codec_context->height);
+ return av_get_bytes_per_sample (audio_sample_format ());
}
-PixelFormat
-FFmpegDecoder::pixel_format () const
+void
+FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
{
- return _video_codec_context->pix_fmt;
-}
+ double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
-int
-FFmpegDecoder::time_base_numerator () const
-{
- return _video_codec_context->time_base.num;
-}
+ /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
+ a number plucked from the air) earlier than we want to end up. The loop below
+ will hopefully then step through to where we want to be.
+ */
+ int initial = frame;
-int
-FFmpegDecoder::time_base_denominator () const
-{
- return _video_codec_context->time_base.den;
-}
+ if (accurate) {
+ initial -= 5;
+ }
-int
-FFmpegDecoder::sample_aspect_ratio_numerator () const
-{
- return _video_codec_context->sample_aspect_ratio.num;
-}
+ if (initial < 0) {
+ initial = 0;
+ }
-int
-FFmpegDecoder::sample_aspect_ratio_denominator () const
-{
- return _video_codec_context->sample_aspect_ratio.den;
-}
+ /* Initial seek time in the stream's timebase */
+ int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base;
-string
-FFmpegDecoder::stream_name (AVStream* s) const
-{
- stringstream n;
-
- AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0);
- if (lang) {
- n << lang->value;
+ av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+
+ avcodec_flush_buffers (video_codec_context());
+ if (_subtitle_codec_context) {
+ avcodec_flush_buffers (_subtitle_codec_context);
}
+
+ _just_sought = true;
+ _video_position = frame;
- AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0);
- if (title) {
- if (!n.str().empty()) {
- n << " ";
- }
- n << title->value;
+ if (frame == 0 || !accurate) {
+ /* We're already there, or we're as close as we need to be */
+ return;
}
- if (n.str().empty()) {
- n << "unknown";
- }
+ while (1) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ return;
+ }
- return n.str ();
-}
+ if (_packet.stream_index != _video_stream) {
+ continue;
+ }
+
+ avcodec_get_frame_defaults (_frame);
+
+ int finished = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ _video_position = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate()
+ );
-int
-FFmpegDecoder::bytes_per_audio_sample () const
-{
- return av_get_bytes_per_sample (audio_sample_format ());
+ if (_video_position >= (frame - 1)) {
+ av_free_packet (&_packet);
+ break;
+ }
+ }
+
+ av_free_packet (&_packet);
+ }
}
void
-FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+FFmpegDecoder::decode_audio_packet ()
{
- AudioDecoder::set_audio_stream (s);
- setup_audio ();
-}
+ /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
+ several times.
+ */
+
+ AVPacket copy_packet = _packet;
+
+ while (copy_packet.size > 0) {
-void
-FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- VideoDecoder::set_subtitle_stream (s);
- setup_subtitle ();
- OutputChanged ();
+ int frame_finished;
+ int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+ if (decode_result < 0) {
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+ return;
+ }
+
+ if (frame_finished) {
+
+ if (_audio_position == 0) {
+ /* Where we are in the source, in seconds */
+ double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset;
+
+ if (pts > 0) {
+ /* Emit some silence */
+ shared_ptr<AudioBuffers> silence (
+ new AudioBuffers (
+ _ffmpeg_content->audio_channels(),
+ pts * _ffmpeg_content->content_audio_frame_rate()
+ )
+ );
+
+ silence->make_silent ();
+ audio (silence, _audio_position);
+ }
+ }
+
+ int const data_size = av_samples_get_buffer_size (
+ 0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
+ );
+
+ audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+ }
+
+ copy_packet.data += decode_result;
+ copy_packet.size -= decode_result;
+ }
}
-void
-FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
+bool
+FFmpegDecoder::decode_video_packet ()
{
+ int frame_finished;
+ if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
+ return false;
+ }
+
boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-
- shared_ptr<FilterGraph> graph;
+ 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)) {
+ while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
++i;
}
if (i == _filter_graphs.end ()) {
- graph.reset (new FilterGraph (_film, this, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ graph.reset (new FilterGraph (_ffmpeg_content, libdcp::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));
+
+ film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
} else {
graph = *i;
}
- list<shared_ptr<Image> > images = graph->process (frame);
-
- for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
- emit_video (*i, frame_time ());
- }
-}
-
-bool
-FFmpegDecoder::seek (double p)
-{
- return do_seek (p, false);
-}
-
-bool
-FFmpegDecoder::seek_to_last ()
-{
- /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
- (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
- staying in the same place.
- */
- return do_seek (last_source_time(), true);
-}
+ list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame);
-bool
-FFmpegDecoder::do_seek (double p, bool backwards)
-{
- int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
-
- int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
+ string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
- avcodec_flush_buffers (_video_codec_context);
- if (_subtitle_codec_context) {
- avcodec_flush_buffers (_subtitle_codec_context);
- }
-
- return r < 0;
-}
+ for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++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));
- }
+ shared_ptr<Image> image = i->first;
+ if (!post_process.empty ()) {
+ image = image->post_process (post_process, true);
+ }
+
+ if (i->second != AV_NOPTS_VALUE) {
- stringstream s (t);
- string type;
- s >> type;
- if (type != "ffmpeg") {
- return shared_ptr<FFmpegAudioStream> ();
- }
+ double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset;
- return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-}
+ if (_just_sought) {
+ /* We just did a seek, so disable any attempts to correct for where we
+ are / should be.
+ */
+ _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
+ _just_sought = false;
+ }
-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");
- }
+ double const next = _video_position / _ffmpeg_content->video_frame_rate();
+ double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
+ double delta = pts - next;
+
+ while (delta > one_frame) {
+ /* This PTS is more than one frame forward in time of where we think we should be; emit
+ a black frame.
+ */
+
+ /* XXX: I think this should be a copy of the last frame... */
+ boost::shared_ptr<Image> black (
+ new Image (
+ static_cast<AVPixelFormat> (_frame->format),
+ libdcp::Size (video_codec_context()->width, video_codec_context()->height),
+ true
+ )
+ );
+
+ black->make_black ();
+ video (image, false, _video_position);
+ delta -= one_frame;
+ }
- for (int i = 0; i < name_index; ++i) {
- size_t const s = t.find (' ');
- if (s != string::npos) {
- t = t.substr (s + 1);
+ if (delta > -one_frame) {
+ /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
+ video (image, false, _video_position);
+ }
+
+ } else {
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log ("Dropping frame without PTS");
}
}
- _name = t;
-}
-
-string
-FFmpegAudioStream::to_string () const
-{
- return String::compose ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name);
+ return true;
}
+
void
-FFmpegDecoder::out_with_sync ()
+FFmpegDecoder::setup_subtitle ()
{
- /* Where we are in the output, in seconds */
- double const out_pts_seconds = video_frame() / frames_per_second();
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
+ boost::mutex::scoped_lock lm (_mutex);
- _film->log()->log (
- String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds),
- Log::VERBOSE
- );
-
- if (!_first_video) {
- _first_video = source_pts_seconds;
+ if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
+ return;
}
-
- /* Difference between where we are and where we should be */
- double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
- double const one_frame = 1 / frames_per_second();
-
- /* Insert frames if required to get out_pts_seconds up to pts_seconds */
- if (delta > one_frame) {
- int const extra = rint (delta / one_frame);
- for (int i = 0; i < extra; ++i) {
- repeat_last_video ();
- _film->log()->log (
- String::compose (
- "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)",
- out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
- )
- );
- }
+
+ _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
+ _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
+
+ if (_subtitle_codec == 0) {
+ throw DecodeError (_("could not find subtitle decoder"));
}
- if (delta > -one_frame) {
- /* Process this frame */
- filter_and_emit_video (_frame);
- } else {
- /* Otherwise we are omitting a frame to keep things right */
- _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
+ if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
+ throw DecodeError (N_("could not open subtitle decoder"));
}
}
+bool
+FFmpegDecoder::done () const
+{
+ bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
+ bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
+ return vd && ad;
+}
+
void
-FFmpegDecoder::film_changed (Film::Property p)
+FFmpegDecoder::decode_subtitle_packet ()
{
- switch (p) {
- case Film::CROP:
- case Film::FILTERS:
- {
- boost::mutex::scoped_lock lm (_filter_graphs_mutex);
- _filter_graphs.clear ();
+ int got_subtitle;
+ AVSubtitle sub;
+ if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
+ return;
}
- OutputChanged ();
- break;
- default:
- break;
+ /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
+ indicate that the previous subtitle should stop.
+ */
+ if (sub.num_rects <= 0) {
+ subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+ return;
+ } else if (sub.num_rects > 1) {
+ throw DecodeError (_("multi-part subtitles not yet supported"));
}
-}
+
+ /* Subtitle PTS in seconds (within the source, not taking into account any of the
+ source that we may have chopped off for the DCP)
+ */
+ double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
+
+ /* hence start time for this sub */
+ Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+ Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
-/** @return Length (in video frames) according to our content's header */
-SourceFrame
-FFmpegDecoder::length () const
-{
- return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
-}
+ AVSubtitleRect const * rect = sub.rects[0];
-double
-FFmpegDecoder::frame_time () const
-{
- return av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base);
+ if (rect->type != SUBTITLE_BITMAP) {
+ throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ }
+
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+
+ /* Start of the first line in the subtitle */
+ uint8_t* sub_p = rect->pict.data[0];
+ /* sub_p looks up into a RGB palette which is here */
+ uint32_t const * palette = (uint32_t *) rect->pict.data[1];
+ /* Start of the output data */
+ uint32_t* out_p = (uint32_t *) image->data()[0];
+
+ for (int y = 0; y < rect->h; ++y) {
+ uint8_t* sub_line_p = sub_p;
+ uint32_t* out_line_p = out_p;
+ for (int x = 0; x < rect->w; ++x) {
+ *out_line_p++ = palette[*sub_line_p++];
+ }
+ sub_p += rect->pict.linesize[0];
+ out_p += image->stride()[0] / sizeof (uint32_t);
+ }
+
+ libdcp::Size const vs = _ffmpeg_content->video_size ();
+
+ subtitle (
+ image,
+ dcpomatic::Rect<double> (
+ static_cast<double> (rect->x) / vs.width,
+ static_cast<double> (rect->y) / vs.height,
+ static_cast<double> (rect->w) / vs.width,
+ static_cast<double> (rect->h) / vs.height
+ ),
+ from,
+ to
+ );
+
+
+ avsubtitle_free (&sub);
}
-
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 2fb8675f9..11f83ed97 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -35,115 +35,55 @@ extern "C" {
#include "decoder.h"
#include "video_decoder.h"
#include "audio_decoder.h"
-#include "film.h"
-
-struct AVFilterGraph;
-struct AVCodecContext;
-struct AVFilterContext;
-struct AVFormatContext;
-struct AVFrame;
-struct AVBufferContext;
-struct AVCodec;
-struct AVStream;
-class Job;
-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;
- }
+#include "subtitle_decoder.h"
+#include "ffmpeg.h"
- 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 Film;
+class FilterGraph;
+class ffmpeg_pts_offset_test;
/** @class FFmpegDecoder
* @brief A decoder using FFmpeg to decode content.
*/
-class FFmpegDecoder : public VideoDecoder, public AudioDecoder
+class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg
{
public:
- FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+ FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
~FFmpegDecoder ();
- float frames_per_second () const;
- Size native_size () const;
- SourceFrame length () const;
- int time_base_numerator () const;
- int time_base_denominator () const;
- int sample_aspect_ratio_numerator () const;
- int sample_aspect_ratio_denominator () const;
-
- void set_audio_stream (boost::shared_ptr<AudioStream>);
- void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
-
- bool seek (double);
- bool seek_to_last ();
+ void pass ();
+ void seek (VideoContent::Frame, bool);
+ bool done () const;
private:
+ friend class ::ffmpeg_pts_offset_test;
- bool pass ();
- bool do_seek (double p, bool);
- PixelFormat pixel_format () const;
- AVSampleFormat audio_sample_format () const;
- int bytes_per_audio_sample () const;
+ static double compute_pts_offset (double, double, float);
- void out_with_sync ();
- void filter_and_emit_video (AVFrame *);
- double frame_time () const;
+ void flush ();
- void setup_general ();
- void setup_video ();
- void setup_audio ();
void setup_subtitle ();
- void maybe_add_subtitle ();
- boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size);
-
- void film_changed (Film::Property);
+ AVSampleFormat audio_sample_format () const;
+ int bytes_per_audio_sample () const;
- std::string stream_name (AVStream* s) const;
+ bool decode_video_packet ();
+ void decode_audio_packet ();
+ void decode_subtitle_packet ();
- AVFormatContext* _format_context;
- int _video_stream;
-
- AVFrame* _frame;
+ void maybe_add_subtitle ();
+ boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
- AVCodecContext* _video_codec_context;
- AVCodec* _video_codec;
- AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio
- AVCodec* _audio_codec; ///< may be 0 if there is no audio
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
- AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
-
- AVPacket _packet;
-
- boost::optional<double> _first_video;
- boost::optional<double> _first_audio;
-
+ AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
+
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
boost::mutex _filter_graphs_mutex;
+
+ bool _decode_video;
+ bool _decode_audio;
+
+ double _video_pts_offset;
+ double _audio_pts_offset;
+ bool _just_sought;
};
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
new file mode 100644
index 000000000..ceeaee112
--- /dev/null
+++ b/src/lib/ffmpeg_examiner.cc
@@ -0,0 +1,166 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+}
+#include "ffmpeg_examiner.h"
+#include "ffmpeg_content.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+using boost::optional;
+
+FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
+ : FFmpeg (c)
+{
+ /* Find audio and subtitle streams */
+
+ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+ AVStream* s = _format_context->streams[i];
+ if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+
+ /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
+ so bodge it here. No idea why we should have to do this.
+ */
+
+ if (s->codec->channel_layout == 0) {
+ s->codec->channel_layout = av_get_default_channel_layout (s->codec->channels);
+ }
+
+ _audio_streams.push_back (
+ shared_ptr<FFmpegAudioStream> (
+ new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channels)
+ )
+ );
+
+ } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
+ _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (stream_name (s), i)));
+ }
+ }
+
+ /* Run through until we find the first audio (for each stream) and video */
+
+ while (1) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ break;
+ }
+
+ int frame_finished;
+ avcodec_get_frame_defaults (_frame);
+
+ AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec;
+
+ if (_packet.stream_index == _video_stream && !_first_video) {
+ if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ _first_video = frame_time (_video_stream);
+ }
+ } else {
+ for (size_t i = 0; i < _audio_streams.size(); ++i) {
+ if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->first_audio) {
+ if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->id);
+ }
+ }
+ }
+ }
+
+ bool have_all_audio = true;
+ size_t i = 0;
+ while (i < _audio_streams.size() && have_all_audio) {
+ have_all_audio = _audio_streams[i]->first_audio;
+ ++i;
+ }
+
+ av_free_packet (&_packet);
+
+ if (_first_video && have_all_audio) {
+ break;
+ }
+ }
+}
+
+optional<double>
+FFmpegExaminer::frame_time (int stream) const
+{
+ optional<double> t;
+
+ int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+ if (bet != AV_NOPTS_VALUE) {
+ t = bet * av_q2d (_format_context->streams[stream]->time_base);
+ }
+
+ return t;
+}
+
+float
+FFmpegExaminer::video_frame_rate () const
+{
+ 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 av_q2d (s->r_frame_rate);
+}
+
+libdcp::Size
+FFmpegExaminer::video_size () const
+{
+ return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+}
+
+/** @return Length (in video frames) according to our content's header */
+VideoContent::Frame
+FFmpegExaminer::video_length () const
+{
+ return (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
+}
+
+string
+FFmpegExaminer::stream_name (AVStream* s) const
+{
+ stringstream n;
+
+ if (s->metadata) {
+ AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0);
+ if (lang) {
+ n << lang->value;
+ }
+
+ AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0);
+ if (title) {
+ if (!n.str().empty()) {
+ n << " ";
+ }
+ n << title->value;
+ }
+ }
+
+ if (n.str().empty()) {
+ n << "unknown";
+ }
+
+ return n.str ();
+}
diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h
new file mode 100644
index 000000000..4912d899a
--- /dev/null
+++ b/src/lib/ffmpeg_examiner.h
@@ -0,0 +1,55 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/optional.hpp>
+#include "ffmpeg.h"
+#include "video_examiner.h"
+
+class FFmpegAudioStream;
+class FFmpegSubtitleStream;
+
+class FFmpegExaminer : public FFmpeg, public VideoExaminer
+{
+public:
+ FFmpegExaminer (boost::shared_ptr<const FFmpegContent>);
+
+ float video_frame_rate () const;
+ libdcp::Size video_size () const;
+ VideoContent::Frame video_length () const;
+
+ std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
+ return _subtitle_streams;
+ }
+
+ std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
+ return _audio_streams;
+ }
+
+ boost::optional<double> first_video () const {
+ return _first_video;
+ }
+
+private:
+ std::string stream_name (AVStream* s) const;
+ boost::optional<double> frame_time (int) const;
+
+ std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
+ std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
+ boost::optional<double> _first_video;
+};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index fb3423bb4..e885fe5fd 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -30,33 +30,30 @@
#include <boost/lexical_cast.hpp>
#include <boost/date_time.hpp>
#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
#include <libdcp/crypt_chain.h>
-#include <libdcp/certificates.h>
-#include "cinema.h"
+#include <libdcp/cpl.h>
#include "film.h"
-#include "format.h"
#include "job.h"
-#include "filter.h"
-#include "transcoder.h"
#include "util.h"
#include "job_manager.h"
-#include "ab_transcode_job.h"
#include "transcode_job.h"
#include "scp_dcp_job.h"
-#include "make_dcp_job.h"
#include "log.h"
-#include "options.h"
#include "exceptions.h"
#include "examine_content_job.h"
#include "scaler.h"
-#include "decoder_factory.h"
#include "config.h"
-#include "check_hashes_job.h"
#include "version.h"
#include "ui_signaller.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "external_audio_decoder.h"
+#include "playlist.h"
+#include "player.h"
+#include "dcp_content_type.h"
+#include "ratio.h"
+#include "cross.h"
+#include "cinema.h"
+
+#include "i18n.h"
using std::string;
using std::stringstream;
@@ -69,52 +66,54 @@ using std::ofstream;
using std::setfill;
using std::min;
using std::make_pair;
-using std::list;
+using std::endl;
using std::cout;
+using std::list;
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
using boost::to_upper_copy;
using boost::ends_with;
using boost::starts_with;
using boost::optional;
+using libdcp::Size;
-int const Film::state_version = 1;
+int const Film::state_version = 4;
-/** Construct a Film object in a given directory, reading any metadata
- * file that exists in that directory. An exception will be thrown if
- * must_exist is true and the specified directory does not exist.
+/** Construct a Film object in a given directory.
*
- * @param d Film directory.
- * @param must_exist true to throw an exception if does not exist.
+ * @param dir Film directory.
*/
-Film::Film (string d, bool must_exist)
- : _use_dci_name (true)
- , _trust_content_header (true)
- , _dcp_content_type (0)
- , _format (0)
+Film::Film (boost::filesystem::path dir)
+ : _playlist (new Playlist)
+ , _use_dci_name (true)
+ , _dcp_content_type (Config::instance()->default_dcp_content_type ())
+ , _container (Config::instance()->default_container ())
+ , _resolution (RESOLUTION_2K)
, _scaler (Scaler::from_id ("bicubic"))
- , _dcp_trim_start (0)
- , _dcp_trim_end (0)
- , _dcp_ab (false)
- , _use_content_audio (true)
- , _audio_gain (0)
- , _audio_delay (0)
- , _still_duration (10)
, _with_subtitles (false)
- , _subtitle_offset (0)
- , _subtitle_scale (1)
, _encrypted (false)
- , _colour_lut (0)
- , _j2k_bandwidth (200000000)
- , _frames_per_second (0)
+ , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
+ , _dci_metadata (Config::instance()->default_dci_metadata ())
+ , _video_frame_rate (24)
+ , _audio_channels (MAX_AUDIO_CHANNELS)
+ , _three_d (false)
+ , _sequence_video (true)
+ , _interop (false)
, _dirty (false)
{
+ set_dci_date_today ();
+
+ _playlist->Changed.connect (bind (&Film::playlist_changed, this));
+ _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
+
/* Make state.directory a complete path without ..s (where possible)
(Code swiped from Adam Bowen on stackoverflow)
*/
- boost::filesystem::path p (boost::filesystem::system_complete (d));
+ boost::filesystem::path p (boost::filesystem::system_complete (dir));
boost::filesystem::path result;
for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
if (*i == "..") {
@@ -129,239 +128,161 @@ Film::Film (string d, bool must_exist)
}
set_directory (result.string ());
-
- if (!boost::filesystem::exists (directory())) {
- if (must_exist) {
- throw OpenFileError (directory());
- } else {
- boost::filesystem::create_directory (directory());
- }
- }
+ _log.reset (new FileLog (file ("log")));
- _external_audio_stream = ExternalAudioStream::create ();
-
- if (must_exist) {
- read_metadata ();
- }
-
- _log = new FileLog (file ("log"));
- set_dci_date_today ();
+ _playlist->set_sequence_video (_sequence_video);
}
-Film::Film (Film const & o)
- : boost::enable_shared_from_this<Film> (o)
- , _log (0)
- , _directory (o._directory)
- , _name (o._name)
- , _use_dci_name (o._use_dci_name)
- , _content (o._content)
- , _trust_content_header (o._trust_content_header)
- , _dcp_content_type (o._dcp_content_type)
- , _format (o._format)
- , _crop (o._crop)
- , _filters (o._filters)
- , _scaler (o._scaler)
- , _dcp_trim_start (o._dcp_trim_start)
- , _dcp_trim_end (o._dcp_trim_end)
- , _reel_size (o._reel_size)
- , _dcp_ab (o._dcp_ab)
- , _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)
- , _subtitle_stream (o._subtitle_stream)
- , _with_subtitles (o._with_subtitles)
- , _subtitle_offset (o._subtitle_offset)
- , _subtitle_scale (o._subtitle_scale)
- , _encrypted (o._encrypted)
- , _colour_lut (o._colour_lut)
- , _j2k_bandwidth (o._j2k_bandwidth)
- , _audio_language (o._audio_language)
- , _subtitle_language (o._subtitle_language)
- , _territory (o._territory)
- , _rating (o._rating)
- , _studio (o._studio)
- , _facility (o._facility)
- , _package_type (o._package_type)
- , _size (o._size)
- , _length (o._length)
- , _content_digest (o._content_digest)
- , _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)
+string
+Film::video_identifier () const
{
+ assert (container ());
+ LocaleGuard lg;
-}
+ stringstream s;
+ s << container()->id()
+ << "_" << resolution_to_string (_resolution)
+ << "_" << _playlist->video_identifier()
+ << "_" << _video_frame_rate
+ << "_" << scaler()->id()
+ << "_" << j2k_bandwidth();
-Film::~Film ()
-{
- delete _log;
+ if (_interop) {
+ s << "_I";
+ } else {
+ s << "_S";
+ }
+
+ if (_three_d) {
+ s << "_3D";
+ }
+
+ return s.str ();
}
-/** @return The path to the directory to write JPEG2000 files to */
+/** @return The path to the directory to write video frame info files to */
string
-Film::j2k_dir () const
+Film::info_dir () const
{
- assert (format());
-
boost::filesystem::path p;
+ p /= "info";
+ p /= video_identifier ();
+ return dir (p.string());
+}
- /* Start with j2c */
- p /= "j2c";
+string
+Film::internal_video_mxf_dir () const
+{
+ return dir ("video");
+}
- pair<string, string> f = Filter::ffmpeg_strings (filters());
+string
+Film::internal_video_mxf_filename () const
+{
+ return video_identifier() + ".mxf";
+}
- /* Write stuff to specify the filter / post-processing settings that are in use,
- so that we don't get confused about J2K files generated using different
- settings.
- */
- stringstream s;
- s << format()->id()
- << "_" << content_digest()
- << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
- << "_" << f.first << "_" << f.second
- << "_" << scaler()->id()
- << "_" << j2k_bandwidth()
- << "_" << boost::lexical_cast<int> (colour_lut());
+string
+Film::video_mxf_filename () const
+{
+ return filename_safe_name() + "_video.mxf";
+}
- p /= s.str ();
+string
+Film::audio_mxf_filename () const
+{
+ return filename_safe_name() + "_audio.mxf";
+}
- /* Similarly for the A/B case */
- if (dcp_ab()) {
- stringstream s;
- pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
- s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
- p /= s.str ();
+string
+Film::filename_safe_name () const
+{
+ string const n = name ();
+ string o;
+ for (size_t i = 0; i < n.length(); ++i) {
+ if (isalnum (n[i])) {
+ o += n[i];
+ } else {
+ o += "_";
+ }
}
-
- return dir (p.string());
+
+ return o;
}
-/** Add suitable Jobs to the JobManager to create a DCP for this Film.
- * @param true to transcode, false to use the WAV and J2K files that are already there.
- */
+boost::filesystem::path
+Film::audio_analysis_path (shared_ptr<const AudioContent> c) const
+{
+ boost::filesystem::path p = dir ("analysis");
+ p /= c->digest();
+ return p;
+}
+
+/** Add suitable Jobs to the JobManager to create a DCP for this Film */
void
-Film::make_dcp (bool transcode)
+Film::make_dcp ()
{
set_dci_date_today ();
if (dcp_name().find ("/") != string::npos) {
- throw BadSettingError ("name", "cannot contain slashes");
+ throw BadSettingError (_("name"), _("cannot contain slashes"));
}
- log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
+ log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
{
char buffer[128];
gethostname (buffer, sizeof (buffer));
log()->log (String::compose ("Starting to make DCP on %1", buffer));
}
-
- log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video")));
- log()->log (String::compose ("Content length %1", length().get()));
- log()->log (String::compose ("Content digest %1", content_digest()));
+
+ ContentList cl = content ();
+ for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
+ log()->log (String::compose ("Content: %1", (*i)->technical_summary()));
+ }
+ log()->log (String::compose ("DCP video rate %1 fps", video_frame_rate()));
log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
-#ifdef DVDOMATIC_DEBUG
- log()->log ("DVD-o-matic built in debug mode.");
+#ifdef DCPOMATIC_DEBUG
+ log()->log ("DCP-o-matic built in debug mode.");
#else
- log()->log ("DVD-o-matic built in optimised mode.");
+ log()->log ("DCP-o-matic built in optimised mode.");
#endif
#ifdef LIBDCP_DEBUG
log()->log ("libdcp built in debug mode.");
#else
log()->log ("libdcp built in optimised mode.");
#endif
- pair<string, int> const c = cpu_info ();
- log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second));
+ log()->log (String::compose ("CPU: %1, %2 processors", cpu_info(), boost::thread::hardware_concurrency ()));
+ list<pair<string, string> > const m = mount_info ();
+ for (list<pair<string, string> >::const_iterator i = m.begin(); i != m.end(); ++i) {
+ log()->log (String::compose ("Mount: %1 %2", i->first, i->second));
+ }
- if (format() == 0) {
- throw MissingSettingError ("format");
+ if (container() == 0) {
+ throw MissingSettingError (_("container"));
}
- if (content().empty ()) {
- throw MissingSettingError ("content");
+ if (content().empty()) {
+ throw StringError (_("You must add some content to the DCP before creating it"));
}
if (dcp_content_type() == 0) {
- throw MissingSettingError ("content type");
+ throw MissingSettingError (_("content type"));
}
if (name().empty()) {
- throw MissingSettingError ("name");
- }
-
- shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
- oe->out_size = format()->dcp_size ();
- oe->padding = format()->dcp_padding (shared_from_this ());
- if (dcp_length ()) {
- oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
- if (audio_stream()) {
- oe->audio_range = make_pair (
-
- video_frames_to_audio_frames (
- oe->video_range.get().first,
- dcp_audio_sample_rate (audio_stream()->sample_rate()),
- dcp_frame_rate (frames_per_second()).frames_per_second
- ),
-
- video_frames_to_audio_frames (
- oe->video_range.get().second,
- dcp_audio_sample_rate (audio_stream()->sample_rate()),
- dcp_frame_rate (frames_per_second()).frames_per_second
- )
- );
- }
-
- }
-
- oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
-
- shared_ptr<DecodeOptions> od (new DecodeOptions);
- od->decode_subtitles = with_subtitles ();
-
- shared_ptr<Job> r;
-
- if (transcode) {
- if (dcp_ab()) {
- r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
- } else {
- r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
- }
+ throw MissingSettingError (_("name"));
}
- r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
- JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
-}
-
-/** Start a job to examine our content file */
-void
-Film::examine_content ()
-{
- if (_examine_content_job) {
- return;
- }
-
- _examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr<Job> ()));
- _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
- JobManager::instance()->add (_examine_content_job);
-}
-
-void
-Film::examine_content_finished ()
-{
- _examine_content_job.reset ();
+ JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
}
/** Start a job to send our DCP to the configured TMS */
void
Film::send_dcp_to_tms ()
{
- shared_ptr<Job> j (new SCPDCPJob (shared_from_this(), shared_ptr<Job> ()));
+ shared_ptr<Job> j (new SCPDCPJob (shared_from_this()));
JobManager::instance()->add (j);
}
@@ -371,12 +292,12 @@ Film::send_dcp_to_tms ()
int
Film::encoded_frames () const
{
- if (format() == 0) {
+ if (container() == 0) {
return 0;
}
int N = 0;
- for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
+ for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
++N;
boost::this_thread::interruption_point ();
}
@@ -388,86 +309,44 @@ Film::encoded_frames () const
void
Film::write_metadata () const
{
- boost::mutex::scoped_lock lm (_state_mutex);
+ if (!boost::filesystem::exists (directory())) {
+ boost::filesystem::create_directory (directory());
+ }
+
+ LocaleGuard lg;
boost::filesystem::create_directories (directory());
- string const m = file ("metadata");
- ofstream f (m.c_str ());
- if (!f.good ()) {
- throw CreateFileError (m);
- }
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("Metadata");
- f << "version " << state_version << "\n";
+ root->add_child("Version")->add_child_text (lexical_cast<string> (state_version));
+ root->add_child("Name")->add_child_text (_name);
+ root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
- /* User stuff */
- f << "name " << _name << "\n";
- f << "use_dci_name " << _use_dci_name << "\n";
- f << "content " << _content << "\n";
- f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n";
if (_dcp_content_type) {
- f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
- }
- if (_format) {
- f << "format " << _format->as_metadata () << "\n";
- }
- f << "left_crop " << _crop.left << "\n";
- f << "right_crop " << _crop.right << "\n";
- f << "top_crop " << _crop.top << "\n";
- f << "bottom_crop " << _crop.bottom << "\n";
- for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
- f << "filter " << (*i)->id () << "\n";
- }
- f << "scaler " << _scaler->id () << "\n";
- f << "dcp_trim_start " << _dcp_trim_start << "\n";
- f << "dcp_trim_end " << _dcp_trim_end << "\n";
- if (_reel_size) {
- f << "reel_size " << _reel_size.get() << "\n";
- }
- f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\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";
- if (_subtitle_stream) {
- f << "selected_subtitle_stream " << _subtitle_stream->to_string() << "\n";
+ root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
}
- f << "with_subtitles " << _with_subtitles << "\n";
- f << "subtitle_offset " << _subtitle_offset << "\n";
- f << "subtitle_scale " << _subtitle_scale << "\n";
- f << "encrypted " << _encrypted << "\n";
- f << "colour_lut " << _colour_lut << "\n";
- f << "j2k_bandwidth " << _j2k_bandwidth << "\n";
- f << "audio_language " << _audio_language << "\n";
- f << "subtitle_language " << _subtitle_language << "\n";
- f << "territory " << _territory << "\n";
- f << "rating " << _rating << "\n";
- f << "studio " << _studio << "\n";
- f << "facility " << _facility << "\n";
- f << "package_type " << _package_type << "\n";
-
- f << "width " << _size.width << "\n";
- f << "height " << _size.height << "\n";
- f << "length " << _length.get_value_or(0) << "\n";
- f << "content_digest " << _content_digest << "\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";
- }
-
- 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";
+ if (_container) {
+ root->add_child("Container")->add_child_text (_container->id ());
}
- f << "frames_per_second " << _frames_per_second << "\n";
+ root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
+ root->add_child("Scaler")->add_child_text (_scaler->id ());
+ root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
+ root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
+ _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+ root->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
+ root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+ root->add_child("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels));
+ root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
+ root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0");
+ root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
+ root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
+ _playlist->as_xml (root->add_child ("Playlist"));
+
+ doc.write_to_file_formatted (file ("metadata.xml"));
_dirty = false;
}
@@ -476,273 +355,82 @@ Film::write_metadata () const
void
Film::read_metadata ()
{
- boost::mutex::scoped_lock lm (_state_mutex);
-
- _external_audio.clear ();
- _content_audio_streams.clear ();
- _subtitle_streams.clear ();
-
- boost::optional<int> version;
-
- /* Backward compatibility things */
- boost::optional<int> audio_sample_rate;
- boost::optional<int> audio_stream_index;
- boost::optional<int> subtitle_stream_index;
+ LocaleGuard lg;
- ifstream f (file ("metadata").c_str());
- if (!f.good()) {
- throw OpenFileError (file("metadata"));
+ if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+ throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!"));
}
-
- multimap<string, string> kv = read_key_value (f);
- /* We need version before anything else */
- multimap<string, string>::iterator v = kv.find ("version");
- if (v != kv.end ()) {
- version = atoi (v->second.c_str());
- }
+ cxml::Document f ("Metadata");
+ f.read_file (file ("metadata.xml"));
- for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
- string const k = i->first;
- string const v = i->second;
+ _name = f.string_child ("Name");
+ _use_dci_name = f.bool_child ("UseDCIName");
- if (k == "audio_sample_rate") {
- audio_sample_rate = atoi (v.c_str());
+ {
+ optional<string> c = f.optional_string_child ("DCPContentType");
+ if (c) {
+ _dcp_content_type = DCPContentType::from_dci_name (c.get ());
}
+ }
- /* User-specified stuff */
- if (k == "name") {
- _name = v;
- } else if (k == "use_dci_name") {
- _use_dci_name = (v == "1");
- } else if (k == "content") {
- _content = v;
- } else if (k == "trust_content_header") {
- _trust_content_header = (v == "1");
- } else if (k == "dcp_content_type") {
- _dcp_content_type = DCPContentType::from_pretty_name (v);
- } else if (k == "format") {
- _format = Format::from_metadata (v);
- } else if (k == "left_crop") {
- _crop.left = atoi (v.c_str ());
- } else if (k == "right_crop") {
- _crop.right = atoi (v.c_str ());
- } else if (k == "top_crop") {
- _crop.top = atoi (v.c_str ());
- } else if (k == "bottom_crop") {
- _crop.bottom = atoi (v.c_str ());
- } else if (k == "filter") {
- _filters.push_back (Filter::from_id (v));
- } else if (k == "scaler") {
- _scaler = Scaler::from_id (v);
- } else if (k == "dcp_trim_start") {
- _dcp_trim_start = atoi (v.c_str ());
- } else if (k == "dcp_trim_end") {
- _dcp_trim_end = atoi (v.c_str ());
- } else if (k == "reel_size") {
- _reel_size = boost::lexical_cast<uint64_t> (v);
- } else if (k == "dcp_ab") {
- _dcp_ab = (v == "1");
- } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
- if (!version) {
- audio_stream_index = atoi (v.c_str ());
- } else {
- _content_audio_stream = audio_stream_factory (v, version);
- }
- } else if (k == "external_audio") {
- _external_audio.push_back (v);
- } else if (k == "use_content_audio") {
- _use_content_audio = (v == "1");
- } else if (k == "audio_gain") {
- _audio_gain = atof (v.c_str ());
- } else if (k == "audio_delay") {
- _audio_delay = atoi (v.c_str ());
- } else if (k == "still_duration") {
- _still_duration = atoi (v.c_str ());
- } else if (k == "selected_subtitle_stream") {
- if (!version) {
- subtitle_stream_index = atoi (v.c_str ());
- } else {
- _subtitle_stream = subtitle_stream_factory (v, version);
- }
- } else if (k == "with_subtitles") {
- _with_subtitles = (v == "1");
- } else if (k == "subtitle_offset") {
- _subtitle_offset = atoi (v.c_str ());
- } else if (k == "subtitle_scale") {
- _subtitle_scale = atof (v.c_str ());
- } else if (k == "encrypted") {
- _encrypted = (v == "1");
- } else if (k == "colour_lut") {
- _colour_lut = atoi (v.c_str ());
- } else if (k == "j2k_bandwidth") {
- _j2k_bandwidth = atoi (v.c_str ());
- } else if (k == "audio_language") {
- _audio_language = v;
- } else if (k == "subtitle_language") {
- _subtitle_language = v;
- } else if (k == "territory") {
- _territory = v;
- } else if (k == "rating") {
- _rating = v;
- } else if (k == "studio") {
- _studio = v;
- } else if (k == "facility") {
- _facility = v;
- } else if (k == "package_type") {
- _package_type = v;
- }
-
- /* Cached stuff */
- if (k == "width") {
- _size.width = atoi (v.c_str ());
- } else if (k == "height") {
- _size.height = atoi (v.c_str ());
- } else if (k == "length") {
- int const vv = atoi (v.c_str ());
- if (vv) {
- _length = vv;
- }
- } else if (k == "content_digest") {
- _content_digest = v;
- } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
- _content_audio_streams.push_back (audio_stream_factory (v, version));
- } else if (k == "external_audio_stream") {
- _external_audio_stream = audio_stream_factory (v, version);
- } else if (k == "subtitle_stream") {
- _subtitle_streams.push_back (subtitle_stream_factory (v, version));
- } else if (k == "frames_per_second") {
- _frames_per_second = atof (v.c_str ());
+ {
+ optional<string> c = f.optional_string_child ("Container");
+ if (c) {
+ _container = Ratio::from_id (c.get ());
}
}
- if (!version) {
- if (audio_sample_rate) {
- /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
- for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
- (*i)->set_sample_rate (audio_sample_rate.get());
- }
- }
+ _resolution = string_to_resolution (f.string_child ("Resolution"));
+ _scaler = Scaler::from_id (f.string_child ("Scaler"));
+ _with_subtitles = f.bool_child ("WithSubtitles");
+ _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+ _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+ _video_frame_rate = f.number_child<int> ("VideoFrameRate");
+ _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+ _audio_channels = f.number_child<int> ("AudioChannels");
+ _sequence_video = f.bool_child ("SequenceVideo");
+ _three_d = f.bool_child ("ThreeD");
+ _interop = f.bool_child ("Interop");
- /* 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()];
- }
+ _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"));
- /* 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;
}
-Size
-Film::cropped_size (Size s) const
-{
- boost::mutex::scoped_lock lm (_state_mutex);
- s.width -= _crop.left + _crop.right;
- s.height -= _crop.top + _crop.bottom;
- return s;
-}
-
/** Given a directory name, return its full path within the Film's directory.
* The directory (and its parents) will be created if they do not exist.
*/
string
Film::dir (string d) const
{
- boost::mutex::scoped_lock lm (_directory_mutex);
boost::filesystem::path p;
p /= _directory;
p /= d;
+
boost::filesystem::create_directories (p);
+
return p.string ();
}
/** Given a file or directory name, return its full path within the Film's directory.
- * _directory_mutex must not be locked on entry.
+ * Any required parent directories will be created.
*/
string
Film::file (string f) const
{
- boost::mutex::scoped_lock lm (_directory_mutex);
boost::filesystem::path p;
p /= _directory;
p /= f;
- return p.string ();
-}
-
-/** @return full path of the content (actual video) file
- * of the Film.
- */
-string
-Film::content_path () const
-{
- boost::mutex::scoped_lock lm (_state_mutex);
- if (boost::filesystem::path(_content).has_root_directory ()) {
- return _content;
- }
-
- return file (_content);
-}
-ContentType
-Film::content_type () const
-{
- if (boost::filesystem::is_directory (_content)) {
- /* Directory of images, we assume */
- return VIDEO;
- }
-
- if (still_image_file (_content)) {
- return STILL;
- }
-
- return VIDEO;
-}
-
-/** @return The sampling rate that we will resample the audio to */
-int
-Film::target_audio_sample_rate () const
-{
- if (!audio_stream()) {
- return 0;
- }
+ boost::filesystem::create_directories (p.parent_path ());
- /* Resample to a DCI-approved sample rate */
- double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
-
- DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
-
- /* Compensate for the fact that video will be rounded to the
- nearest integer number of frames per second.
- */
- if (dfr.run_fast) {
- t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
- }
-
- return rint (t);
-}
-
-boost::optional<int>
-Film::dcp_length () const
-{
- if (content_type() == STILL) {
- return _still_duration * frames_per_second();
- }
-
- if (!length()) {
- return boost::optional<int> ();
- }
-
- return length().get() - dcp_trim_start() - dcp_trim_end();
+ return p.string ();
}
/** @return a DCI-compliant name for a DCP of this film */
string
-Film::dci_name () const
+Film::dci_name (bool if_created_now) const
{
stringstream d;
@@ -758,64 +446,82 @@ Film::dci_name () const
fixed_name = fixed_name.substr (0, 14);
}
- d << fixed_name << "_";
+ d << fixed_name;
if (dcp_content_type()) {
- d << dcp_content_type()->dci_name() << "_";
+ d << "_" << dcp_content_type()->dci_name();
+ d << "-" << dci_metadata().content_version;
+ }
+
+ if (three_d ()) {
+ d << "-3D";
+ }
+
+ if (video_frame_rate() != 24) {
+ d << "-" << video_frame_rate();
}
- if (format()) {
- d << format()->dci_name() << "_";
+ if (container()) {
+ d << "_" << container()->dci_name();
}
- if (!audio_language().empty ()) {
- d << audio_language();
- if (!subtitle_language().empty() && with_subtitles()) {
- d << "-" << subtitle_language();
+ DCIMetadata const dm = dci_metadata ();
+
+ if (!dm.audio_language.empty ()) {
+ d << "_" << dm.audio_language;
+ if (!dm.subtitle_language.empty() && with_subtitles()) {
+ d << "-" << dm.subtitle_language;
} else {
d << "-XX";
}
-
- d << "_";
}
- if (!territory().empty ()) {
- d << territory();
- if (!rating().empty ()) {
- d << "-" << rating();
+ if (!dm.territory.empty ()) {
+ d << "_" << dm.territory;
+ if (!dm.rating.empty ()) {
+ d << "-" << dm.rating;
}
- d << "_";
}
- switch (audio_channels()) {
+ switch (audio_channels ()) {
case 1:
- d << "10_";
+ d << "_10";
break;
case 2:
- d << "20_";
+ d << "_20";
break;
- case 6:
- d << "51_";
+ case 3:
+ d << "_30";
+ break;
+ case 4:
+ d << "_40";
break;
- case 8:
- d << "71_";
+ case 5:
+ d << "_50";
+ break;
+ case 6:
+ d << "_51";
break;
}
- d << "2K_";
+ d << "_" << resolution_to_string (_resolution);
- if (!studio().empty ()) {
- d << studio() << "_";
+ if (!dm.studio.empty ()) {
+ d << "_" << dm.studio;
}
- d << boost::gregorian::to_iso_string (_dci_date) << "_";
+ if (if_created_now) {
+ d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
+ } else {
+ d << "_" << boost::gregorian::to_iso_string (_dci_date);
+ }
- if (!facility().empty ()) {
- d << facility() << "_";
+ if (!dm.facility.empty ()) {
+ d << "_" << dm.facility;
}
- if (!package_type().empty ()) {
- d << package_type();
+ if (!dm.package_type.empty ()) {
+ d << "_" << dm.package_type;
}
return d.str ();
@@ -823,10 +529,10 @@ Film::dci_name () const
/** @return name to give the DCP */
string
-Film::dcp_name () const
+Film::dcp_name (bool if_created_now) const
{
if (use_dci_name()) {
- return dci_name ();
+ return dci_name (if_created_now);
}
return name();
@@ -836,7 +542,6 @@ Film::dcp_name () const
void
Film::set_directory (string d)
{
- boost::mutex::scoped_lock lm (_state_mutex);
_directory = d;
_dirty = true;
}
@@ -844,612 +549,344 @@ Film::set_directory (string d)
void
Film::set_name (string n)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _name = n;
- }
+ _name = n;
signal_changed (NAME);
}
void
Film::set_use_dci_name (bool u)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _use_dci_name = u;
- }
+ _use_dci_name = u;
signal_changed (USE_DCI_NAME);
}
void
-Film::set_content (string c)
-{
- string check = directory ();
-
-#if BOOST_FILESYSTEM_VERSION == 3
- boost::filesystem::path slash ("/");
- string platform_slash = slash.make_preferred().string ();
-#else
-#ifdef DVDOMATIC_WINDOWS
- string platform_slash = "\\";
-#else
- string platform_slash = "/";
-#endif
-#endif
-
- if (!ends_with (check, platform_slash)) {
- check += platform_slash;
- }
-
- if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
- c = c.substr (_directory.length() + 1);
- }
-
- string old_content;
-
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (c == _content) {
- return;
- }
-
- old_content = _content;
- _content = c;
- }
-
- /* Reset streams here in case the new content doesn't have one or the other */
- _content_audio_stream = shared_ptr<AudioStream> ();
- _subtitle_stream = shared_ptr<SubtitleStream> ();
-
- /* Start off using content audio */
- set_use_content_audio (true);
-
- /* Create a temporary decoder so that we can get information
- about the content.
- */
-
- try {
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- Decoders d = decoder_factory (shared_from_this(), o, 0);
-
- set_size (d.video->native_size ());
- set_frames_per_second (d.video->frames_per_second ());
- set_subtitle_streams (d.video->subtitle_streams ());
- if (d.audio) {
- set_content_audio_streams (d.audio->audio_streams ());
- }
-
- /* Start off with the first audio and subtitle streams */
- if (d.audio && !d.audio->audio_streams().empty()) {
- set_content_audio_stream (d.audio->audio_streams().front());
- }
-
- if (!d.video->subtitle_streams().empty()) {
- set_subtitle_stream (d.video->subtitle_streams().front());
- }
-
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = c;
- }
-
- signal_changed (CONTENT);
-
- examine_content ();
-
- } catch (...) {
-
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = old_content;
- throw;
-
- }
-
- /* Default format */
- switch (content_type()) {
- case STILL:
- set_format (Format::from_id ("var-185"));
- break;
- case VIDEO:
- set_format (Format::from_id ("185"));
- break;
- }
-
- /* Still image DCPs must use external audio */
- if (content_type() == STILL) {
- set_use_content_audio (false);
- }
-}
-
-void
-Film::set_trust_content_header (bool t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _trust_content_header = t;
- }
-
- signal_changed (TRUST_CONTENT_HEADER);
-
- if (!_trust_content_header && !content().empty()) {
- /* We just said that we don't trust the content's header */
- examine_content ();
- }
-}
-
-void
Film::set_dcp_content_type (DCPContentType const * t)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_content_type = t;
- }
+ _dcp_content_type = t;
signal_changed (DCP_CONTENT_TYPE);
}
void
-Film::set_format (Format const * f)
+Film::set_container (Ratio const * c)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _format = f;
- }
- signal_changed (FORMAT);
+ _container = c;
+ signal_changed (CONTAINER);
}
void
-Film::set_crop (Crop c)
+Film::set_resolution (Resolution r)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _crop = c;
- }
- signal_changed (CROP);
+ _resolution = r;
+ signal_changed (RESOLUTION);
}
void
-Film::set_left_crop (int c)
+Film::set_scaler (Scaler const * s)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
-
- if (_crop.left == c) {
- return;
- }
-
- _crop.left = c;
- }
- signal_changed (CROP);
+ _scaler = s;
+ signal_changed (SCALER);
}
void
-Film::set_right_crop (int c)
+Film::set_with_subtitles (bool w)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (_crop.right == c) {
- return;
- }
-
- _crop.right = c;
- }
- signal_changed (CROP);
+ _with_subtitles = w;
+ signal_changed (WITH_SUBTITLES);
}
void
-Film::set_top_crop (int c)
+Film::set_j2k_bandwidth (int b)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (_crop.top == c) {
- return;
- }
-
- _crop.top = c;
- }
- signal_changed (CROP);
+ _j2k_bandwidth = b;
+ signal_changed (J2K_BANDWIDTH);
}
void
-Film::set_bottom_crop (int c)
+Film::set_dci_metadata (DCIMetadata m)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (_crop.bottom == c) {
- return;
- }
-
- _crop.bottom = c;
- }
- signal_changed (CROP);
+ _dci_metadata = m;
+ signal_changed (DCI_METADATA);
}
void
-Film::set_filters (vector<Filter const *> f)
+Film::set_video_frame_rate (int f)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _filters = f;
- }
- signal_changed (FILTERS);
+ _video_frame_rate = f;
+ signal_changed (VIDEO_FRAME_RATE);
}
void
-Film::set_scaler (Scaler const * s)
+Film::set_audio_channels (int c)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _scaler = s;
- }
- signal_changed (SCALER);
+ _audio_channels = c;
+ signal_changed (AUDIO_CHANNELS);
}
void
-Film::set_dcp_trim_start (int t)
+Film::set_three_d (bool t)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_trim_start = t;
- }
- signal_changed (DCP_TRIM_START);
+ _three_d = t;
+ signal_changed (THREE_D);
}
void
-Film::set_dcp_trim_end (int t)
+Film::set_interop (bool i)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_trim_end = t;
- }
- signal_changed (DCP_TRIM_END);
+ _interop = i;
+ signal_changed (INTEROP);
}
void
-Film::set_reel_size (uint64_t s)
+Film::signal_changed (Property p)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _reel_size = s;
- }
- signal_changed (REEL_SIZE);
-}
+ _dirty = true;
-void
-Film::unset_reel_size ()
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _reel_size = boost::optional<uint64_t> ();
+ switch (p) {
+ case Film::CONTENT:
+ set_video_frame_rate (_playlist->best_dcp_frame_rate ());
+ break;
+ case Film::VIDEO_FRAME_RATE:
+ case Film::SEQUENCE_VIDEO:
+ _playlist->maybe_sequence_video ();
+ break;
+ default:
+ break;
}
- signal_changed (REEL_SIZE);
-}
-void
-Film::set_dcp_ab (bool a)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_ab = a;
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (Changed), p));
}
- signal_changed (DCP_AB);
}
void
-Film::set_content_audio_stream (shared_ptr<AudioStream> s)
+Film::set_dci_date_today ()
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_audio_stream = s;
- }
- signal_changed (CONTENT_AUDIO_STREAM);
+ _dci_date = boost::gregorian::day_clock::local_day ();
}
-void
-Film::set_external_audio (vector<string> a)
+string
+Film::info_path (int f, Eyes e) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _external_audio = a;
- }
+ boost::filesystem::path p;
+ p /= info_dir ();
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
- if (decoder->audio_stream()) {
- _external_audio_stream = decoder->audio_stream ();
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << f;
+
+ if (e == EYES_LEFT) {
+ s << ".L";
+ } else if (e == EYES_RIGHT) {
+ s << ".R";
}
+
+ s << ".md5";
- signal_changed (EXTERNAL_AUDIO);
+ p /= s.str();
+
+ /* info_dir() will already have added any initial bit of the path,
+ so don't call file() on this.
+ */
+ return p.string ();
}
-void
-Film::set_use_content_audio (bool e)
+string
+Film::j2c_path (int f, Eyes e, bool t) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _use_content_audio = e;
- }
+ boost::filesystem::path p;
+ p /= "j2c";
+ p /= video_identifier ();
- signal_changed (USE_CONTENT_AUDIO);
-}
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << f;
-void
-Film::set_audio_gain (float g)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_gain = g;
+ if (e == EYES_LEFT) {
+ s << ".L";
+ } else if (e == EYES_RIGHT) {
+ s << ".R";
}
- signal_changed (AUDIO_GAIN);
-}
+
+ s << ".j2c";
-void
-Film::set_audio_delay (int d)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_delay = d;
+ if (t) {
+ s << ".tmp";
}
- signal_changed (AUDIO_DELAY);
-}
-void
-Film::set_still_duration (int d)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _still_duration = d;
- }
- signal_changed (STILL_DURATION);
+ p /= s.str();
+ return file (p.string ());
}
-void
-Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_stream = s;
- }
- signal_changed (SUBTITLE_STREAM);
-}
+/** Make an educated guess as to whether we have a complete DCP
+ * or not.
+ * @return true if we do.
+ */
-void
-Film::set_with_subtitles (bool w)
+bool
+Film::have_dcp () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _with_subtitles = w;
+ try {
+ libdcp::DCP dcp (dir (dcp_name()));
+ dcp.read ();
+ } catch (...) {
+ return false;
}
- signal_changed (WITH_SUBTITLES);
-}
-void
-Film::set_subtitle_offset (int o)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_offset = o;
- }
- signal_changed (SUBTITLE_OFFSET);
+ return true;
}
-void
-Film::set_subtitle_scale (float s)
+shared_ptr<Player>
+Film::make_player () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_scale = s;
- }
- signal_changed (SUBTITLE_SCALE);
+ return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
}
void
Film::set_encrypted (bool e)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _encrypted = e;
- }
+ _encrypted = e;
signal_changed (ENCRYPTED);
}
-void
-Film::set_colour_lut (int i)
+shared_ptr<Playlist>
+Film::playlist () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _colour_lut = i;
- }
- signal_changed (COLOUR_LUT);
+ return _playlist;
}
-void
-Film::set_j2k_bandwidth (int b)
+ContentList
+Film::content () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _j2k_bandwidth = b;
- }
- signal_changed (J2K_BANDWIDTH);
+ return _playlist->content ();
}
void
-Film::set_audio_language (string l)
+Film::examine_and_add_content (shared_ptr<Content> c)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_language = l;
- }
- signal_changed (DCI_METADATA);
+ shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
+ j->Finished.connect (bind (&Film::maybe_add_content, this, boost::weak_ptr<Job> (j), boost::weak_ptr<Content> (c)));
+ JobManager::instance()->add (j);
}
void
-Film::set_subtitle_language (string l)
+Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_language = l;
+ shared_ptr<Job> job = j.lock ();
+ if (!job || !job->finished_ok ()) {
+ return;
}
- signal_changed (DCI_METADATA);
-}
-
-void
-Film::set_territory (string t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _territory = t;
+
+ shared_ptr<Content> content = c.lock ();
+ if (content) {
+ add_content (content);
}
- signal_changed (DCI_METADATA);
}
void
-Film::set_rating (string r)
+Film::add_content (shared_ptr<Content> c)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _rating = r;
+ /* Add video content after any existing content */
+ if (dynamic_pointer_cast<VideoContent> (c)) {
+ c->set_position (_playlist->video_end ());
}
- signal_changed (DCI_METADATA);
+
+ _playlist->add (c);
}
void
-Film::set_studio (string s)
+Film::remove_content (shared_ptr<Content> c)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _studio = s;
- }
- signal_changed (DCI_METADATA);
+ _playlist->remove (c);
}
-void
-Film::set_facility (string f)
+Time
+Film::length () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _facility = f;
- }
- signal_changed (DCI_METADATA);
+ return _playlist->length ();
}
-void
-Film::set_package_type (string p)
+bool
+Film::has_subtitles () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _package_type = p;
- }
- signal_changed (DCI_METADATA);
+ return _playlist->has_subtitles ();
}
-void
-Film::set_size (Size s)
+OutputVideoFrame
+Film::best_video_frame_rate () const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _size = s;
- }
- signal_changed (SIZE);
+ return _playlist->best_dcp_frame_rate ();
}
void
-Film::set_length (SourceFrame l)
+Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _length = l;
+ if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
+ set_video_frame_rate (_playlist->best_dcp_frame_rate ());
+ }
+
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
}
- signal_changed (LENGTH);
}
void
-Film::unset_length ()
+Film::playlist_changed ()
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _length = boost::none;
- }
- signal_changed (LENGTH);
+ signal_changed (CONTENT);
}
-void
-Film::set_content_digest (string d)
+OutputAudioFrame
+Film::time_to_audio_frames (Time t) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_digest = d;
- }
- _dirty = true;
+ return t * audio_frame_rate () / TIME_HZ;
}
-void
-Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
+OutputVideoFrame
+Film::time_to_video_frames (Time t) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_audio_streams = s;
- }
- signal_changed (CONTENT_AUDIO_STREAMS);
+ return t * video_frame_rate () / TIME_HZ;
}
-void
-Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
+Time
+Film::audio_frames_to_time (OutputAudioFrame f) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_streams = s;
- }
- signal_changed (SUBTITLE_STREAMS);
+ return f * TIME_HZ / audio_frame_rate ();
}
-void
-Film::set_frames_per_second (float f)
+Time
+Film::video_frames_to_time (OutputVideoFrame f) const
{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _frames_per_second = f;
- }
- signal_changed (FRAMES_PER_SECOND);
-}
-
-void
-Film::signal_changed (Property p)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dirty = true;
- }
-
- if (ui_signaller) {
- ui_signaller->emit (boost::bind (boost::ref (Changed), p));
- }
+ return f * TIME_HZ / video_frame_rate ();
}
-int
-Film::audio_channels () const
+OutputAudioFrame
+Film::audio_frame_rate () const
{
- shared_ptr<AudioStream> s = audio_stream ();
- if (!s) {
- return 0;
- }
-
- return s->channels ();
+ /* XXX */
+ return 48000;
}
void
-Film::set_dci_date_today ()
+Film::set_sequence_video (bool s)
{
- _dci_date = boost::gregorian::day_clock::local_day ();
+ _sequence_video = s;
+ _playlist->set_sequence_video (s);
+ signal_changed (SEQUENCE_VIDEO);
}
-boost::shared_ptr<AudioStream>
-Film::audio_stream () const
+libdcp::Size
+Film::full_frame () const
{
- if (use_content_audio()) {
- return _content_audio_stream;
+ switch (_resolution) {
+ case RESOLUTION_2K:
+ return libdcp::Size (2048, 1080);
+ case RESOLUTION_4K:
+ return libdcp::Size (4096, 2160);
}
- return _external_audio_stream;
+ assert (false);
+ return libdcp::Size ();
}
void
@@ -1509,7 +946,9 @@ Film::make_kdms (
dcp.read ();
/* XXX: single CPL only */
- shared_ptr<xmlpp::Document> kdm = dcp.cpls().front()->make_kdm (chain, signer_key.string(), (*i)->certificate, from, until);
+ shared_ptr<xmlpp::Document> kdm = dcp.cpls().front()->make_kdm (
+ chain, signer_key.string(), (*i)->certificate, from, until, _interop, libdcp::MXFMetadata (), Config::instance()->dcp_metadata ()
+ );
boost::filesystem::path out = directory;
out /= "kdm.xml";
diff --git a/src/lib/film.h b/src/lib/film.h
index 1a78e9d34..809eabdaa 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -18,60 +18,61 @@
*/
/** @file src/film.h
- * @brief A representation of a piece of video (with sound), including naming,
- * the source content file, and how it should be presented in a DCP.
+ * @brief A representation of some audio and video content, and details of
+ * how they should be presented in a DCP.
*/
-#ifndef DVDOMATIC_FILM_H
-#define DVDOMATIC_FILM_H
+#ifndef DCPOMATIC_FILM_H
+#define DCPOMATIC_FILM_H
#include <string>
#include <vector>
#include <inttypes.h>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread.hpp>
#include <boost/signals2.hpp>
#include <boost/enable_shared_from_this.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-extern "C" {
-#include <libavcodec/avcodec.h>
-}
-#include "dcp_content_type.h"
+#include <boost/filesystem.hpp>
#include "util.h"
-#include "stream.h"
+#include "types.h"
+#include "dci_metadata.h"
-class Format;
-class Job;
-class Filter;
+class DCPContentType;
class Log;
-class ExamineContentJob;
-class ExternalAudioStream;
+class Content;
+class Player;
+class Playlist;
+class AudioContent;
+class Scaler;
class Screen;
/** @class Film
- * @brief A representation of a video, maybe with sound.
*
- * A representation of a piece of video (maybe with sound), including naming,
- * the source content file, and how it should be presented in a DCP.
+ * @brief A representation of some audio and video content, and details of
+ * how they should be presented in a DCP.
+ *
+ * The content of a Film is held in a Playlist (created and managed by the Film).
*/
-class Film : public boost::enable_shared_from_this<Film>
+class Film : public boost::enable_shared_from_this<Film>, public boost::noncopyable
{
public:
- Film (std::string d, bool must_exist = true);
- Film (Film const &);
- ~Film ();
+ Film (boost::filesystem::path);
- std::string j2k_dir () const;
+ std::string info_dir () const;
+ std::string j2c_path (int, Eyes, bool) const;
+ std::string info_path (int, Eyes) const;
+ std::string internal_video_mxf_dir () const;
+ std::string internal_video_mxf_filename () const;
+ boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
- void examine_content ();
- void send_dcp_to_tms ();
+ std::string video_mxf_filename () const;
+ std::string audio_mxf_filename () const;
- void make_dcp (bool);
+ void send_dcp_to_tms ();
+ void make_dcp ();
/** @return Logger.
* It is safe to call this from any thread.
*/
- Log* log () const {
+ boost::shared_ptr<Log> log () const {
return _log;
}
@@ -80,27 +81,38 @@ public:
std::string file (std::string f) const;
std::string dir (std::string d) const;
- std::string content_path () const;
- ContentType content_type () const;
-
- int target_audio_sample_rate () const;
-
- void write_metadata () const;
void read_metadata ();
+ void write_metadata () const;
- Size cropped_size (Size) const;
- boost::optional<int> dcp_length () const;
- std::string dci_name () const;
- std::string dcp_name () const;
+ std::string dci_name (bool if_created_now) const;
+ std::string dcp_name (bool if_created_now = false) const;
/** @return true if our state has changed since we last saved it */
bool dirty () const {
return _dirty;
}
- int audio_channels () const;
+ libdcp::Size full_frame () const;
- void set_dci_date_today ();
+ bool have_dcp () const;
+
+ boost::shared_ptr<Player> make_player () const;
+ boost::shared_ptr<Playlist> playlist () const;
+
+ OutputAudioFrame audio_frame_rate () const;
+
+ OutputAudioFrame time_to_audio_frames (Time) const;
+ OutputVideoFrame time_to_video_frames (Time) const;
+ Time video_frames_to_time (OutputVideoFrame) const;
+ Time audio_frames_to_time (OutputAudioFrame) const;
+
+ /* Proxies for some Playlist methods */
+
+ ContentList content () const;
+
+ Time length () const;
+ bool has_subtitles () const;
+ OutputVideoFrame best_video_frame_rate () const;
void make_kdms (
std::list<boost::shared_ptr<Screen> >,
@@ -116,424 +128,180 @@ public:
NONE,
NAME,
USE_DCI_NAME,
+ /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */
CONTENT,
- TRUST_CONTENT_HEADER,
DCP_CONTENT_TYPE,
- FORMAT,
- CROP,
- FILTERS,
+ CONTAINER,
+ RESOLUTION,
SCALER,
- DCP_TRIM_START,
- DCP_TRIM_END,
- REEL_SIZE,
- DCP_AB,
- CONTENT_AUDIO_STREAM,
- EXTERNAL_AUDIO,
- USE_CONTENT_AUDIO,
- AUDIO_GAIN,
- AUDIO_DELAY,
- STILL_DURATION,
- SUBTITLE_STREAM,
WITH_SUBTITLES,
- SUBTITLE_OFFSET,
- SUBTITLE_SCALE,
ENCRYPTED,
- COLOUR_LUT,
J2K_BANDWIDTH,
DCI_METADATA,
- SIZE,
- LENGTH,
- CONTENT_AUDIO_STREAMS,
- SUBTITLE_STREAMS,
- FRAMES_PER_SECOND,
+ VIDEO_FRAME_RATE,
+ AUDIO_CHANNELS,
+ /** The setting of _three_d has been changed */
+ THREE_D,
+ SEQUENCE_VIDEO,
+ INTEROP,
};
/* GET */
std::string directory () const {
- boost::mutex::scoped_lock lm (_directory_mutex);
return _directory;
}
std::string name () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _name;
}
bool use_dci_name () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _use_dci_name;
}
- std::string content () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content;
- }
-
- bool trust_content_header () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _trust_content_header;
- }
-
DCPContentType const * dcp_content_type () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _dcp_content_type;
}
- Format const * format () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _format;
+ Ratio const * container () const {
+ return _container;
}
- Crop crop () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _crop;
- }
-
- std::vector<Filter const *> filters () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _filters;
+ Resolution resolution () const {
+ return _resolution;
}
Scaler const * scaler () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _scaler;
}
- SourceFrame dcp_trim_start () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_trim_start;
- }
-
- SourceFrame dcp_trim_end () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_trim_end;
- }
-
- boost::optional<uint64_t> reel_size () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _reel_size;
- }
-
- bool dcp_ab () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_ab;
- }
-
- boost::shared_ptr<AudioStream> content_audio_stream () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_audio_stream;
- }
-
- std::vector<std::string> external_audio () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _external_audio;
- }
-
- bool use_content_audio () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _use_content_audio;
- }
-
- float audio_gain () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _audio_gain;
- }
-
- int audio_delay () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _audio_delay;
- }
-
- int still_duration () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _still_duration;
- }
-
- boost::shared_ptr<SubtitleStream> subtitle_stream () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_stream;
- }
-
bool with_subtitles () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _with_subtitles;
}
- int subtitle_offset () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_offset;
- }
-
- float subtitle_scale () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_scale;
- }
-
bool encrypted () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _encrypted;
}
- int colour_lut () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _colour_lut;
- }
-
int j2k_bandwidth () const {
- boost::mutex::scoped_lock lm (_state_mutex);
return _j2k_bandwidth;
}
- std::string audio_language () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _audio_language;
- }
-
- std::string subtitle_language () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_language;
- }
-
- std::string territory () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _territory;
- }
-
- std::string rating () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _rating;
- }
-
- std::string studio () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _studio;
- }
-
- std::string facility () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _facility;
- }
-
- std::string package_type () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _package_type;
+ DCIMetadata dci_metadata () const {
+ return _dci_metadata;
}
- Size size () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _size;
+ /** @return The frame rate of the DCP */
+ int video_frame_rate () const {
+ return _video_frame_rate;
}
- boost::optional<SourceFrame> length () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _length;
+ int audio_channels () const {
+ return _audio_channels;
}
-
- std::string content_digest () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_digest;
+
+ bool three_d () const {
+ return _three_d;
}
-
- std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_audio_streams;
+
+ bool sequence_video () const {
+ return _sequence_video;
}
- std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_streams;
+ bool interop () const {
+ return _interop;
}
- float frames_per_second () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (content_type() == STILL) {
- return 24;
- }
-
- return _frames_per_second;
- }
-
- boost::shared_ptr<AudioStream> audio_stream () const;
-
/* SET */
void set_directory (std::string);
void set_name (std::string);
void set_use_dci_name (bool);
- void set_content (std::string);
- void set_trust_content_header (bool);
+ void examine_and_add_content (boost::shared_ptr<Content>);
+ void add_content (boost::shared_ptr<Content>);
+ void remove_content (boost::shared_ptr<Content>);
void set_dcp_content_type (DCPContentType const *);
- void set_format (Format const *);
- void set_crop (Crop);
- void set_left_crop (int);
- void set_right_crop (int);
- void set_top_crop (int);
- void set_bottom_crop (int);
- void set_filters (std::vector<Filter const *>);
+ void set_container (Ratio const *);
+ void set_resolution (Resolution);
void set_scaler (Scaler const *);
- void set_dcp_trim_start (int);
- void set_dcp_trim_end (int);
- void set_reel_size (uint64_t);
- void unset_reel_size ();
- void set_dcp_ab (bool);
- void set_content_audio_stream (boost::shared_ptr<AudioStream>);
- void set_external_audio (std::vector<std::string>);
- void set_use_content_audio (bool);
- void set_audio_gain (float);
- void set_audio_delay (int);
- void set_still_duration (int);
- void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
void set_with_subtitles (bool);
- void set_subtitle_offset (int);
- void set_subtitle_scale (float);
void set_encrypted (bool);
- void set_colour_lut (int);
void set_j2k_bandwidth (int);
- void set_audio_language (std::string);
- void set_subtitle_language (std::string);
- void set_territory (std::string);
- void set_rating (std::string);
- void set_studio (std::string);
- void set_facility (std::string);
- void set_package_type (std::string);
- void set_size (Size);
- void set_length (SourceFrame);
- void unset_length ();
- void set_content_digest (std::string);
- void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
- void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
- void set_frames_per_second (float);
-
- /** Emitted when some property has changed */
+ void set_dci_metadata (DCIMetadata);
+ void set_video_frame_rate (int);
+ void set_audio_channels (int);
+ void set_three_d (bool);
+ void set_dci_date_today ();
+ void set_sequence_video (bool);
+ void set_interop (bool);
+
+ /** Emitted when some property has of the Film has changed */
mutable boost::signals2::signal<void (Property)> Changed;
+ /** Emitted when some property of our content has changed */
+ mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+
/** Current version number of the state file */
static int const state_version;
private:
-
- /** Log to write to */
- Log* _log;
-
- /** Any running ExamineContentJob, or 0 */
- boost::shared_ptr<ExamineContentJob> _examine_content_job;
-
- /** The date that we should use in a DCI name */
- boost::gregorian::date _dci_date;
void signal_changed (Property);
- void examine_content_finished ();
+ std::string video_identifier () const;
+ void playlist_changed ();
+ void playlist_content_changed (boost::weak_ptr<Content>, int);
+ std::string filename_safe_name () const;
+ void maybe_add_content (boost::weak_ptr<Job>, boost::weak_ptr<Content>);
+
+ /** Log to write to */
+ boost::shared_ptr<Log> _log;
+ boost::shared_ptr<Playlist> _playlist;
/** Complete path to directory containing the film metadata;
* must not be relative.
*/
std::string _directory;
- /** Mutex for _directory */
- mutable boost::mutex _directory_mutex;
- /** Name for DVD-o-matic */
+ /** Name for DCP-o-matic */
std::string _name;
/** True if a auto-generated DCI-compliant name should be used for our DCP */
bool _use_dci_name;
- /** File or directory containing content; may be relative to our directory
- * or an absolute path.
- */
- std::string _content;
- /** If this is true, we will believe the length specified by the content
- * file's header; if false, we will run through the whole content file
- * the first time we see it in order to obtain the length.
- */
- bool _trust_content_header;
/** The type of content that this Film represents (feature, trailer etc.) */
DCPContentType const * _dcp_content_type;
- /** The format to present this Film in (flat, scope, etc.) */
- Format const * _format;
- /** The crop to apply to the source */
- Crop _crop;
- /** Video filters that should be used when generating DCPs */
- std::vector<Filter const *> _filters;
+ /** The container to put this Film in (flat, scope, etc.) */
+ Ratio const * _container;
+ /** DCP resolution (2K or 4K) */
+ Resolution _resolution;
/** Scaler algorithm to use */
Scaler const * _scaler;
- /** Frames to trim off the start of the DCP */
- int _dcp_trim_start;
- /** Frames to trim off the end of the DCP */
- int _dcp_trim_end;
- /** Approximate target reel size in bytes; if not set, use a single reel */
- boost::optional<uint64_t> _reel_size;
- /** true to create an A/B comparison DCP, where the left half of the image
- is the video without any filters or post-processing, and the right half
- has the specified filters and post-processing.
- */
- bool _dcp_ab;
- /** The audio stream to use from our content */
- boost::shared_ptr<AudioStream> _content_audio_stream;
- /** List of filenames of external audio files, in channel order
- (L, R, C, Lfe, Ls, Rs)
- */
- std::vector<std::string> _external_audio;
- /** true to use audio from our content file; false to use external audio */
- bool _use_content_audio;
- /** Gain to apply to audio in dB */
- float _audio_gain;
- /** Delay to apply to audio (positive moves audio later) in milliseconds */
- int _audio_delay;
- /** Duration to make still-sourced films (in seconds) */
- int _still_duration;
- boost::shared_ptr<SubtitleStream> _subtitle_stream;
/** True if subtitles should be shown for this film */
bool _with_subtitles;
- /** y offset for placing subtitles, in source pixels; +ve is further down
- the frame, -ve is further up.
- */
- int _subtitle_offset;
- /** scale factor to apply to subtitles */
- float _subtitle_scale;
bool _encrypted;
-
- /** index of colour LUT to use when converting RGB to XYZ.
- * 0: sRGB
- * 1: Rec 709
- */
- int _colour_lut;
/** bandwidth for J2K files in bits per second */
int _j2k_bandwidth;
-
- /* DCI naming stuff */
- std::string _audio_language;
- std::string _subtitle_language;
- std::string _territory;
- std::string _rating;
- std::string _studio;
- std::string _facility;
- std::string _package_type;
-
- /* Data which are cached to speed things up */
-
- /** Size, in pixels, of the source (ignoring cropping) */
- Size _size;
- /** The length of the source, in video frames (as far as we know) */
- boost::optional<SourceFrame> _length;
- /** MD5 digest of our content file */
- std::string _content_digest;
- /** The audio streams in our content */
- std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams;
- /** A stream to represent possible external audio (will always exist) */
- boost::shared_ptr<AudioStream> _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;
+ /** DCI naming stuff */
+ DCIMetadata _dci_metadata;
+ /** Frames per second to run our DCP at */
+ int _video_frame_rate;
+ /** The date that we should use in a DCI name */
+ boost::gregorian::date _dci_date;
+ /** Number of audio channels to put in the DCP */
+ int _audio_channels;
+ /** If true, the DCP will be written in 3D mode; otherwise in 2D.
+ This will be regardless of what content is on the playlist.
+ */
+ bool _three_d;
+ bool _sequence_video;
+ bool _interop;
/** true if our state has changed since we last saved it */
mutable bool _dirty;
- /** Mutex for all state except _directory */
- mutable boost::mutex _state_mutex;
-
friend class paths_test;
+ friend class film_metadata_test;
};
#endif
diff --git a/src/lib/filter.cc b/src/lib/filter.cc
index 446cc111d..640a531e8 100644
--- a/src/lib/filter.cc
+++ b/src/lib/filter.cc
@@ -22,6 +22,12 @@
*/
#include "filter.h"
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libpostproc/postprocess.h>
+}
+
+#include "i18n.h"
using namespace std;
@@ -29,12 +35,14 @@ vector<Filter const *> Filter::_filters;
/** @param i Our id.
* @param n User-visible name.
+ * @param c User-visible category.
* @param v String for a FFmpeg video filter descriptor, or "".
* @param p String for a FFmpeg post-processing descriptor, or "".
*/
-Filter::Filter (string i, string n, string v, string p)
+Filter::Filter (string i, string n, string c, string v, string p)
: _id (i)
, _name (n)
+ , _category (c)
, _vf (v)
, _pp (p)
{
@@ -57,30 +65,46 @@ Filter::setup_filters ()
{
/* Note: "none" is a magic id name, so don't use it here */
- _filters.push_back (new Filter ("pphb", "Horizontal deblocking filter", "", "hb"));
- _filters.push_back (new Filter ("ppvb", "Vertical deblocking filter", "", "vb"));
- _filters.push_back (new Filter ("ppha", "Horizontal deblocking filter A", "", "ha"));
- _filters.push_back (new Filter ("ppva", "Vertical deblocking filter A", "", "va"));
- _filters.push_back (new Filter ("pph1", "Experimental horizontal deblocking filter 1", "", "h1"));
- _filters.push_back (new Filter ("pphv", "Experimental vertical deblocking filter 1", "", "v1"));
- _filters.push_back (new Filter ("ppdr", "Deringing filter", "", "dr"));
- _filters.push_back (new Filter ("pplb", "Linear blend deinterlacer", "", "lb"));
- _filters.push_back (new Filter ("ppli", "Linear interpolating deinterlacer", "", "li"));
- _filters.push_back (new Filter ("ppci", "Cubic interpolating deinterlacer", "", "ci"));
- _filters.push_back (new Filter ("ppmd", "Median deinterlacer", "", "md"));
- _filters.push_back (new Filter ("ppfd", "FFMPEG deinterlacer", "", "fd"));
- _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5"));
- _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", ""));
- _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", ""));
- _filters.push_back (new Filter ("yadif", "Yet Another Deinterlacing Filter", "yadif", ""));
- _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn"));
- _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq"));
- _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", ""));
- _filters.push_back (new Filter ("unsharp", "Unsharp mask and Gaussian blur", "unsharp", ""));
- _filters.push_back (new Filter ("denoise3d", "3D denoiser", "denoise3d", ""));
- _filters.push_back (new Filter ("hqdn3d", "High quality 3D denoiser", "hqdn3d", ""));
- _filters.push_back (new Filter ("telecine", "Telecine filter", "telecine", ""));
- _filters.push_back (new Filter ("ow", "Overcomplete wavelet denoiser", "mp=ow", ""));
+ maybe_add (N_("pphb"), _("Horizontal deblocking filter"), _("De-blocking"), N_(""), N_("hb"));
+ maybe_add (N_("ppvb"), _("Vertical deblocking filter"), _("De-blocking"), N_(""), N_("vb"));
+ maybe_add (N_("ppha"), _("Horizontal deblocking filter A"), _("De-blocking"), N_(""), N_("ha"));
+ maybe_add (N_("ppva"), _("Vertical deblocking filter A"), _("De-blocking"), N_(""), N_("va"));
+ maybe_add (N_("pph1"), _("Experimental horizontal deblocking filter 1"), _("De-blocking"), N_(""), N_("h1"));
+ maybe_add (N_("pphv"), _("Experimental vertical deblocking filter 1"), _("De-blocking"), N_(""), N_("v1"));
+ maybe_add (N_("ppdr"), _("Deringing filter"), _("Misc"), N_(""), N_("dr"));
+ maybe_add (N_("pplb"), _("Linear blend deinterlacer"), _("De-interlacing"), N_(""), N_("lb"));
+ maybe_add (N_("ppli"), _("Linear interpolating deinterlacer"), _("De-interlacing"), N_(""), N_("li"));
+ maybe_add (N_("ppci"), _("Cubic interpolating deinterlacer"), _("De-interlacing"), N_(""), N_("ci"));
+ maybe_add (N_("ppmd"), _("Median deinterlacer"), _("De-interlacing"), N_(""), N_("md"));
+ maybe_add (N_("ppfd"), _("FFMPEG deinterlacer"), _("De-interlacing"), N_(""), N_("fd"));
+ maybe_add (N_("ppl5"), _("FIR low-pass deinterlacer"), _("De-interlacing"), N_(""), N_("l5"));
+ maybe_add (N_("mcdeint"), _("Motion compensating deinterlacer"), _("De-interlacing"), N_("mcdeint"), N_(""));
+ maybe_add (N_("kerndeint"), _("Kernel deinterlacer"), _("De-interlacing"), N_("kerndeint"), N_(""));
+ maybe_add (N_("yadif"), _("Yet Another Deinterlacing Filter"), _("De-interlacing"), N_("yadif"), N_(""));
+ maybe_add (N_("pptn"), _("Temporal noise reducer"), _("Noise reduction"), N_(""), N_("tn"));
+ maybe_add (N_("ppfq"), _("Force quantizer"), _("Misc"), N_(""), N_("fq"));
+ maybe_add (N_("gradfun"), _("Gradient debander"), _("Misc"), N_("gradfun"), N_(""));
+ maybe_add (N_("unsharp"), _("Unsharp mask and Gaussian blur"), _("Misc"), N_("unsharp"), N_(""));
+ maybe_add (N_("denoise3d"), _("3D denoiser"), _("Noise reduction"), N_("denoise3d"), N_(""));
+ maybe_add (N_("hqdn3d"), _("High quality 3D denoiser"), _("Noise reduction"), N_("hqdn3d"), N_(""));
+ maybe_add (N_("telecine"), _("Telecine filter"), _("Misc"), N_("telecine"), N_(""));
+ maybe_add (N_("ow"), _("Overcomplete wavelet denoiser"), _("Noise reduction"), N_("mp=ow"), N_(""));
+}
+
+void
+Filter::maybe_add (string i, string n, string c, string v, string p)
+{
+ if (!v.empty ()) {
+ if (avfilter_get_by_name (i.c_str())) {
+ _filters.push_back (new Filter (i, n, c, v, p));
+ }
+ } else if (!p.empty ()) {
+ pp_mode* m = pp_get_mode_by_name_and_quality (p.c_str(), PP_QUALITY_MAX);
+ if (m) {
+ _filters.push_back (new Filter (i, n, c, v, p));
+ pp_free_mode (m);
+ }
+ }
}
/** @param filters Set of filters.
@@ -96,14 +120,14 @@ Filter::ffmpeg_strings (vector<Filter const *> const & filters)
for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
if (!(*i)->vf().empty ()) {
if (!vf.empty ()) {
- vf += ",";
+ vf += N_(",");
}
vf += (*i)->vf ();
}
if (!(*i)->pp().empty ()) {
if (!pp.empty()) {
- pp += ",";
+ pp += N_(",");
}
pp += (*i)->pp ();
}
diff --git a/src/lib/filter.h b/src/lib/filter.h
index 20c55049c..5971cd5cf 100644
--- a/src/lib/filter.h
+++ b/src/lib/filter.h
@@ -21,19 +21,20 @@
* @brief A class to describe one of FFmpeg's video or post-processing filters.
*/
-#ifndef DVDOMATIC_FILTER_H
-#define DVDOMATIC_FILTER_H
+#ifndef DCPOMATIC_FILTER_H
+#define DCPOMATIC_FILTER_H
#include <string>
#include <vector>
+#include <boost/utility.hpp>
/** @class Filter
* @brief A class to describe one of FFmpeg's video or post-processing filters.
*/
-class Filter
+class Filter : public boost::noncopyable
{
public:
- Filter (std::string, std::string, std::string, std::string);
+ Filter (std::string, std::string, std::string, std::string, std::string);
/** @return our id */
std::string id () const {
@@ -54,6 +55,10 @@ public:
std::string pp () const {
return _pp;
}
+
+ std::string category () const {
+ return _category;
+ }
static std::vector<Filter const *> all ();
static Filter const * from_id (std::string);
@@ -66,6 +71,7 @@ private:
std::string _id;
/** user-visible name */
std::string _name;
+ std::string _category;
/** string for a FFmpeg video filter descriptor */
std::string _vf;
/** string for a FFmpeg post-processing descriptor */
@@ -73,6 +79,7 @@ private:
/** all available filters */
static std::vector<Filter const *> _filters;
+ static void maybe_add (std::string, std::string, std::string, std::string, std::string);
};
#endif
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index 376ab404f..cd5d19807 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -23,77 +23,71 @@
extern "C" {
#include <libavfilter/avfiltergraph.h>
-#ifdef HAVE_BUFFERSRC_H
#include <libavfilter/buffersrc.h>
-#endif
-#if (LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 77) || LIBAVFILTER_VERSION_MAJOR == 3
#include <libavfilter/avcodec.h>
#include <libavfilter/buffersink.h>
-#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-#include <libavfilter/vsrc_buffer.h>
-#endif
#include <libavformat/avio.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"
+#include "ffmpeg_content.h"
+
+#include "i18n.h"
using std::stringstream;
using std::string;
using std::list;
+using std::pair;
+using std::make_pair;
+using std::cout;
using boost::shared_ptr;
+using boost::weak_ptr;
+using libdcp::Size;
-/** Construct a FilterGraph for the settings in a film.
- * @param film Film.
- * @param decoder Decoder that we are using.
+/** Construct a FilterGraph for the settings in a piece of content.
+ * @param content Content.
* @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, Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
, _pixel_format (p)
{
- string filters = Filter::ffmpeg_strings (film->filters()).first;
- if (!filters.empty ()) {
- filters += ",";
+ _frame = av_frame_alloc ();
+
+ string filters = Filter::ffmpeg_strings (content->filters()).first;
+ if (filters.empty ()) {
+ filters = "copy";
}
- filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
-
- avfilter_register_all ();
-
AVFilterGraph* graph = avfilter_graph_alloc();
if (graph == 0) {
- throw DecodeError ("Could not create filter graph.");
+ throw DecodeError (N_("could not create filter graph."));
}
- AVFilter* buffer_src = avfilter_get_by_name("buffer");
+ AVFilter* buffer_src = avfilter_get_by_name(N_("buffer"));
if (buffer_src == 0) {
- throw DecodeError ("Could not find buffer src filter");
+ throw DecodeError (N_("could not find buffer src filter"));
}
- AVFilter* buffer_sink = get_sink ();
+ AVFilter* buffer_sink = avfilter_get_by_name(N_("buffersink"));
+ if (buffer_sink == 0) {
+ throw DecodeError (N_("Could not create buffer sink filter"));
+ }
stringstream a;
- a << _size.width << ":"
- << _size.height << ":"
- << _pixel_format << ":"
- << decoder->time_base_numerator() << ":"
- << decoder->time_base_denominator() << ":"
- << decoder->sample_aspect_ratio_numerator() << ":"
- << decoder->sample_aspect_ratio_denominator();
-
- int r;
-
- if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph)) < 0) {
- throw DecodeError ("could not create buffer source");
+ a << "video_size=" << _size.width << "x" << _size.height << ":"
+ << "pix_fmt=" << _pixel_format << ":"
+ << "time_base=1/1:"
+ << "pixel_aspect=1/1";
+
+ if (avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph) < 0) {
+ throw DecodeError (N_("could not create buffer source"));
}
AVBufferSinkParams* sink_params = av_buffersink_params_alloc ();
@@ -102,99 +96,59 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, Size s,
pixel_fmts[1] = PIX_FMT_NONE;
sink_params->pixel_fmts = pixel_fmts;
- if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, "out", 0, sink_params, graph) < 0) {
- throw DecodeError ("could not create buffer sink.");
+ if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, N_("out"), 0, sink_params, graph) < 0) {
+ throw DecodeError (N_("could not create buffer sink."));
}
+ av_free (sink_params);
+
AVFilterInOut* outputs = avfilter_inout_alloc ();
- outputs->name = av_strdup("in");
+ outputs->name = av_strdup(N_("in"));
outputs->filter_ctx = _buffer_src_context;
outputs->pad_idx = 0;
outputs->next = 0;
AVFilterInOut* inputs = avfilter_inout_alloc ();
- inputs->name = av_strdup("out");
+ inputs->name = av_strdup(N_("out"));
inputs->filter_ctx = _buffer_sink_context;
inputs->pad_idx = 0;
inputs->next = 0;
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
- if (avfilter_graph_parse (graph, filters.c_str(), inputs, outputs, 0) < 0) {
- throw DecodeError ("could not set up filter graph.");
- }
-#else
if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) {
- throw DecodeError ("could not set up filter graph.");
+ throw DecodeError (N_("could not set up filter graph."));
}
-#endif
if (avfilter_graph_config (graph, 0) < 0) {
- throw DecodeError ("could not configure filter graph.");
+ throw DecodeError (N_("could not configure filter graph."));
}
/* XXX: leaking `inputs' / `outputs' ? */
}
+FilterGraph::~FilterGraph ()
+{
+ av_frame_free (&_frame);
+}
+
/** Take an AVFrame and process it using our configured filters, returning a
- * set of Images.
+ * set of Images. Caller handles memory management of the input frame.
*/
-list<shared_ptr<Image> >
-FilterGraph::process (AVFrame const * frame)
+list<pair<shared_ptr<Image>, int64_t> >
+FilterGraph::process (AVFrame* frame)
{
- list<shared_ptr<Image> > images;
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 61
-
- if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0) < 0) {
- throw DecodeError ("could not push buffer into filter chain.");
- }
-
-#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-
- AVRational par;
- par.num = sample_aspect_ratio_numerator ();
- par.den = sample_aspect_ratio_denominator ();
-
- if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0, par) < 0) {
- throw DecodeError ("could not push buffer into filter chain.");
- }
-
-#else
+ list<pair<shared_ptr<Image>, int64_t> > images;
if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) {
- throw DecodeError ("could not push buffer into filter chain.");
+ throw DecodeError (N_("could not push buffer into filter chain."));
}
-#endif
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15 && LIBAVFILTER_VERSION_MINOR <= 61
- while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) {
-#else
- while (av_buffersink_read (_buffer_sink_context, 0)) {
-#endif
-
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15
-
- int r = avfilter_request_frame (_buffer_sink_context->inputs[0]);
- if (r < 0) {
- throw DecodeError ("could not request filtered frame");
- }
-
- AVFilterBufferRef* filter_buffer = _buffer_sink_context->inputs[0]->cur_buf;
-
-#else
-
- AVFilterBufferRef* filter_buffer;
- if (av_buffersink_get_buffer_ref (_buffer_sink_context, &filter_buffer, 0) < 0) {
- filter_buffer = 0;
+ while (1) {
+ if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) {
+ break;
}
-#endif
-
- if (filter_buffer) {
- /* This takes ownership of filter_buffer */
- images.push_back (shared_ptr<Image> (new FilterBufferImage ((PixelFormat) frame->format, filter_buffer)));
- }
+ images.push_back (make_pair (shared_ptr<Image> (new Image (_frame)), av_frame_get_best_effort_timestamp (_frame)));
+ av_frame_unref (_frame);
}
return images;
@@ -205,7 +159,7 @@ FilterGraph::process (AVFrame const * frame)
* @return true if this chain can process images with `s' and `p', otherwise false.
*/
bool
-FilterGraph::can_process (Size s, AVPixelFormat p) const
+FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
{
return (_size == s && _pixel_format == p);
}
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index 9e6ac6252..9b403c2bc 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -21,32 +21,33 @@
* @brief A graph of FFmpeg filters.
*/
-#ifndef DVDOMATIC_FILTER_GRAPH_H
-#define DVDOMATIC_FILTER_GRAPH_H
+#ifndef DCPOMATIC_FILTER_GRAPH_H
+#define DCPOMATIC_FILTER_GRAPH_H
#include "util.h"
-#include "ffmpeg_compatibility.h"
class Image;
class VideoFilter;
-class FFmpegDecoder;
+class FFmpegContent;
/** @class FilterGraph
* @brief A graph of FFmpeg filters.
*/
-class FilterGraph
+class FilterGraph : public boost::noncopyable
{
public:
- FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+ ~FilterGraph ();
- bool can_process (Size s, AVPixelFormat p) const;
- std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
+ bool can_process (libdcp::Size s, AVPixelFormat p) const;
+ std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame);
private:
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
- Size _size; ///< size of the images that this chain can process
+ libdcp::Size _size; ///< size of the images that this chain can process
AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
+ AVFrame* _frame;
};
#endif
diff --git a/src/lib/format.cc b/src/lib/format.cc
deleted file mode 100644
index 975862411..000000000
--- a/src/lib/format.cc
+++ /dev/null
@@ -1,186 +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/format.cc
- * @brief Class to describe a format (aspect ratio) that a Film should
- * be shown in.
- */
-
-#include <sstream>
-#include <cstdlib>
-#include <cassert>
-#include <iomanip>
-#include <iostream>
-#include "format.h"
-#include "film.h"
-
-using std::string;
-using std::setprecision;
-using std::stringstream;
-using std::vector;
-using boost::shared_ptr;
-
-vector<Format const *> Format::_formats;
-
-/** @return A name to be presented to the user */
-string
-FixedFormat::name () const
-{
- stringstream s;
- if (!_nickname.empty ()) {
- s << _nickname << " (";
- }
-
- s << setprecision(3) << (_ratio / 100.0) << ":1";
-
- if (!_nickname.empty ()) {
- s << ")";
- }
-
- return s.str ();
-}
-
-/** @return Identifier for this format as metadata for a Film's metadata file */
-string
-Format::as_metadata () const
-{
- return _id;
-}
-
-/** Fill our _formats vector with all available formats */
-void
-Format::setup_formats ()
-{
- _formats.push_back (new FixedFormat (119, Size (1285, 1080), "119", "1.19", "F"));
- _formats.push_back (new FixedFormat (133, Size (1436, 1080), "133", "1.33", "F"));
- _formats.push_back (new FixedFormat (138, Size (1485, 1080), "138", "1.375", "F"));
- _formats.push_back (new FixedFormat (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat", "F"));
- _formats.push_back (new FixedFormat (137, Size (1480, 1080), "137", "Academy", "F"));
- _formats.push_back (new FixedFormat (166, Size (1793, 1080), "166", "1.66", "F"));
- _formats.push_back (new FixedFormat (166, Size (1998, 1080), "166-in-flat", "1.66 within Flat", "F"));
- _formats.push_back (new FixedFormat (178, Size (1998, 1080), "178-in-flat", "16:9 within Flat", "F"));
- _formats.push_back (new FixedFormat (178, Size (1920, 1080), "178", "16:9", "F"));
- _formats.push_back (new FixedFormat (185, Size (1998, 1080), "185", "Flat", "F"));
- _formats.push_back (new FixedFormat (239, Size (2048, 858), "239", "Scope", "S"));
- _formats.push_back (new VariableFormat (Size (1998, 1080), "var-185", "Flat", "F"));
- _formats.push_back (new VariableFormat (Size (2048, 858), "var-239", "Scope", "S"));
-}
-
-/** @param n Nickname.
- * @return Matching Format, or 0.
- */
-Format const *
-Format::from_nickname (string n)
-{
- vector<Format const *>::iterator i = _formats.begin ();
- while (i != _formats.end() && (*i)->nickname() != n) {
- ++i;
- }
-
- if (i == _formats.end ()) {
- return 0;
- }
-
- return *i;
-}
-
-/** @param i Id.
- * @return Matching Format, or 0.
- */
-Format const *
-Format::from_id (string i)
-{
- vector<Format const *>::iterator j = _formats.begin ();
- while (j != _formats.end() && (*j)->id() != i) {
- ++j;
- }
-
- if (j == _formats.end ()) {
- return 0;
- }
-
- return *j;
-}
-
-
-/** @param m Metadata, as returned from as_metadata().
- * @return Matching Format, or 0.
- */
-Format const *
-Format::from_metadata (string m)
-{
- return from_id (m);
-}
-
-/** @return All available formats */
-vector<Format const *>
-Format::all ()
-{
- return _formats;
-}
-
-/** @param r Ratio multiplied by 100 (e.g. 185)
- * @param dcp Size (in pixels) of the images that we should put in a DCP.
- * @param id ID (e.g. 185)
- * @param n Nick name (e.g. Flat)
- */
-FixedFormat::FixedFormat (int r, Size dcp, string id, string n, string d)
- : Format (dcp, id, n, d)
- , _ratio (r)
-{
-
-}
-
-int
-Format::dcp_padding (shared_ptr<const Film> f) const
-{
- int p = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_integer(f) / 100.0)) / 2.0);
-
- /* This comes out -ve for Scope; bodge it */
- if (p < 0) {
- p = 0;
- }
-
- return p;
-}
-
-VariableFormat::VariableFormat (Size dcp, string id, string n, string d)
- : Format (dcp, id, n, d)
-{
-
-}
-
-int
-VariableFormat::ratio_as_integer (shared_ptr<const Film> f) const
-{
- return rint (ratio_as_float (f) * 100);
-}
-
-float
-VariableFormat::ratio_as_float (shared_ptr<const Film> f) const
-{
- return float (f->size().width) / f->size().height;
-}
-
-/** @return A name to be presented to the user */
-string
-VariableFormat::name () const
-{
- return _nickname;
-}
diff --git a/src/lib/format.h b/src/lib/format.h
deleted file mode 100644
index 2118237a4..000000000
--- a/src/lib/format.h
+++ /dev/null
@@ -1,134 +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/format.h
- * @brief Classes to describe a format (aspect ratio) that a Film should
- * be shown in.
- */
-
-#include <string>
-#include <vector>
-#include "util.h"
-
-class Film;
-
-class Format
-{
-public:
- Format (Size dcp, std::string id, std::string n, std::string d)
- : _dcp_size (dcp)
- , _id (id)
- , _nickname (n)
- , _dci_name (d)
- {}
-
- /** @return the aspect ratio multiplied by 100
- * (e.g. 239 for Cinemascope 2.39:1)
- */
- virtual int ratio_as_integer (boost::shared_ptr<const Film> f) const = 0;
-
- /** @return the ratio as a floating point number */
- virtual float ratio_as_float (boost::shared_ptr<const Film> f) const = 0;
-
- int dcp_padding (boost::shared_ptr<const Film> f) const;
-
- /** @return size in pixels of the images that we should
- * put in a DCP for this ratio. This size will not correspond
- * to the ratio when we are doing things like 16:9 in a Flat frame.
- */
- Size dcp_size () const {
- return _dcp_size;
- }
-
- std::string id () const {
- return _id;
- }
-
- /** @return Full name to present to the user */
- virtual std::string name () const = 0;
-
- /** @return Nickname (e.g. Flat, Scope) */
- std::string nickname () const {
- return _nickname;
- }
-
- std::string dci_name () const {
- return _dci_name;
- }
-
- std::string as_metadata () const;
-
- static Format const * from_nickname (std::string n);
- static Format const * from_metadata (std::string m);
- static Format const * from_id (std::string i);
- static std::vector<Format const *> all ();
- static void setup_formats ();
-
-protected:
- /** Size in pixels of the images that we should
- * put in a DCP for this ratio. This size will not correspond
- * to the ratio when we are doing things like 16:9 in a Flat frame.
- */
- Size _dcp_size;
- /** id for use in metadata */
- std::string _id;
- /** nickname (e.g. Flat, Scope) */
- std::string _nickname;
- std::string _dci_name;
-
-private:
- /** all available formats */
- static std::vector<Format const *> _formats;
-};
-
-/** @class FixedFormat
- * @brief Class to describe a format whose ratio is fixed regardless
- * of source size.
- */
-class FixedFormat : public Format
-{
-public:
- FixedFormat (int, Size, std::string, std::string, std::string);
-
- int ratio_as_integer (boost::shared_ptr<const Film>) const {
- return _ratio;
- }
-
- float ratio_as_float (boost::shared_ptr<const Film>) const {
- return _ratio / 100.0;
- }
-
- std::string name () const;
-
-private:
-
- /** Ratio expressed as the actual ratio multiplied by 100 */
- int _ratio;
-};
-
-class VariableFormat : public Format
-{
-public:
- VariableFormat (Size, std::string, std::string, std::string);
-
- int ratio_as_integer (boost::shared_ptr<const Film> f) const;
- float ratio_as_float (boost::shared_ptr<const Film> f) const;
-
- std::string name () const;
-};
diff --git a/src/lib/audio_sink.h b/src/lib/i18n.h
index 11d578a60..890313bc6 100644
--- a/src/lib/audio_sink.h
+++ b/src/lib/i18n.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,14 +17,7 @@
*/
-#ifndef DVDOMATIC_AUDIO_SINK_H
-#define DVDOMATIC_AUDIO_SINK_H
+#include <libintl.h>
-class AudioSink
-{
-public:
- /** Call with some audio data */
- virtual void process_audio (boost::shared_ptr<AudioBuffers>) = 0;
-};
-
-#endif
+#define _(x) dgettext ("libdcpomatic", x)
+#define N_(x) x
diff --git a/src/lib/image.cc b/src/lib/image.cc
index f774f476f..dbea62d26 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -18,35 +18,39 @@
*/
/** @file src/image.cc
- * @brief A set of classes to describe video images.
+ * @brief A class to describe a video image.
*/
-#include <sstream>
-#include <iomanip>
#include <iostream>
-#include <sys/time.h>
-#include <boost/algorithm/string.hpp>
-#include <boost/bind.hpp>
-#include <openjpeg.h>
extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
-#include <libavfilter/avfiltergraph.h>
-#include <libpostproc/postprocess.h>
#include <libavutil/pixfmt.h>
+#include <libavutil/pixdesc.h>
+#include <libpostproc/postprocess.h>
}
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
-using namespace std;
-using namespace boost;
+using std::string;
+using std::min;
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
-void
-Image::swap (Image& other)
+int
+Image::line_factor (int n) const
{
- std::swap (_pixel_format, other._pixel_format);
+ if (n == 0) {
+ return 1;
+ }
+
+ AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ if (!d) {
+ throw PixelFormatError ("lines()", _pixel_format);
+ }
+
+ return pow (2.0f, d->log2_chroma_h);
}
/** @param n Component index.
@@ -55,55 +59,39 @@ Image::swap (Image& other)
int
Image::lines (int n) const
{
- switch (_pixel_format) {
- case PIX_FMT_YUV420P:
- if (n == 0) {
- return size().height;
- } else {
- return size().height / 2;
- }
- break;
- case PIX_FMT_RGB24:
- case PIX_FMT_RGBA:
- case PIX_FMT_YUV422P10LE:
- case PIX_FMT_YUV422P:
- return size().height;
- default:
- assert (false);
- }
-
- return 0;
+ return rint (ceil (static_cast<double>(size().height) / line_factor (n)));
}
/** @return Number of components */
int
Image::components () const
{
- switch (_pixel_format) {
- case PIX_FMT_YUV420P:
- case PIX_FMT_YUV422P10LE:
- case PIX_FMT_YUV422P:
- return 3;
- case PIX_FMT_RGB24:
- case PIX_FMT_RGBA:
- return 1;
- default:
- assert (false);
+ AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ if (!d) {
+ throw PixelFormatError ("components()", _pixel_format);
}
- return 0;
+ if ((d->flags & PIX_FMT_PLANAR) == 0) {
+ return 1;
+ }
+
+ return d->nb_components;
}
shared_ptr<Image>
-Image::scale (Size out_size, Scaler const * scaler, bool aligned) const
+Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat result_format, bool result_aligned) const
{
assert (scaler);
+ /* Empirical testing suggests that sws_scale() will crash if
+ the input image is not aligned.
+ */
+ assert (aligned ());
- shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned));
+ shared_ptr<Image> scaled (new Image (result_format, out_size, result_aligned));
struct SwsContext* scale_context = sws_getContext (
size().width, size().height, pixel_format(),
- out_size.width, out_size.height, pixel_format(),
+ out_size.width, out_size.height, result_format,
scaler->ffmpeg_id (), 0, 0, 0
);
@@ -119,61 +107,6 @@ Image::scale (Size out_size, Scaler const * scaler, bool aligned) const
return scaled;
}
-/** Scale this image to a given size and convert it to RGB.
- * @param out_size Output image size in pixels.
- * @param scaler Scaler to use.
- */
-shared_ptr<Image>
-Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const
-{
- assert (scaler);
-
- Size content_size = out_size;
- content_size.width -= (padding * 2);
-
- shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, aligned));
-
- struct SwsContext* scale_context = sws_getContext (
- size().width, size().height, pixel_format(),
- content_size.width, content_size.height, PIX_FMT_RGB24,
- scaler->ffmpeg_id (), 0, 0, 0
- );
-
- /* Scale and convert to RGB from whatever its currently in (which may be RGB) */
- sws_scale (
- scale_context,
- data(), stride(),
- 0, size().height,
- rgb->data(), rgb->stride()
- );
-
- /* Put the image in the right place in a black frame if are padding; this is
- a bit grubby and expensive, but probably inconsequential in the great
- scheme of things.
- */
- if (padding > 0) {
- shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, aligned));
- padded_rgb->make_black ();
-
- /* XXX: we are cheating a bit here; we know the frame is RGB so we can
- make assumptions about its composition.
- */
- uint8_t* p = padded_rgb->data()[0] + padding * 3;
- uint8_t* q = rgb->data()[0];
- for (int j = 0; j < rgb->lines(0); ++j) {
- memcpy (p, q, rgb->line_size()[0]);
- p += padded_rgb->stride()[0];
- q += rgb->stride()[0];
- }
-
- rgb = padded_rgb;
- }
-
- sws_freeContext (scale_context);
-
- return rgb;
-}
-
/** Run a FFmpeg post-process on this image and return the processed version.
* @param pp Flags for the required set of post processes.
* @return Post-processed image.
@@ -181,7 +114,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal
shared_ptr<Image>
Image::post_process (string pp, bool aligned) const
{
- shared_ptr<Image> out (new SimpleImage (pixel_format(), size (), aligned));
+ shared_ptr<Image> out (new Image (pixel_format(), size (), aligned));
int pp_format = 0;
switch (pixel_format()) {
@@ -190,10 +123,17 @@ Image::post_process (string pp, bool aligned) const
break;
case PIX_FMT_YUV422P10LE:
case PIX_FMT_YUV422P:
+ case PIX_FMT_UYVY422:
pp_format = PP_FORMAT_422;
break;
+ case PIX_FMT_YUV444P:
+ case PIX_FMT_YUV444P9BE:
+ case PIX_FMT_YUV444P9LE:
+ case PIX_FMT_YUV444P10BE:
+ case PIX_FMT_YUV444P10LE:
+ pp_format = PP_FORMAT_444;
default:
- assert (false);
+ throw PixelFormatError ("post_process", pixel_format());
}
pp_mode* mode = pp_get_mode_by_name_and_quality (pp.c_str (), PP_QUALITY_MAX);
@@ -215,53 +155,137 @@ Image::post_process (string pp, bool aligned) const
shared_ptr<Image>
Image::crop (Crop crop, bool aligned) const
{
- Size cropped_size = size ();
+ libdcp::Size cropped_size = size ();
cropped_size.width -= crop.left + crop.right;
cropped_size.height -= crop.top + crop.bottom;
- shared_ptr<Image> out (new SimpleImage (pixel_format(), cropped_size, aligned));
+ shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
for (int c = 0; c < components(); ++c) {
int const crop_left_in_bytes = bytes_per_pixel(c) * crop.left;
- int const cropped_width_in_bytes = bytes_per_pixel(c) * cropped_size.width;
-
+ /* bytes_per_pixel() could be a fraction; in this case the stride will be rounded
+ up, and we need to make sure that we copy over the width (up to the stride)
+ rather than short of the width; hence the ceil() here.
+ */
+ int const cropped_width_in_bytes = ceil (bytes_per_pixel(c) * cropped_size.width);
+
/* Start of the source line, cropped from the top but not the left */
- uint8_t* in_p = data()[c] + crop.top * stride()[c];
+ uint8_t* in_p = data()[c] + (crop.top / out->line_factor(c)) * stride()[c];
uint8_t* out_p = out->data()[c];
-
- for (int y = 0; y < cropped_size.height; ++y) {
+
+ for (int y = 0; y < out->lines(c); ++y) {
memcpy (out_p, in_p + crop_left_in_bytes, cropped_width_in_bytes);
- in_p += line_size()[c];
- out_p += out->line_size()[c];
+ in_p += stride()[c];
+ out_p += out->stride()[c];
}
}
return out;
}
+/** Blacken a YUV image whose bits per pixel is rounded up to 16 */
+void
+Image::yuv_16_black (uint16_t v)
+{
+ memset (data()[0], 0, lines(0) * stride()[0]);
+ for (int i = 1; i < 3; ++i) {
+ int16_t* p = reinterpret_cast<int16_t*> (data()[i]);
+ for (int y = 0; y < size().height; ++y) {
+ for (int x = 0; x < line_size()[i] / 2; ++x) {
+ p[x] = v;
+ }
+ p += stride()[i] / 2;
+ }
+ }
+}
+
+uint16_t
+Image::swap_16 (uint16_t v)
+{
+ return ((v >> 8) & 0xff) | ((v & 0xff) << 8);
+}
+
void
Image::make_black ()
{
+ /* U/V black value for 8-bit colour */
+ static uint8_t const eight_bit_uv = (1 << 7) - 1;
+ /* U/V black value for 9-bit colour */
+ static uint16_t const nine_bit_uv = (1 << 8) - 1;
+ /* U/V black value for 10-bit colour */
+ static uint16_t const ten_bit_uv = (1 << 9) - 1;
+ /* U/V black value for 16-bit colour */
+ static uint16_t const sixteen_bit_uv = (1 << 15) - 1;
+
switch (_pixel_format) {
case PIX_FMT_YUV420P:
- case PIX_FMT_YUV422P10LE:
case PIX_FMT_YUV422P:
+ case PIX_FMT_YUV444P:
memset (data()[0], 0, lines(0) * stride()[0]);
- memset (data()[1], 0x80, lines(1) * stride()[1]);
- memset (data()[2], 0x80, lines(2) * stride()[2]);
+ memset (data()[1], eight_bit_uv, lines(1) * stride()[1]);
+ memset (data()[2], eight_bit_uv, lines(2) * stride()[2]);
+ break;
+
+ case PIX_FMT_YUVJ420P:
+ case PIX_FMT_YUVJ422P:
+ case PIX_FMT_YUVJ444P:
+ memset (data()[0], 0, lines(0) * stride()[0]);
+ memset (data()[1], eight_bit_uv + 1, lines(1) * stride()[1]);
+ memset (data()[2], eight_bit_uv + 1, lines(2) * stride()[2]);
+ break;
+
+ case PIX_FMT_YUV422P9LE:
+ case PIX_FMT_YUV444P9LE:
+ yuv_16_black (nine_bit_uv);
+ break;
+
+ case PIX_FMT_YUV422P9BE:
+ case PIX_FMT_YUV444P9BE:
+ yuv_16_black (swap_16 (nine_bit_uv));
+ break;
+
+ case PIX_FMT_YUV422P10LE:
+ case PIX_FMT_YUV444P10LE:
+ yuv_16_black (ten_bit_uv);
+ break;
+
+ case PIX_FMT_YUV422P16LE:
+ case PIX_FMT_YUV444P16LE:
+ yuv_16_black (sixteen_bit_uv);
+ break;
+
+ case PIX_FMT_YUV444P10BE:
+ case PIX_FMT_YUV422P10BE:
+ yuv_16_black (swap_16 (ten_bit_uv));
break;
case PIX_FMT_RGB24:
memset (data()[0], 0, lines(0) * stride()[0]);
break;
+ case PIX_FMT_UYVY422:
+ {
+ int const Y = lines(0);
+ int const X = line_size()[0];
+ uint8_t* p = data()[0];
+ for (int y = 0; y < Y; ++y) {
+ for (int x = 0; x < X / 4; ++x) {
+ *p++ = eight_bit_uv; // Cb
+ *p++ = 0; // Y0
+ *p++ = eight_bit_uv; // Cr
+ *p++ = 0; // Y1
+ }
+ }
+ break;
+ }
+
default:
- assert (false);
+ throw PixelFormatError ("make_black()", _pixel_format);
}
}
void
-Image::alpha_blend (shared_ptr<const Image> other, Position position)
+Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
/* Only implemented for RGBA onto RGB24 so far */
assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
@@ -297,12 +321,27 @@ Image::alpha_blend (shared_ptr<const Image> other, Position position)
}
void
+Image::copy (shared_ptr<const Image> other, Position<int> position)
+{
+ /* Only implemented for RGB24 onto RGB24 so far */
+ assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGB24);
+ assert (position.x >= 0 && position.y >= 0);
+
+ int const N = min (position.x + other->size().width, size().width) - position.x;
+ for (int ty = position.y, oy = 0; ty < size().height && oy < other->size().height; ++ty, ++oy) {
+ uint8_t * const tp = data()[0] + ty * stride()[0] + position.x * 3;
+ uint8_t * const op = other->data()[0] + oy * other->stride()[0];
+ memcpy (tp, op, N * 3);
+ }
+}
+
+void
Image::read_from_socket (shared_ptr<Socket> socket)
{
for (int i = 0; i < components(); ++i) {
uint8_t* p = data()[i];
for (int y = 0; y < lines(i); ++y) {
- socket->read_definite_and_consume (p, line_size()[i], 30);
+ socket->read (p, line_size()[i]);
p += stride()[i];
}
}
@@ -314,7 +353,7 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
for (int i = 0; i < components(); ++i) {
uint8_t* p = data()[i];
for (int y = 0; y < lines(i); ++y) {
- socket->write (p, line_size()[i], 30);
+ socket->write (p, line_size()[i]);
p += stride()[i];
}
}
@@ -324,60 +363,52 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
float
Image::bytes_per_pixel (int c) const
{
- if (c == 3) {
+ AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ if (!d) {
+ throw PixelFormatError ("lines()", _pixel_format);
+ }
+
+ if (c >= components()) {
return 0;
}
+
+ float bpp[4] = { 0, 0, 0, 0 };
+
+ bpp[0] = floor ((d->comp[0].depth_minus1 + 1 + 7) / 8);
+ if (d->nb_components > 1) {
+ bpp[1] = floor ((d->comp[1].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
+ }
+ if (d->nb_components > 2) {
+ bpp[2] = floor ((d->comp[2].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
+ }
+ if (d->nb_components > 3) {
+ bpp[3] = floor ((d->comp[3].depth_minus1 + 1 + 7) / 8) / pow (2.0f, d->log2_chroma_w);
+ }
- switch (_pixel_format) {
- case PIX_FMT_RGB24:
- if (c == 0) {
- return 3;
- } else {
- return 0;
- }
- case PIX_FMT_RGBA:
- if (c == 0) {
- return 4;
- } else {
- return 0;
- }
- case PIX_FMT_YUV420P:
- case PIX_FMT_YUV422P:
- if (c == 0) {
- return 1;
- } else {
- return 0.5;
- }
- case PIX_FMT_YUV422P10LE:
- if (c == 1) {
- return 2;
- } else {
- return 1;
- }
- default:
- assert (false);
+ if ((d->flags & PIX_FMT_PLANAR) == 0) {
+ /* Not planar; sum them up */
+ return bpp[0] + bpp[1] + bpp[2] + bpp[3];
}
- return 0;
+ return bpp[c];
}
-
-/** Construct a SimpleImage of a given size and format, allocating memory
+/** Construct a Image of a given size and format, allocating memory
* as required.
*
* @param p Pixel format.
* @param s Size in pixels.
*/
-SimpleImage::SimpleImage (AVPixelFormat p, Size s, bool aligned)
- : Image (p)
- , _size (s)
+Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned)
+ : libdcp::Image (s)
+ , _pixel_format (p)
, _aligned (aligned)
{
allocate ();
}
void
-SimpleImage::allocate ()
+Image::allocate ()
{
_data = (uint8_t **) av_malloc (4 * sizeof (uint8_t *));
_data[0] = _data[1] = _data[2] = _data[3] = 0;
@@ -389,43 +420,96 @@ SimpleImage::allocate ()
_stride[0] = _stride[1] = _stride[2] = _stride[3] = 0;
for (int i = 0; i < components(); ++i) {
- _line_size[i] = _size.width * bytes_per_pixel(i);
+ _line_size[i] = ceil (_size.width * bytes_per_pixel(i));
_stride[i] = stride_round_up (i, _line_size, _aligned ? 32 : 1);
- _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i));
+
+ /* The assembler function ff_rgb24ToY_avx (in libswscale/x86/input.asm)
+ uses a 16-byte fetch to read three bytes (R/G/B) of image data.
+ Hence on the last pixel of the last line it reads over the end of
+ the actual data by 1 byte. If the width of an image is a multiple
+ of the stride alignment there will be no padding at the end of image lines.
+ OS X crashes on this illegal read, though other operating systems don't
+ seem to mind. The nasty + 1 in this malloc makes sure there is always a byte
+ for that instruction to read safely.
+ */
+ _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i) + 1);
}
}
-SimpleImage::SimpleImage (SimpleImage const & other)
- : Image (other)
+Image::Image (Image const & other)
+ : libdcp::Image (other)
+ , _pixel_format (other._pixel_format)
+ , _aligned (other._aligned)
+{
+ allocate ();
+
+ for (int i = 0; i < components(); ++i) {
+ uint8_t* p = _data[i];
+ uint8_t* q = other._data[i];
+ for (int j = 0; j < lines(i); ++j) {
+ memcpy (p, q, _line_size[i]);
+ p += stride()[i];
+ q += other.stride()[i];
+ }
+ }
+}
+
+Image::Image (AVFrame* frame)
+ : libdcp::Image (libdcp::Size (frame->width, frame->height))
+ , _pixel_format (static_cast<AVPixelFormat> (frame->format))
+ , _aligned (true)
{
- _size = other._size;
- _aligned = other._aligned;
-
allocate ();
for (int i = 0; i < components(); ++i) {
- memcpy (_data[i], other._data[i], _line_size[i] * lines(i));
+ uint8_t* p = _data[i];
+ uint8_t* q = frame->data[i];
+ for (int j = 0; j < lines(i); ++j) {
+ memcpy (p, q, _line_size[i]);
+ p += stride()[i];
+ /* AVFrame's linesize is what we call `stride' */
+ q += frame->linesize[i];
+ }
}
}
-SimpleImage&
-SimpleImage::operator= (SimpleImage const & other)
+Image::Image (shared_ptr<const Image> other, bool aligned)
+ : libdcp::Image (other)
+ , _pixel_format (other->_pixel_format)
+ , _aligned (aligned)
+{
+ allocate ();
+
+ for (int i = 0; i < components(); ++i) {
+ assert(line_size()[i] == other->line_size()[i]);
+ uint8_t* p = _data[i];
+ uint8_t* q = other->data()[i];
+ for (int j = 0; j < lines(i); ++j) {
+ memcpy (p, q, line_size()[i]);
+ p += stride()[i];
+ q += other->stride()[i];
+ }
+ }
+}
+
+Image&
+Image::operator= (Image const & other)
{
if (this == &other) {
return *this;
}
- SimpleImage tmp (other);
+ Image tmp (other);
swap (tmp);
return *this;
}
void
-SimpleImage::swap (SimpleImage & other)
+Image::swap (Image & other)
{
- Image::swap (other);
+ libdcp::Image::swap (other);
- std::swap (_size, other._size);
+ std::swap (_pixel_format, other._pixel_format);
for (int i = 0; i < 4; ++i) {
std::swap (_data[i], other._data[i]);
@@ -436,8 +520,8 @@ SimpleImage::swap (SimpleImage & other)
std::swap (_aligned, other._aligned);
}
-/** Destroy a SimpleImage */
-SimpleImage::~SimpleImage ()
+/** Destroy a Image */
+Image::~Image ()
{
for (int i = 0; i < components(); ++i) {
av_free (_data[i]);
@@ -449,91 +533,32 @@ SimpleImage::~SimpleImage ()
}
uint8_t **
-SimpleImage::data () const
+Image::data () const
{
return _data;
}
int *
-SimpleImage::line_size () const
+Image::line_size () const
{
return _line_size;
}
int *
-SimpleImage::stride () const
+Image::stride () const
{
return _stride;
}
-Size
-SimpleImage::size () const
+libdcp::Size
+Image::size () const
{
return _size;
}
-FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b)
- : Image (p)
- , _buffer (b)
-{
-
-}
-
-FilterBufferImage::~FilterBufferImage ()
-{
- avfilter_unref_buffer (_buffer);
-}
-
-uint8_t **
-FilterBufferImage::data () const
-{
- return _buffer->data;
-}
-
-int *
-FilterBufferImage::line_size () const
-{
- return _buffer->linesize;
-}
-
-int *
-FilterBufferImage::stride () const
-{
- /* XXX? */
- return _buffer->linesize;
-}
-
-Size
-FilterBufferImage::size () const
-{
- return Size (_buffer->video->w, _buffer->video->h);
-}
-
-RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im)
- : SimpleImage (im->pixel_format(), im->size(), false)
-{
- assert (im->pixel_format() == PIX_FMT_RGBA);
-
- _alpha = (uint8_t *) av_malloc (im->size().width * im->size().height);
-
- uint8_t* in = im->data()[0];
- uint8_t* out = data()[0];
- uint8_t* out_alpha = _alpha;
- for (int y = 0; y < im->size().height; ++y) {
- uint8_t* in_r = in;
- for (int x = 0; x < im->size().width; ++x) {
- *out++ = *in_r++;
- *out++ = *in_r++;
- *out++ = *in_r++;
- *out_alpha++ = *in_r++;
- }
-
- in += im->stride()[0];
- }
-}
-
-RGBPlusAlphaImage::~RGBPlusAlphaImage ()
+bool
+Image::aligned () const
{
- av_free (_alpha);
+ return _aligned;
}
diff --git a/src/lib/image.h b/src/lib/image.h
index e19c6f54b..6af74a8c5 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -21,8 +21,8 @@
* @brief A set of classes to describe video images.
*/
-#ifndef DVDOMATIC_IMAGE_H
-#define DVDOMATIC_IMAGE_H
+#ifndef DCPOMATIC_IMAGE_H
+#define DCPOMATIC_IMAGE_H
#include <string>
#include <boost/shared_ptr.hpp>
@@ -31,50 +31,36 @@ extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
}
+#include <libdcp/image.h>
#include "util.h"
-#include "ffmpeg_compatibility.h"
+#include "position.h"
class Scaler;
-class RGBFrameImage;
-class SimpleImage;
-/** @class Image
- * @brief Parent class for wrappers of some image, in some format, that
- * can present a set of components and a size in pixels.
- *
- * This class also has some conversion / processing methods.
- *
- * The main point of this class (and its subclasses) is to abstract
- * details of FFmpeg's memory management and varying data formats.
- */
-class Image
+class Image : public libdcp::Image
{
public:
- Image (AVPixelFormat p)
- : _pixel_format (p)
- {}
+ Image (AVPixelFormat, libdcp::Size, bool);
+ Image (AVFrame *);
+ Image (Image const &);
+ Image (boost::shared_ptr<const Image>, bool);
+ Image& operator= (Image const &);
+ ~Image ();
- virtual ~Image () {}
-
- /** @return Array of pointers to arrays of the component data */
- virtual uint8_t ** data () const = 0;
-
- /** @return Array of sizes of the data in each line, in bytes (without any alignment padding bytes) */
- virtual int * line_size () const = 0;
-
- /** @return Array of strides for each line (including any alignment padding bytes) */
- virtual int * stride () const = 0;
-
- /** @return Size of the image, in pixels */
- virtual Size size () const = 0;
+ uint8_t ** data () const;
+ int * line_size () const;
+ int * stride () const;
+ libdcp::Size size () const;
+ bool aligned () const;
int components () const;
+ int line_factor (int) const;
int lines (int) const;
- boost::shared_ptr<Image> scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const;
- boost::shared_ptr<Image> scale (Size, Scaler const *, bool aligned) const;
+ boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
boost::shared_ptr<Image> post_process (std::string, bool aligned) const;
- void alpha_blend (boost::shared_ptr<const Image> image, Position pos);
+ void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
+ void copy (boost::shared_ptr<const Image> image, Position<int> pos);
boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
void make_black ();
@@ -86,76 +72,20 @@ public:
return _pixel_format;
}
-protected:
- virtual void swap (Image &);
- float bytes_per_pixel (int) const;
-
-private:
- AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
-};
-
-/** @class FilterBufferImage
- * @brief An Image that is held in an AVFilterBufferRef.
- */
-class FilterBufferImage : public Image
-{
-public:
- FilterBufferImage (AVPixelFormat, AVFilterBufferRef *);
- ~FilterBufferImage ();
-
- uint8_t ** data () const;
- int * line_size () const;
- int * stride () const;
- Size size () const;
-
private:
- /* Not allowed */
- FilterBufferImage (FilterBufferImage const &);
- FilterBufferImage& operator= (FilterBufferImage const &);
+ friend class pixel_formats_test;
- AVFilterBufferRef* _buffer;
-};
-
-/** @class SimpleImage
- * @brief An Image for which memory is allocated using a `simple' av_malloc().
- */
-class SimpleImage : public Image
-{
-public:
- SimpleImage (AVPixelFormat, Size, bool);
- SimpleImage (SimpleImage const &);
- SimpleImage& operator= (SimpleImage const &);
- ~SimpleImage ();
-
- uint8_t ** data () const;
- int * line_size () const;
- int * stride () const;
- Size size () const;
-
-protected:
void allocate ();
- void swap (SimpleImage &);
+ void swap (Image &);
+ float bytes_per_pixel (int) const;
+ void yuv_16_black (uint16_t);
+ static uint16_t swap_16 (uint16_t);
-private:
- Size _size; ///< size in pixels
+ AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
uint8_t** _data; ///< array of pointers to components
int* _line_size; ///< array of sizes of the data in each line, in pixels (without any alignment padding bytes)
int* _stride; ///< array of strides for each line (including any alignment padding bytes)
bool _aligned;
};
-class RGBPlusAlphaImage : public SimpleImage
-{
-public:
- RGBPlusAlphaImage (boost::shared_ptr<const Image>);
- ~RGBPlusAlphaImage ();
-
- uint8_t* alpha () const {
- return _alpha;
- }
-
-private:
- uint8_t* _alpha;
-};
-
#endif
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
deleted file mode 100644
index bad1fb813..000000000
--- a/src/lib/imagemagick_decoder.cc
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <Magick++.h>
-#include "imagemagick_decoder.h"
-#include "image.h"
-#include "film.h"
-#include "exceptions.h"
-
-using std::cout;
-using boost::shared_ptr;
-
-ImageMagickDecoder::ImageMagickDecoder (
- boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
- : Decoder (f, o, j)
- , VideoDecoder (f, o, j)
-{
- 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
-{
- 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 ()
-{
- if (_iter == _files.end()) {
- if (!_film->dcp_length() || video_frame() >= _film->dcp_length().get()) {
- return true;
- }
-
- repeat_last_video ();
- return false;
- }
-
- Magick::Image* magick_image = new Magick::Image (_film->content_path ());
-
- Size size = native_size ();
- shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false));
-
- using namespace MagickCore;
-
- 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);
- *p++ = c.redQuantum() * 255 / QuantumRange;
- *p++ = c.greenQuantum() * 255 / QuantumRange;
- *p++ = c.blueQuantum() * 255 / QuantumRange;
- }
- }
-
- delete magick_image;
-
- image = image->crop (_film->crop(), false);
-
- emit_video (image, 0);
-
- ++_iter;
- return false;
-}
-
-PixelFormat
-ImageMagickDecoder::pixel_format () const
-{
- /* XXX: always true? */
- return PIX_FMT_RGB24;
-}
-
-bool
-ImageMagickDecoder::seek_to_last ()
-{
- if (_iter == _files.end()) {
- _iter = _files.begin();
- } else {
- --_iter;
- }
-
- return false;
-}
-
-bool
-ImageMagickDecoder::seek (double t)
-{
- int const f = t * frames_per_second();
-
- _iter = _files.begin ();
- for (int i = 0; i < f; ++i) {
- if (_iter == _files.end()) {
- return true;
- }
- ++_iter;
- }
-
- return false;
-}
-
-void
-ImageMagickDecoder::film_changed (Film::Property p)
-{
- if (p == Film::CROP) {
- OutputChanged ();
- }
-}
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
deleted file mode 100644
index 6f426f308..000000000
--- a/src/lib/imagemagick_decoder.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "video_decoder.h"
-
-namespace Magick {
- class Image;
-}
-
-class ImageMagickDecoder : public VideoDecoder
-{
-public:
- ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
-
- float frames_per_second () const {
- /* We don't know */
- return 0;
- }
-
- Size native_size () const;
-
- SourceFrame length () const {
- /* We don't know */
- return 0;
- }
-
- int audio_channels () const {
- return 0;
- }
-
- int audio_sample_rate () const {
- return 0;
- }
-
- int64_t audio_channel_layout () const {
- return 0;
- }
-
- bool has_subtitles () const {
- return false;
- }
-
- bool seek (double);
- bool seek_to_last ();
-
-protected:
- bool pass ();
- PixelFormat pixel_format () const;
-
- int time_base_numerator () const {
- return 0;
- }
-
- int time_base_denominator () const {
- return 0;
- }
-
- int sample_aspect_ratio_numerator () const {
- /* XXX */
- return 1;
- }
-
- int sample_aspect_ratio_denominator () const {
- /* XXX */
- return 1;
- }
-
-private:
- void film_changed (Film::Property);
-
- std::list<std::string> _files;
- std::list<std::string>::iterator _iter;
-};
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 896862d14..8924fa09c 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -26,21 +26,25 @@
#include <libdcp/exceptions.h>
#include "job.h"
#include "util.h"
+#include "cross.h"
+#include "ui_signaller.h"
+#include "exceptions.h"
+
+#include "i18n.h"
using std::string;
using std::list;
+using std::cout;
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)
+Job::Job (shared_ptr<const Film> f)
: _film (f)
- , _required (req)
+ , _thread (0)
, _state (NEW)
, _start_time (0)
, _progress_unknown (false)
+ , _last_set (0)
, _ran_for (0)
{
descend (1);
@@ -52,7 +56,7 @@ Job::start ()
{
set_state (RUNNING);
_start_time = time (0);
- boost::thread (boost::bind (&Job::run_wrapper, this));
+ _thread = new boost::thread (boost::bind (&Job::run_wrapper, this));
}
/** A wrapper for the ::run() method to catch exceptions */
@@ -67,19 +71,52 @@ Job::run_wrapper ()
set_progress (1);
set_state (FINISHED_ERROR);
- set_error (String::compose ("%1 (%2)", e.what(), boost::filesystem::path (e.filename()).leaf()));
+
+ string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
+
+ try {
+ boost::filesystem::space_info const s = boost::filesystem::space (e.filename());
+ if (s.available < pow (1024, 3)) {
+ m += N_("\n\n");
+ m += _("The drive that the film is stored on is low in disc space. Free some more space and try again.");
+ }
+ } catch (...) {
+
+ }
+
+ set_error (e.what(), m);
+
+ } catch (OpenFileError& e) {
+
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+
+ set_error (
+ String::compose (_("Could not open %1"), e.file().string()),
+ String::compose (_("DCP-o-matic could not open the file %1. Perhaps it does not exist or is in an unexpected format."), e.file().string())
+ );
+
+ } catch (boost::thread_interrupted &) {
+
+ set_state (FINISHED_CANCELLED);
} catch (std::exception& e) {
set_progress (1);
set_state (FINISHED_ERROR);
- set_error (e.what ());
+ set_error (
+ e.what (),
+ _("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)")
+ );
} catch (...) {
set_progress (1);
set_state (FINISHED_ERROR);
- set_error ("unknown exception");
+ set_error (
+ _("Unknown error"),
+ _("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)")
+ );
}
}
@@ -105,7 +142,7 @@ bool
Job::finished () const
{
boost::mutex::scoped_lock lm (_state_mutex);
- return _state == FINISHED_OK || _state == FINISHED_ERROR;
+ return _state == FINISHED_OK || _state == FINISHED_ERROR || _state == FINISHED_CANCELLED;
}
/** @return true if the job has finished successfully */
@@ -124,18 +161,40 @@ Job::finished_in_error () const
return _state == FINISHED_ERROR;
}
+bool
+Job::finished_cancelled () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state == FINISHED_CANCELLED;
+}
+
+bool
+Job::paused () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state == PAUSED;
+}
+
/** Set the state of this job.
* @param s New state.
*/
void
Job::set_state (State s)
{
- boost::mutex::scoped_lock lm (_state_mutex);
- _state = s;
+ bool finished = false;
+
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _state = s;
+
+ if (_state == FINISHED_OK || _state == FINISHED_ERROR || _state == FINISHED_CANCELLED) {
+ _ran_for = elapsed_time ();
+ finished = true;
+ }
+ }
- if (_state == FINISHED_OK || _state == FINISHED_ERROR) {
- _ran_for = elapsed_time ();
- Finished ();
+ if (finished && ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (Finished)));
}
}
@@ -156,9 +215,25 @@ Job::elapsed_time () const
void
Job::set_progress (float p)
{
+ if (fabs (p - _last_set) < 0.01) {
+ /* Calm excessive progress reporting */
+ return;
+ }
+
+ _last_set = p;
+
boost::mutex::scoped_lock lm (_progress_mutex);
_progress_unknown = false;
_stack.back().normalised = p;
+ boost::this_thread::interruption_point ();
+
+ if (paused ()) {
+ dcpomatic_sleep (1);
+ }
+
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (Progress)));
+ }
}
/** @return fractional overall progress, or -1 if not known */
@@ -211,22 +286,30 @@ Job::descend (float a)
_stack.push_back (Level (a));
}
-/** @return Any error string that the job has generated */
string
-Job::error () const
+Job::error_details () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _error_details;
+}
+
+/** @return A summary of any error that the job has generated */
+string
+Job::error_summary () const
{
boost::mutex::scoped_lock lm (_state_mutex);
- return _error;
+ return _error_summary;
}
/** Set the current error string.
* @param e New error string.
*/
void
-Job::set_error (string e)
+Job::set_error (string s, string d)
{
boost::mutex::scoped_lock lm (_state_mutex);
- _error = e;
+ _error_summary = s;
+ _error_details = d;
}
/** Say that this job's progress will be unknown until further notice */
@@ -245,15 +328,26 @@ Job::status () const
int const t = elapsed_time ();
int const r = remaining_time ();
+ int pc = rint (p * 100);
+ if (pc == 100) {
+ /* 100% makes it sound like we've finished when we haven't */
+ pc = 99;
+ }
+
stringstream s;
- if (!finished () && p >= 0 && t > 10 && r > 0) {
- s << rint (p * 100) << "%; " << seconds_to_approximate_hms (r) << " remaining";
- } else if (!finished () && (t <= 10 || r == 0)) {
- s << rint (p * 100) << "%";
+ if (!finished ()) {
+ s << pc << N_("%");
+ if (p >= 0 && t > 10 && r > 0) {
+ /// TRANSLATORS: remaining here follows an amount of time that is remaining
+ /// on an operation.
+ s << "; " << seconds_to_approximate_hms (r) << " " << _("remaining");
+ }
} else if (finished_ok ()) {
- s << "OK (ran for " << seconds_to_hms (_ran_for) << ")";
+ s << String::compose (_("OK (ran for %1)"), seconds_to_hms (_ran_for));
} else if (finished_in_error ()) {
- s << "Error (" << error() << ")";
+ s << String::compose (_("Error (%1)"), error_summary());
+ } else if (finished_cancelled ()) {
+ s << _("Cancelled");
}
return s.str ();
@@ -265,3 +359,30 @@ Job::remaining_time () const
{
return elapsed_time() / overall_progress() - elapsed_time();
}
+
+void
+Job::cancel ()
+{
+ if (!_thread) {
+ return;
+ }
+
+ _thread->interrupt ();
+ _thread->join ();
+}
+
+void
+Job::pause ()
+{
+ if (running ()) {
+ set_state (PAUSED);
+ }
+}
+
+void
+Job::resume ()
+{
+ if (paused ()) {
+ set_state (RUNNING);
+ }
+}
diff --git a/src/lib/job.h b/src/lib/job.h
index f32cfa811..9b8b14a93 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -21,23 +21,24 @@
* @brief A parent class to represent long-running tasks which are run in their own thread.
*/
-#ifndef DVDOMATIC_JOB_H
-#define DVDOMATIC_JOB_H
+#ifndef DCPOMATIC_JOB_H
+#define DCPOMATIC_JOB_H
#include <string>
#include <boost/thread/mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/signals2.hpp>
+#include <boost/thread.hpp>
class Film;
/** @class Job
* @brief A parent class to represent long-running tasks which are run in their own thread.
*/
-class Job : public boost::enable_shared_from_this<Job>
+class Job : public boost::enable_shared_from_this<Job>, public boost::noncopyable
{
public:
- Job (boost::shared_ptr<Film> s, boost::shared_ptr<Job> req);
+ Job (boost::shared_ptr<const Film>);
virtual ~Job() {}
/** @return user-readable name of this job */
@@ -46,14 +47,20 @@ public:
virtual void run () = 0;
void start ();
+ void pause ();
+ void resume ();
+ void cancel ();
bool is_new () const;
bool running () const;
bool finished () const;
bool finished_ok () const;
bool finished_in_error () const;
+ bool finished_cancelled () const;
+ bool paused () const;
- std::string error () const;
+ std::string error_summary () const;
+ std::string error_details () const;
int elapsed_time () const;
virtual std::string status () const;
@@ -63,11 +70,12 @@ public:
void ascend ();
void descend (float);
float overall_progress () const;
-
- boost::shared_ptr<Job> required () const {
- return _required;
+ bool progress_unknown () const {
+ return _progress_unknown;
}
+ boost::signals2::signal<void()> Progress;
+ /** Emitted from the UI thread when the job is finished */
boost::signals2::signal<void()> Finished;
protected:
@@ -76,30 +84,32 @@ protected:
/** Description of a job's state */
enum State {
- NEW, ///< the job hasn't been started yet
- RUNNING, ///< the job is running
- FINISHED_OK, ///< the job has finished successfully
- FINISHED_ERROR ///< the job has finished in error
+ NEW, ///< the job hasn't been started yet
+ RUNNING, ///< the job is running
+ PAUSED, ///< the job has been paused
+ FINISHED_OK, ///< the job has finished successfully
+ FINISHED_ERROR, ///< the job has finished in error
+ FINISHED_CANCELLED ///< the job was cancelled
};
void set_state (State);
- void set_error (std::string e);
+ void set_error (std::string s, std::string d);
- /** Film for this job */
- boost::shared_ptr<Film> _film;
+ boost::shared_ptr<const Film> _film;
private:
void run_wrapper ();
- boost::shared_ptr<Job> _required;
+ boost::thread* _thread;
/** mutex for _state and _error */
mutable boost::mutex _state_mutex;
/** current state of the job */
State _state;
- /** message for an error that has occurred (when state == FINISHED_ERROR) */
- std::string _error;
+ /** summary of an error that has occurred (when state == FINISHED_ERROR) */
+ std::string _error_summary;
+ std::string _error_details;
/** time that this job was started */
time_t _start_time;
@@ -119,6 +129,8 @@ private:
/** true if this job's progress will always be unknown */
bool _progress_unknown;
+ float _last_set;
+
int _ran_for;
};
diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc
index fa02fd370..a841fa60b 100644
--- a/src/lib/job_manager.cc
+++ b/src/lib/job_manager.cc
@@ -30,7 +30,9 @@
using std::string;
using std::list;
+using std::cout;
using boost::shared_ptr;
+using boost::weak_ptr;
JobManager* JobManager::_instance = 0;
@@ -43,19 +45,16 @@ JobManager::JobManager ()
shared_ptr<Job>
JobManager::add (shared_ptr<Job> j)
{
- boost::mutex::scoped_lock lm (_mutex);
- _jobs.push_back (j);
- return j;
-}
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _jobs.push_back (j);
+ }
-void
-JobManager::add_after (shared_ptr<Job> after, shared_ptr<Job> j)
-{
- boost::mutex::scoped_lock lm (_mutex);
- list<shared_ptr<Job> >::iterator i = find (_jobs.begin(), _jobs.end(), after);
- assert (i != _jobs.end ());
- ++i;
- _jobs.insert (i, j);
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (JobAdded), weak_ptr<Job> (j)));
+ }
+
+ return j;
}
list<shared_ptr<Job> >
@@ -111,13 +110,10 @@ JobManager::scheduler ()
}
if ((*i)->is_new()) {
- shared_ptr<Job> r = (*i)->required ();
- if (!r || r->finished_ok ()) {
- (*i)->start ();
-
- /* Only start one job at once */
- break;
- }
+ (*i)->start ();
+
+ /* Only start one job at once */
+ break;
}
}
}
@@ -129,7 +125,7 @@ JobManager::scheduler ()
}
}
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
}
}
diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h
index cc1c1d28f..0040568c6 100644
--- a/src/lib/job_manager.h
+++ b/src/lib/job_manager.h
@@ -30,21 +30,24 @@ class Job;
/** @class JobManager
* @brief A simple scheduler for jobs.
*/
-class JobManager
+class JobManager : public boost::noncopyable
{
public:
boost::shared_ptr<Job> add (boost::shared_ptr<Job>);
- void add_after (boost::shared_ptr<Job> after, boost::shared_ptr<Job> j);
std::list<boost::shared_ptr<Job> > get () const;
bool work_to_do () const;
bool errors () const;
+ boost::signals2::signal<void (boost::weak_ptr<Job>)> JobAdded;
boost::signals2::signal<void (bool)> ActiveJobsChanged;
static JobManager* instance ();
private:
+ /* This function is part of the test suite */
+ friend void ::wait_for_jobs ();
+
JobManager ();
void scheduler ();
diff --git a/src/lib/log.cc b/src/lib/log.cc
index 06cff0495..ef36a902c 100644
--- a/src/lib/log.cc
+++ b/src/lib/log.cc
@@ -25,10 +25,12 @@
#include <time.h>
#include "log.h"
+#include "i18n.h"
+
using namespace std;
Log::Log ()
- : _level (VERBOSE)
+ : _level (STANDARD)
{
}
@@ -48,7 +50,7 @@ Log::log (string m, Level l)
string a = ctime (&t);
stringstream s;
- s << a.substr (0, a.length() - 1) << ": " << m;
+ s << a.substr (0, a.length() - 1) << N_(": ") << m;
do_log (s.str ());
}
@@ -65,7 +67,7 @@ Log::microsecond_log (string m, Level l)
gettimeofday (&tv, 0);
stringstream s;
- s << tv.tv_sec << ":" << tv.tv_usec << " " << m;
+ s << tv.tv_sec << N_(":") << tv.tv_usec << N_(" ") << m;
do_log (s.str ());
}
@@ -79,10 +81,10 @@ Log::set_level (Level l)
void
Log::set_level (string l)
{
- if (l == "verbose") {
+ if (l == N_("verbose")) {
set_level (VERBOSE);
return;
- } else if (l == "timing") {
+ } else if (l == N_("timing")) {
set_level (TIMING);
return;
}
@@ -101,6 +103,6 @@ void
FileLog::do_log (string m)
{
ofstream f (_file.c_str(), fstream::app);
- f << m << "\n";
+ f << m << N_("\n");
}
diff --git a/src/lib/log.h b/src/lib/log.h
index 3a2cfcbfd..bd0066a83 100644
--- a/src/lib/log.h
+++ b/src/lib/log.h
@@ -17,8 +17,8 @@
*/
-#ifndef DVDOMATIC_LOG_H
-#define DVDOMATIC_LOG_H
+#ifndef DCPOMATIC_LOG_H
+#define DCPOMATIC_LOG_H
/** @file src/log.h
* @brief A very simple logging class.
@@ -30,7 +30,7 @@
/** @class Log
* @brief A very simple logging class.
*/
-class Log
+class Log : public boost::noncopyable
{
public:
Log ();
diff --git a/src/lib/lut.h b/src/lib/lut.h
deleted file mode 100644
index 26888a24a..000000000
--- a/src/lib/lut.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- Taken from OpenDCP: Builds Digital Cinema Packages
- Copyright (c) 2010-2011 Terrence Meiczinger, All Rights Reserved
-
- 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 3 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, see <http://www.gnu.org/licenses/>.
-*/
-
-/** @file src/lut.h
- * @brief Look-up tables for colour conversions (from OpenDCP)
- */
-
-#define BIT_DEPTH 12
-#define BIT_PRECISION 16
-#define COLOR_DEPTH (4095)
-#define DCI_LUT_SIZE ((COLOR_DEPTH + 1) * BIT_PRECISION)
-#define DCI_GAMMA (2.6)
-#define DCI_DEGAMMA (1/DCI_GAMMA)
-#define DCI_COEFFICENT (48.0/52.37)
-
-enum COLOR_PROFILE_ENUM {
- CP_SRGB = 0,
- CP_REC709,
- CP_DC28,
- CP_MAX
-};
-
-enum LUT_IN_ENUM {
- LI_SRGB = 0,
- LI_REC709,
- LI_MAX
-};
-
-enum LUT_OUT_ENUM {
- LO_DCI = 0,
- LO_MAX
-};
-
-extern float color_matrix[3][3][3];
-extern float lut_in[LI_MAX][4095+1];
-extern int lut_out[1][DCI_LUT_SIZE];
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
deleted file mode 100644
index 2b7a080fc..000000000
--- a/src/lib/matcher.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "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, bool same, boost::shared_ptr<Subtitle> s)
-{
- Video (i, same, 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 SimpleImage (_pixel_format.get(), _size.get(), false));
- black->make_black ();
- for (int i = 0; i < black_video_frames; ++i) {
- Video (black, i != 0, 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
deleted file mode 100644
index b94c28446..000000000
--- a/src/lib/matcher.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/optional.hpp>
-#include "processor.h"
-#include "ffmpeg_compatibility.h"
-
-class Matcher : public AudioVideoProcessor
-{
-public:
- Matcher (Log* log, int sample_rate, float frames_per_second);
- void process_video (boost::shared_ptr<Image> i, bool, 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/video_source.cc b/src/lib/moving_image.h
index 56742e2b4..a81403dbd 100644
--- a/src/lib/video_source.cc
+++ b/src/lib/moving_image.h
@@ -17,14 +17,19 @@
*/
-#include "video_source.h"
-#include "video_sink.h"
+class MovingImageContent;
-using boost::shared_ptr;
-using boost::bind;
-
-void
-VideoSource::connect_video (shared_ptr<VideoSink> s)
+class MovingImage
{
- Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
-}
+public:
+ MovingImage (boost::shared_ptr<const MovingImageContent> c)
+ : _moving_image_content (c)
+ {}
+
+ boost::shared_ptr<const MovingImageContent> content () const {
+ return _moving_image_content;
+ }
+
+protected:
+ boost::shared_ptr<const MovingImageContent> _moving_image_content;
+};
diff --git a/src/lib/moving_image_content.cc b/src/lib/moving_image_content.cc
new file mode 100644
index 000000000..a72ad6e8e
--- /dev/null
+++ b/src/lib/moving_image_content.cc
@@ -0,0 +1,118 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "moving_image_content.h"
+#include "moving_image_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::list;
+using std::stringstream;
+using std::vector;
+using boost::shared_ptr;
+
+MovingImageContent::MovingImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , VideoContent (f, p)
+{
+
+}
+
+MovingImageContent::MovingImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , VideoContent (f, node)
+{
+ list<shared_ptr<cxml::Node> > c = node->node_children ("File");
+ for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+ _files.push_back ((*i)->content ());
+ }
+}
+
+string
+MovingImageContent::summary () const
+{
+ /* Get the string() here so that the name does not have quotes around it */
+ return String::compose (_("%1 [moving images]"), path().filename().string());
+}
+
+string
+MovingImageContent::technical_summary () const
+{
+ return Content::technical_summary() + " - "
+ + VideoContent::technical_summary() + " - "
+ + "moving";
+}
+
+void
+MovingImageContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("MovingImage");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+
+ for (vector<boost::filesystem::path>::const_iterator i = _files.begin(); i != _files.end(); ++i) {
+ node->add_child("File")->add_child_text (i->filename().string());
+ }
+}
+
+void
+MovingImageContent::examine (shared_ptr<Job> job)
+{
+ job->descend (0.5);
+ Content::examine (job);
+ job->ascend ();
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ job->descend (0.5);
+ shared_ptr<MovingImageExaminer> examiner (new MovingImageExaminer (film, shared_from_this(), job));
+ job->ascend ();
+
+ take_from_video_examiner (examiner);
+
+ _video_length = examiner->files().size ();
+ _files = examiner->files ();
+}
+
+Time
+MovingImageContent::full_length () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+ return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
+}
+
+string
+MovingImageContent::identifier () const
+{
+ stringstream s;
+ s << VideoContent::identifier ();
+ s << "_" << video_length();
+ return s.str ();
+}
diff --git a/src/lib/moving_image_content.h b/src/lib/moving_image_content.h
new file mode 100644
index 000000000..1a64750fe
--- /dev/null
+++ b/src/lib/moving_image_content.h
@@ -0,0 +1,57 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_MOVING_IMAGE_CONTENT_H
+#define DCPOMATIC_MOVING_IMAGE_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+ class Node;
+}
+
+/** A directory of image files which are to be presented as a movie */
+class MovingImageContent : public VideoContent
+{
+public:
+ MovingImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ MovingImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<MovingImageContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<MovingImageContent> (Content::shared_from_this ());
+ };
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ void as_xml (xmlpp::Node *) const;
+ Time full_length () const;
+
+ std::string identifier () const;
+
+ std::vector<boost::filesystem::path> const & files () const {
+ return _files;
+ }
+
+private:
+ std::vector<boost::filesystem::path> _files;
+};
+
+#endif
diff --git a/src/lib/moving_image_decoder.cc b/src/lib/moving_image_decoder.cc
new file mode 100644
index 000000000..096096063
--- /dev/null
+++ b/src/lib/moving_image_decoder.cc
@@ -0,0 +1,85 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/filesystem.hpp>
+#include <Magick++.h>
+#include "moving_image_content.h"
+#include "moving_image_decoder.h"
+#include "image.h"
+#include "film.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
+
+MovingImageDecoder::MovingImageDecoder (shared_ptr<const Film> f, shared_ptr<const MovingImageContent> c)
+ : Decoder (f)
+ , VideoDecoder (f, c)
+ , MovingImage (c)
+{
+
+}
+
+void
+MovingImageDecoder::pass ()
+{
+ if (_video_position >= _moving_image_content->video_length ()) {
+ return;
+ }
+
+ boost::filesystem::path path = _moving_image_content->path ();
+ path /= _moving_image_content->files()[_video_position];
+
+ Magick::Image* magick_image = new Magick::Image (path.string());
+ libdcp::Size size (magick_image->columns(), magick_image->rows());
+
+ shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, false));
+
+ using namespace MagickCore;
+
+ 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);
+ *p++ = c.redQuantum() * 255 / QuantumRange;
+ *p++ = c.greenQuantum() * 255 / QuantumRange;
+ *p++ = c.blueQuantum() * 255 / QuantumRange;
+ }
+ }
+
+ delete magick_image;
+
+ video (image, false, _video_position);
+}
+
+void
+MovingImageDecoder::seek (VideoContent::Frame frame, bool)
+{
+ _video_position = frame;
+}
+
+bool
+MovingImageDecoder::done () const
+{
+ return _video_position >= _moving_image_content->video_length ();
+}
diff --git a/src/lib/moving_image_decoder.h b/src/lib/moving_image_decoder.h
new file mode 100644
index 000000000..5cc8b32b9
--- /dev/null
+++ b/src/lib/moving_image_decoder.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "video_decoder.h"
+#include "moving_image.h"
+
+namespace Magick {
+ class Image;
+}
+
+class MovingImageContent;
+
+class MovingImageDecoder : public VideoDecoder, public MovingImage
+{
+public:
+ MovingImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>);
+
+ /* Decoder */
+
+ void pass ();
+ void seek (VideoContent::Frame, bool);
+ bool done () const;
+};
+
diff --git a/src/lib/moving_image_examiner.cc b/src/lib/moving_image_examiner.cc
new file mode 100644
index 000000000..077545aed
--- /dev/null
+++ b/src/lib/moving_image_examiner.cc
@@ -0,0 +1,110 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+#include <Magick++.h>
+#include "moving_image_content.h"
+#include "moving_image_examiner.h"
+#include "film.h"
+#include "job.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using std::list;
+using std::sort;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+MovingImageExaminer::MovingImageExaminer (shared_ptr<const Film> film, shared_ptr<const MovingImageContent> content, shared_ptr<Job> job)
+ : MovingImage (content)
+ , _film (film)
+ , _video_length (0)
+{
+ list<unsigned int> frames;
+ unsigned int files = 0;
+
+ for (boost::filesystem::directory_iterator i(content->path()); i != boost::filesystem::directory_iterator(); ++i) {
+ if (boost::filesystem::is_regular_file (i->path ())) {
+ ++files;
+ }
+ }
+
+ int j = 0;
+ for (boost::filesystem::directory_iterator i(content->path()); i != boost::filesystem::directory_iterator(); ++i) {
+ if (!boost::filesystem::is_regular_file (i->path ())) {
+ continue;
+ }
+
+ if (valid_image_file (i->path ())) {
+ int n = lexical_cast<int> (i->path().stem().string());
+ frames.push_back (n);
+ _files.push_back (i->path().filename ());
+
+ if (!_video_size) {
+ using namespace MagickCore;
+ Magick::Image* image = new Magick::Image (i->path().string());
+ _video_size = libdcp::Size (image->columns(), image->rows());
+ delete image;
+ }
+ }
+
+ job->set_progress (float (j) / files);
+ ++j;
+ }
+
+ frames.sort ();
+ sort (_files.begin(), _files.end ());
+
+ if (frames.size() < 2) {
+ throw StringError (String::compose (_("only %1 file(s) found in moving image directory"), frames.size ()));
+ }
+
+ if (frames.front() != 0 && frames.front() != 1) {
+ throw StringError (String::compose (_("first frame in moving image directory is number %1"), frames.front ()));
+ }
+
+ if (frames.back() != frames.size() && frames.back() != (frames.size() - 1)) {
+ throw StringError (String::compose (_("there are %1 images in the directory but the last one is number %2"), frames.size(), frames.back ()));
+ }
+
+ _video_length = frames.size ();
+}
+
+libdcp::Size
+MovingImageExaminer::video_size () const
+{
+ return _video_size.get ();
+}
+
+int
+MovingImageExaminer::video_length () const
+{
+ cout << "ex video length is " << _video_length << "\n";
+ return _video_length;
+}
+
+float
+MovingImageExaminer::video_frame_rate () const
+{
+ return 24;
+}
+
diff --git a/src/lib/combiner.h b/src/lib/moving_image_examiner.h
index 7fad1aeae..db6845ee5 100644
--- a/src/lib/combiner.h
+++ b/src/lib/moving_image_examiner.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,26 +17,31 @@
*/
-/** @file src/lib/combiner.h
- * @brief Class for combining two video streams.
- */
+#include "moving_image.h"
+#include "video_examiner.h"
-#include "processor.h"
+namespace Magick {
+ class Image;
+}
-/** @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
+class MovingImageContent;
+
+class MovingImageExaminer : public MovingImage, public VideoExaminer
{
public:
- Combiner (Log* log);
+ MovingImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>, boost::shared_ptr<Job>);
+
+ float video_frame_rate () const;
+ libdcp::Size video_size () const;
+ VideoContent::Frame video_length () const;
- void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
- void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s);
+ std::vector<boost::filesystem::path> const & files () const {
+ return _files;
+ }
private:
- /** The image that we are currently working on */
- boost::shared_ptr<Image> _image;
+ boost::weak_ptr<const Film> _film;
+ boost::optional<libdcp::Size> _video_size;
+ VideoContent::Frame _video_length;
+ std::vector<boost::filesystem::path> _files;
};
diff --git a/src/lib/options.h b/src/lib/options.h
deleted file mode 100644
index 55b066a2d..000000000
--- a/src/lib/options.h
+++ /dev/null
@@ -1,128 +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/options.h
- * @brief Options for a transcoding operation.
- */
-
-#include <string>
-#include <iomanip>
-#include <sstream>
-#include <boost/optional.hpp>
-#include "util.h"
-
-/** @class EncodeOptions
- * @brief EncodeOptions for an encoding operation.
- *
- * These are settings which may be different, in different circumstances, for
- * the same film; ie they are options for a particular operation.
- */
-class EncodeOptions
-{
-public:
-
- EncodeOptions (std::string f, std::string e, std::string m)
- : padding (0)
- , video_skip (0)
- , _frame_out_path (f)
- , _frame_out_extension (e)
- , _multichannel_audio_out_path (m)
- {}
-
- /** @return The path to write video frames to */
- std::string frame_out_path () const {
- return _frame_out_path;
- }
-
- /** @param f Source frame index.
- * @param t true to return a temporary file path, otherwise a permanent one.
- * @return The path to write this video frame to.
- */
- std::string frame_out_path (SourceFrame f, bool t) const {
- std::stringstream s;
- s << _frame_out_path << "/";
- s.width (8);
- s << std::setfill('0') << f << _frame_out_extension;
-
- if (t) {
- s << ".tmp";
- }
-
- return s.str ();
- }
-
- std::string hash_out_path (SourceFrame f, bool t) const {
- return frame_out_path (f, t) + ".md5";
- }
-
- /** @return Path to write multichannel audio data to */
- std::string multichannel_audio_out_path () const {
- return _multichannel_audio_out_path;
- }
-
- /** @param c Audio channel index.
- * @param t true to return a temporary file path, otherwise a permanent one.
- * @return The path to write this audio file to.
- */
- std::string multichannel_audio_out_path (int c, bool t) const {
- std::stringstream s;
- s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav";
- if (t) {
- s << ".tmp";
- }
-
- return s.str ();
- }
-
- Size out_size; ///< size of output images
- int padding; ///< number of pixels of padding (in terms of the output size) each side of the image
-
- /** Range of video frames to encode (in DCP frames) */
- boost::optional<std::pair<int, int> > video_range;
- /** Range of audio frames to decode (in the DCP's sampling rate) */
- boost::optional<std::pair<int64_t, int64_t> > audio_range;
-
- /** Skip frames such that we don't decode any frame where (index % decode_video_skip) != 0; e.g.
- * 1 for every frame, 2 for every other frame, etc.
- */
- SourceFrame video_skip;
-
-private:
- /** Path of the directory to write video frames to */
- std::string _frame_out_path;
- /** Extension to use for video frame files (including the leading .) */
- std::string _frame_out_extension;
- /** Path of the directory to write audio files to */
- std::string _multichannel_audio_out_path;
-};
-
-
-class DecodeOptions
-{
-public:
- DecodeOptions ()
- : decode_audio (true)
- , decode_subtitles (false)
- , video_sync (true)
- {}
-
- bool decode_audio;
- bool decode_subtitles;
- bool video_sync;
-};
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644
index 000000000..70d6fa877
--- /dev/null
+++ b/src/lib/player.cc
@@ -0,0 +1,654 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include "player.h"
+#include "film.h"
+#include "ffmpeg_decoder.h"
+#include "ffmpeg_content.h"
+#include "still_image_decoder.h"
+#include "still_image_content.h"
+#include "moving_image_decoder.h"
+#include "moving_image_content.h"
+#include "sndfile_decoder.h"
+#include "sndfile_content.h"
+#include "subtitle_content.h"
+#include "playlist.h"
+#include "job.h"
+#include "image.h"
+#include "ratio.h"
+#include "resampler.h"
+#include "log.h"
+#include "scaler.h"
+
+using std::list;
+using std::cout;
+using std::min;
+using std::max;
+using std::vector;
+using std::pair;
+using std::map;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+
+//#define DEBUG_PLAYER 1
+
+class Piece
+{
+public:
+ Piece (shared_ptr<Content> c)
+ : content (c)
+ , video_position (c->position ())
+ , audio_position (c->position ())
+ {}
+
+ Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+ : content (c)
+ , decoder (d)
+ , video_position (c->position ())
+ , audio_position (c->position ())
+ {}
+
+ shared_ptr<Content> content;
+ shared_ptr<Decoder> decoder;
+ Time video_position;
+ Time audio_position;
+};
+
+#ifdef DEBUG_PLAYER
+std::ostream& operator<<(std::ostream& s, Piece const & p)
+{
+ if (dynamic_pointer_cast<FFmpegContent> (p.content)) {
+ s << "\tffmpeg ";
+ } else if (dynamic_pointer_cast<StillImageContent> (p.content)) {
+ s << "\tstill image";
+ } else if (dynamic_pointer_cast<SndfileContent> (p.content)) {
+ s << "\tsndfile ";
+ }
+
+ s << " at " << p.content->position() << " until " << p.content->end();
+
+ return s;
+}
+#endif
+
+Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
+ : _film (f)
+ , _playlist (p)
+ , _video (true)
+ , _audio (true)
+ , _have_valid_pieces (false)
+ , _video_position (0)
+ , _audio_position (0)
+ , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
+ , _last_emit_was_black (false)
+{
+ _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
+ _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
+ _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
+ set_video_container_size (_film->container()->size (_film->full_frame ()));
+}
+
+void
+Player::disable_video ()
+{
+ _video = false;
+}
+
+void
+Player::disable_audio ()
+{
+ _audio = false;
+}
+
+bool
+Player::pass ()
+{
+ if (!_have_valid_pieces) {
+ setup_pieces ();
+ _have_valid_pieces = true;
+ }
+
+#ifdef DEBUG_PLAYER
+ cout << "= PASS\n";
+#endif
+
+ Time earliest_t = TIME_MAX;
+ shared_ptr<Piece> earliest;
+ enum {
+ VIDEO,
+ AUDIO
+ } type = VIDEO;
+
+ for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+ if ((*i)->decoder->done ()) {
+ continue;
+ }
+
+ if (_video && dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+ if ((*i)->video_position < earliest_t) {
+ earliest_t = (*i)->video_position;
+ earliest = *i;
+ type = VIDEO;
+ }
+ }
+
+ if (_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+ if ((*i)->audio_position < earliest_t) {
+ earliest_t = (*i)->audio_position;
+ earliest = *i;
+ type = AUDIO;
+ }
+ }
+ }
+
+ if (!earliest) {
+#ifdef DEBUG_PLAYER
+ cout << "no earliest piece.\n";
+#endif
+
+ flush ();
+ return true;
+ }
+
+ switch (type) {
+ case VIDEO:
+ if (earliest_t > _video_position) {
+#ifdef DEBUG_PLAYER
+ cout << "no video here; emitting black frame (earliest=" << earliest_t << ", video_position=" << _video_position << ").\n";
+#endif
+ emit_black ();
+ } else {
+#ifdef DEBUG_PLAYER
+ cout << "Pass video " << *earliest << "\n";
+#endif
+ earliest->decoder->pass ();
+ }
+ break;
+
+ case AUDIO:
+ if (earliest_t > _audio_position) {
+#ifdef DEBUG_PLAYER
+ cout << "no audio here (none until " << earliest_t << "); emitting silence.\n";
+#endif
+ emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+ } else {
+#ifdef DEBUG_PLAYER
+ cout << "Pass audio " << *earliest << "\n";
+#endif
+ earliest->decoder->pass ();
+
+ if (earliest->decoder->done()) {
+ shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
+ assert (ac);
+ shared_ptr<Resampler> re = resampler (ac, false);
+ if (re) {
+ shared_ptr<const AudioBuffers> b = re->flush ();
+ if (b->frames ()) {
+ process_audio (earliest, b, ac->audio_length ());
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ if (_audio) {
+ Time audio_done_up_to = TIME_MAX;
+ for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+ if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+ audio_done_up_to = min (audio_done_up_to, (*i)->audio_position);
+ }
+ }
+
+ TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to);
+ Audio (tb.audio, tb.time);
+ _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ }
+
+#ifdef DEBUG_PLAYER
+ cout << "\tpost pass _video_position=" << _video_position << " _audio_position=" << _audio_position << "\n";
+#endif
+
+ return false;
+}
+
+void
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame)
+{
+ shared_ptr<Piece> piece = weak_piece.lock ();
+ if (!piece) {
+ return;
+ }
+
+ shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+ assert (content);
+
+ FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
+ if (frc.skip && (frame % 2) == 1) {
+ return;
+ }
+
+ Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
+ if (content->trimmed (relative_time)) {
+ return;
+ }
+
+ /* Convert to RGB first, as FFmpeg doesn't seem to like handling YUV images with odd widths */
+ shared_ptr<Image> work_image = image->scale (image->size (), _film->scaler(), PIX_FMT_RGB24, true);
+
+ work_image = work_image->crop (content->crop(), true);
+
+ libdcp::Size const image_size = content->ratio()->size (_video_container_size);
+
+ work_image = work_image->scale (image_size, _film->scaler(), PIX_FMT_RGB24, true);
+
+ Time time = content->position() + relative_time - content->trim_start ();
+
+ if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+ work_image->alpha_blend (_out_subtitle.image, _out_subtitle.position);
+ }
+
+ if (image_size != _video_container_size) {
+ assert (image_size.width <= _video_container_size.width);
+ assert (image_size.height <= _video_container_size.height);
+ shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
+ im->make_black ();
+ im->copy (work_image, Position<int> ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
+ work_image = im;
+ }
+
+#ifdef DCPOMATIC_DEBUG
+ _last_video = piece->content;
+#endif
+
+ Video (work_image, eyes, content->colour_conversion(), same, time);
+ time += TIME_HZ / _film->video_frame_rate();
+
+ if (frc.repeat) {
+ Video (work_image, eyes, content->colour_conversion(), true, time);
+ time += TIME_HZ / _film->video_frame_rate();
+ }
+
+ _last_emit_was_black = false;
+
+ _video_position = piece->video_position = time;
+}
+
+void
+Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+{
+ shared_ptr<Piece> piece = weak_piece.lock ();
+ if (!piece) {
+ return;
+ }
+
+ shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
+ assert (content);
+
+ /* Gain */
+ if (content->audio_gain() != 0) {
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+ gain->apply_gain (content->audio_gain ());
+ audio = gain;
+ }
+
+ /* Resample */
+ if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
+ shared_ptr<Resampler> r = resampler (content, true);
+ pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
+ audio = ro.first;
+ frame = ro.second;
+ }
+
+ Time const relative_time = _film->audio_frames_to_time (frame);
+
+ if (content->trimmed (relative_time)) {
+ return;
+ }
+
+ Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time;
+
+ /* Remap channels */
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+ dcp_mapped->make_silent ();
+ list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
+ for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
+ if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
+ dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+ }
+ }
+
+ audio = dcp_mapped;
+
+ /* We must cut off anything that comes before the start of all time */
+ if (time < 0) {
+ int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
+ if (frames >= audio->frames ()) {
+ return;
+ }
+
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
+ trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+
+ audio = trimmed;
+ time = 0;
+ }
+
+ _audio_merger.push (audio, time);
+ piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+}
+
+void
+Player::flush ()
+{
+ TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+ if (tb.audio) {
+ Audio (tb.audio, tb.time);
+ _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ }
+
+ while (_video_position < _audio_position) {
+ emit_black ();
+ }
+
+ while (_audio_position < _video_position) {
+ emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ }
+
+}
+
+/** Seek so that the next pass() will yield (approximately) the requested frame.
+ * Pass accurate = true to try harder to get close to the request.
+ * @return true on error
+ */
+void
+Player::seek (Time t, bool accurate)
+{
+ if (!_have_valid_pieces) {
+ setup_pieces ();
+ _have_valid_pieces = true;
+ }
+
+ if (_pieces.empty ()) {
+ return;
+ }
+
+ for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
+ if (!vc) {
+ continue;
+ }
+
+ Time s = t - vc->position ();
+ s = max (static_cast<Time> (0), s);
+ s = min (vc->length_after_trim(), s);
+
+ (*i)->video_position = (*i)->audio_position = vc->position() + s;
+
+ FrameRateConversion frc (vc->video_frame_rate(), _film->video_frame_rate());
+ /* Here we are converting from time (in the DCP) to a frame number in the content.
+ Hence we need to use the DCP's frame rate and the double/skip correction, not
+ the source's rate.
+ */
+ VideoContent::Frame f = (s + vc->trim_start ()) * _film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f, accurate);
+ }
+
+ _video_position = _audio_position = t;
+
+ /* XXX: don't seek audio because we don't need to... */
+}
+
+void
+Player::setup_pieces ()
+{
+ list<shared_ptr<Piece> > old_pieces = _pieces;
+
+ _pieces.clear ();
+
+ ContentList content = _playlist->content ();
+ sort (content.begin(), content.end(), ContentSorter ());
+
+ for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+
+ shared_ptr<Piece> piece (new Piece (*i));
+
+ /* XXX: into content? */
+
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+ if (fc) {
+ shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
+
+ fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
+ fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
+ fd->Subtitle.connect (bind (&Player::process_subtitle, this, piece, _1, _2, _3, _4));
+
+ piece->decoder = fd;
+ }
+
+ shared_ptr<const StillImageContent> ic = dynamic_pointer_cast<const StillImageContent> (*i);
+ if (ic) {
+ shared_ptr<StillImageDecoder> id;
+
+ /* See if we can re-use an old StillImageDecoder */
+ for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
+ shared_ptr<StillImageDecoder> imd = dynamic_pointer_cast<StillImageDecoder> ((*j)->decoder);
+ if (imd && imd->content() == ic) {
+ id = imd;
+ }
+ }
+
+ if (!id) {
+ id.reset (new StillImageDecoder (_film, ic));
+ id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
+ }
+
+ piece->decoder = id;
+ }
+
+ shared_ptr<const MovingImageContent> mc = dynamic_pointer_cast<const MovingImageContent> (*i);
+ if (mc) {
+ shared_ptr<MovingImageDecoder> md;
+
+ if (!md) {
+ md.reset (new MovingImageDecoder (_film, mc));
+ md->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
+ }
+
+ piece->decoder = md;
+ }
+
+ shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
+ if (sc) {
+ shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
+ sd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
+
+ piece->decoder = sd;
+ }
+
+ _pieces.push_back (piece);
+ }
+
+#ifdef DEBUG_PLAYER
+ cout << "=== Player setup:\n";
+ for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+ cout << *(i->get()) << "\n";
+ }
+#endif
+}
+
+void
+Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
+{
+ shared_ptr<Content> c = w.lock ();
+ if (!c) {
+ return;
+ }
+
+ if (
+ property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
+ property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
+ property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO
+ ) {
+
+ _have_valid_pieces = false;
+ Changed (frequent);
+
+ } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
+ update_subtitle ();
+ Changed (frequent);
+ } else if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
+ Changed (frequent);
+ }
+}
+
+void
+Player::playlist_changed ()
+{
+ _have_valid_pieces = false;
+ Changed (false);
+}
+
+void
+Player::set_video_container_size (libdcp::Size s)
+{
+ _video_container_size = s;
+ _black_frame.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
+ _black_frame->make_black ();
+}
+
+shared_ptr<Resampler>
+Player::resampler (shared_ptr<AudioContent> c, bool create)
+{
+ map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
+ if (i != _resamplers.end ()) {
+ return i->second;
+ }
+
+ if (!create) {
+ return shared_ptr<Resampler> ();
+ }
+
+ shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
+ _resamplers[c] = r;
+ return r;
+}
+
+void
+Player::emit_black ()
+{
+#ifdef DCPOMATIC_DEBUG
+ _last_video.reset ();
+#endif
+
+ Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
+ _video_position += _film->video_frames_to_time (1);
+ _last_emit_was_black = true;
+}
+
+void
+Player::emit_silence (OutputAudioFrame most)
+{
+ if (most == 0) {
+ return;
+ }
+
+ OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+ silence->make_silent ();
+ Audio (silence, _audio_position);
+ _audio_position += _film->audio_frames_to_time (N);
+}
+
+void
+Player::film_changed (Film::Property p)
+{
+ /* Here we should notice Film properties that affect our output, and
+ alert listeners that our output now would be different to how it was
+ last time we were run.
+ */
+
+ if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
+ Changed (false);
+ }
+}
+
+void
+Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+{
+ _in_subtitle.piece = weak_piece;
+ _in_subtitle.image = image;
+ _in_subtitle.rect = rect;
+ _in_subtitle.from = from;
+ _in_subtitle.to = to;
+
+ update_subtitle ();
+}
+
+void
+Player::update_subtitle ()
+{
+ shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+ if (!piece) {
+ return;
+ }
+
+ if (!_in_subtitle.image) {
+ _out_subtitle.image.reset ();
+ return;
+ }
+
+ shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
+ assert (sc);
+
+ dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
+ libdcp::Size scaled_size;
+
+ in_rect.y += sc->subtitle_offset ();
+
+ /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
+ scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
+ scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
+
+ /* Then we need a corrective translation, consisting of two parts:
+ *
+ * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
+ * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
+ *
+ * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
+ * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
+ * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+ *
+ * Combining these two translations gives these expressions.
+ */
+
+ _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
+ _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
+
+ _out_subtitle.image = _in_subtitle.image->scale (
+ scaled_size,
+ Scaler::from_id ("bicubic"),
+ _in_subtitle.image->pixel_format (),
+ true
+ );
+ _out_subtitle.from = _in_subtitle.from + piece->content->position ();
+ _out_subtitle.to = _in_subtitle.to + piece->content->position ();
+}
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644
index 000000000..2261f66ea
--- /dev/null
+++ b/src/lib/player.h
@@ -0,0 +1,146 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_PLAYER_H
+#define DCPOMATIC_PLAYER_H
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "playlist.h"
+#include "content.h"
+#include "film.h"
+#include "rect.h"
+#include "audio_merger.h"
+#include "audio_content.h"
+
+class Job;
+class Film;
+class Playlist;
+class AudioContent;
+class Piece;
+class Image;
+class Resampler;
+
+/** @class Player
+ * @brief A class which can `play' a Playlist; emitting its audio and video.
+ */
+
+class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
+{
+public:
+ Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
+
+ void disable_video ();
+ void disable_audio ();
+
+ bool pass ();
+ void seek (Time, bool);
+
+ Time video_position () const {
+ return _video_position;
+ }
+
+ void set_video_container_size (libdcp::Size);
+
+ /** Emitted when a video frame is ready.
+ * First parameter is the video image.
+ * Second parameter is the eye(s) that should see this image.
+ * Third parameter is the colour conversion that should be used for this image.
+ * Fourth parameter is true if the image is the same as the last one that was emitted.
+ * Fifth parameter is the time.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, ColourConversion, bool, Time)> Video;
+
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+
+ /** Emitted when something has changed such that if we went back and emitted
+ * the last frame again it would look different. This is not emitted after
+ * a seek.
+ *
+ * The parameter is true if these signals are currently likely to be frequent.
+ */
+ boost::signals2::signal<void (bool)> Changed;
+
+private:
+ friend class PlayerWrapper;
+
+ void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame);
+ void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+ void setup_pieces ();
+ void playlist_changed ();
+ void content_changed (boost::weak_ptr<Content>, int, bool);
+ void do_seek (Time, bool);
+ void flush ();
+ void emit_black ();
+ void emit_silence (OutputAudioFrame);
+ boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
+ void film_changed (Film::Property);
+ void update_subtitle ();
+
+ boost::shared_ptr<const Film> _film;
+ boost::shared_ptr<const Playlist> _playlist;
+
+ bool _video;
+ bool _audio;
+
+ /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
+ bool _have_valid_pieces;
+ std::list<boost::shared_ptr<Piece> > _pieces;
+
+ /** The time after the last video that we emitted */
+ Time _video_position;
+ /** The time after the last audio that we emitted */
+ Time _audio_position;
+
+ AudioMerger<Time, AudioContent::Frame> _audio_merger;
+
+ libdcp::Size _video_container_size;
+ boost::shared_ptr<Image> _black_frame;
+ std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
+
+ struct {
+ boost::weak_ptr<Piece> piece;
+ boost::shared_ptr<Image> image;
+ dcpomatic::Rect<double> rect;
+ Time from;
+ Time to;
+ } _in_subtitle;
+
+ struct {
+ boost::shared_ptr<Image> image;
+ Position<int> position;
+ Time from;
+ Time to;
+ } _out_subtitle;
+
+#ifdef DCPOMATIC_DEBUG
+ boost::shared_ptr<Content> _last_video;
+#endif
+
+ bool _last_emit_was_black;
+
+ boost::signals2::scoped_connection _playlist_changed_connection;
+ boost::signals2::scoped_connection _playlist_content_changed_connection;
+ boost::signals2::scoped_connection _film_changed_connection;
+};
+
+#endif
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
new file mode 100644
index 000000000..de48ff5f5
--- /dev/null
+++ b/src/lib/playlist.cc
@@ -0,0 +1,330 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include "playlist.h"
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "video_content.h"
+#include "ffmpeg_decoder.h"
+#include "ffmpeg_content.h"
+#include "still_image_decoder.h"
+#include "still_image_content.h"
+#include "content_factory.h"
+#include "job.h"
+#include "config.h"
+#include "util.h"
+
+#include "i18n.h"
+
+using std::list;
+using std::cout;
+using std::vector;
+using std::min;
+using std::max;
+using std::string;
+using std::stringstream;
+using std::pair;
+using boost::optional;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
+
+Playlist::Playlist ()
+ : _sequence_video (true)
+ , _sequencing_video (false)
+{
+
+}
+
+Playlist::~Playlist ()
+{
+ _content.clear ();
+ reconnect ();
+}
+
+void
+Playlist::content_changed (weak_ptr<Content> content, int property, bool frequent)
+{
+ if (property == ContentProperty::LENGTH) {
+ maybe_sequence_video ();
+ }
+
+ ContentChanged (content, property, frequent);
+}
+
+void
+Playlist::maybe_sequence_video ()
+{
+ if (!_sequence_video || _sequencing_video) {
+ return;
+ }
+
+ _sequencing_video = true;
+
+ ContentList cl = _content;
+ sort (cl.begin(), cl.end(), ContentSorter ());
+ Time last = 0;
+ for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) {
+ if (!dynamic_pointer_cast<VideoContent> (*i)) {
+ continue;
+ }
+
+ (*i)->set_position (last);
+ last = (*i)->end ();
+ }
+
+ _sequencing_video = false;
+}
+
+string
+Playlist::video_identifier () const
+{
+ string t;
+
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (vc) {
+ t += vc->identifier ();
+ }
+ }
+
+ return md5_digest (t.c_str(), t.length());
+}
+
+/** @param node <Playlist> node */
+void
+Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node)
+{
+ list<shared_ptr<cxml::Node> > c = node->node_children ("Content");
+ for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+ _content.push_back (content_factory (film, *i));
+ }
+
+ reconnect ();
+}
+
+/** @param node <Playlist> node */
+void
+Playlist::as_xml (xmlpp::Node* node)
+{
+ for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
+ (*i)->as_xml (node->add_child ("Content"));
+ }
+}
+
+void
+Playlist::add (shared_ptr<Content> c)
+{
+ _content.push_back (c);
+ reconnect ();
+ Changed ();
+}
+
+void
+Playlist::remove (shared_ptr<Content> c)
+{
+ ContentList::iterator i = _content.begin ();
+ while (i != _content.end() && *i != c) {
+ ++i;
+ }
+
+ if (i != _content.end ()) {
+ _content.erase (i);
+ Changed ();
+ }
+}
+
+void
+Playlist::remove (ContentList c)
+{
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+ ContentList::iterator j = _content.begin ();
+ while (j != _content.end() && *j != *i) {
+ ++j;
+ }
+
+ if (j != _content.end ()) {
+ _content.erase (j);
+ }
+ }
+
+ Changed ();
+}
+
+bool
+Playlist::has_subtitles () const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
+ if (fc && !fc->subtitle_streams().empty()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+class FrameRateCandidate
+{
+public:
+ FrameRateCandidate (float source_, int dcp_)
+ : source (source_)
+ , dcp (dcp_)
+ {}
+
+ float source;
+ int dcp;
+};
+
+int
+Playlist::best_dcp_frame_rate () const
+{
+ list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
+
+ /* Work out what rates we could manage, including those achieved by using skip / repeat. */
+ list<FrameRateCandidate> candidates;
+
+ /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
+ for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+ candidates.push_back (FrameRateCandidate (*i, *i));
+ }
+
+ /* Then the skip/repeat ones */
+ for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+ candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
+ candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
+ }
+
+ /* Pick the best one */
+ float error = std::numeric_limits<float>::max ();
+ optional<FrameRateCandidate> best;
+ list<FrameRateCandidate>::iterator i = candidates.begin();
+ while (i != candidates.end()) {
+
+ float this_error = 0;
+ for (ContentList::const_iterator j = _content.begin(); j != _content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ /* Use the largest difference between DCP and source as the "error" */
+ this_error = max (this_error, float (fabs (i->source - vc->video_frame_rate ())));
+ }
+
+ if (this_error < error) {
+ error = this_error;
+ best = *i;
+ }
+
+ ++i;
+ }
+
+ if (!best) {
+ return 24;
+ }
+
+ return best->dcp;
+}
+
+Time
+Playlist::length () const
+{
+ Time len = 0;
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ len = max (len, (*i)->end ());
+ }
+
+ return len;
+}
+
+void
+Playlist::reconnect ()
+{
+ for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
+ i->disconnect ();
+ }
+
+ _content_connections.clear ();
+
+ for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
+ _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2, _3)));
+ }
+}
+
+Time
+Playlist::video_end () const
+{
+ Time end = 0;
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ if (dynamic_pointer_cast<const VideoContent> (*i)) {
+ end = max (end, (*i)->end ());
+ }
+ }
+
+ return end;
+}
+
+void
+Playlist::set_sequence_video (bool s)
+{
+ _sequence_video = s;
+}
+
+bool
+ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
+{
+ return a->position() < b->position();
+}
+
+/** @return content in an undefined order */
+ContentList
+Playlist::content () const
+{
+ return _content;
+}
+
+void
+Playlist::repeat (ContentList c, int n)
+{
+ pair<Time, Time> range (TIME_MAX, 0);
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+ range.first = min (range.first, (*i)->position ());
+ range.second = max (range.second, (*i)->position ());
+ range.first = min (range.first, (*i)->end ());
+ range.second = max (range.second, (*i)->end ());
+ }
+
+ Time pos = range.second;
+ for (int i = 0; i < n; ++i) {
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+ shared_ptr<Content> copy = (*i)->clone ();
+ copy->set_position (pos + copy->position() - range.first);
+ _content.push_back (copy);
+ }
+ pos += range.second - range.first;
+ }
+
+ reconnect ();
+ Changed ();
+}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
new file mode 100644
index 000000000..7dbf41604
--- /dev/null
+++ b/src/lib/playlist.h
@@ -0,0 +1,95 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_PLAYLIST_H
+#define DCPOMATIC_PLAYLIST_H
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "ffmpeg_content.h"
+#include "audio_mapping.h"
+
+class Content;
+class FFmpegContent;
+class FFmpegDecoder;
+class StillImageMagickContent;
+class StillImageMagickDecoder;
+class SndfileContent;
+class SndfileDecoder;
+class Job;
+class Film;
+class Region;
+
+/** @class Playlist
+ * @brief A set of content files (video and audio), with knowledge of how they should be arranged into
+ * a DCP.
+ *
+ * This class holds Content objects, and it knows how they should be arranged.
+ */
+
+struct ContentSorter
+{
+ bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b);
+};
+
+class Playlist : public boost::noncopyable
+{
+public:
+ Playlist ();
+ ~Playlist ();
+
+ void as_xml (xmlpp::Node *);
+ void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ void add (boost::shared_ptr<Content>);
+ void remove (boost::shared_ptr<Content>);
+ void remove (ContentList);
+
+ bool has_subtitles () const;
+
+ ContentList content () const;
+
+ std::string video_identifier () const;
+
+ Time length () const;
+
+ int best_dcp_frame_rate () const;
+ Time video_end () const;
+
+ void set_sequence_video (bool);
+ void maybe_sequence_video ();
+
+ void repeat (ContentList, int);
+
+ mutable boost::signals2::signal<void ()> Changed;
+ /** Third parameter is true if signals are currently being emitted frequently */
+ mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> ContentChanged;
+
+private:
+ void content_changed (boost::weak_ptr<Content>, int, bool);
+ void reconnect ();
+
+ ContentList _content;
+ bool _sequence_video;
+ bool _sequencing_video;
+ std::list<boost::signals2::connection> _content_connections;
+};
+
+#endif
diff --git a/src/lib/po/es_ES.po b/src/lib/po/es_ES.po
new file mode 100644
index 000000000..0e1776ac6
--- /dev/null
+++ b/src/lib/po/es_ES.po
@@ -0,0 +1,658 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: LIBDCPOMATIC\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-02 19:10-0500\n"
+"Last-Translator: Manuel AC <manuel.acevedo@civantos.>\n"
+"Language-Team: Manuel AC <manuel.acevedo@civantos.com>\n"
+"Language: es-ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr ""
+
+#: src/lib/ffmpeg_content.cc:193
+#, fuzzy
+msgid "%1 frames; %2 frames per second"
+msgstr "fotogramas por segundo"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr ""
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1.19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1.375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1.66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "reducción de ruido 3D"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr ""
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Publicidad"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Ha ocurrido un error con el fichero %1."
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analizar audio de %1"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Área"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bicúbico"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilineal"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr ""
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr ""
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr ""
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Copiar DCP al TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "No se pudo conectar al servidor %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "No se pudo crear la carpeta remota %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "No se pudo abrir %1 para enviar"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "No se pudo iniciar la sesión SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "No se pudo escribir el fichero remoto (%1)"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Desentrelazado por interpolación cúbica"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "La fuente y el DCP tienen la misma velocidad.\n"
+
+#: src/lib/util.cc:733
+#, fuzzy
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "El DCP se reproducirá al %1%% de la velocidad de la fuente.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "El DCP usará fotogramas alternos de la fuente.\n"
+
+#: src/lib/filter.cc:68 src/lib/filter.cc:69 src/lib/filter.cc:70
+#: src/lib/filter.cc:71 src/lib/filter.cc:72 src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "De-blocking"
+
+#: src/lib/filter.cc:75 src/lib/filter.cc:76 src/lib/filter.cc:77
+#: src/lib/filter.cc:78 src/lib/filter.cc:79 src/lib/filter.cc:80
+#: src/lib/filter.cc:81 src/lib/filter.cc:82 src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "Desentrelazado"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Deringing filter"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Se doblará cada fotograma de la fuente en el DCP.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Error (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Examinar contenido"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Experimental horizontal deblocking filter 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Experimental vertical deblocking filter 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "Desentrelazado FFMPEG"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "Desentrelazado paso bajo FIR"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "Fallo al identificarse con el servidor (%1)"
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Bilineal rápido"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Película"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Force quantizer"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr ""
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussiano"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Gradient debander"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Reductor de ruido 3D de alta calidad"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Horizontal deblocking filter"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Horizontal deblocking filter A"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr ""
+
+#: src/lib/job.cc:96 src/lib/job.cc:105
+msgid ""
+"It is not known what caused this error. The best idea is to report the "
+"problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr ""
+"Error desconocido. La mejor idea es informar del problema a la lista de "
+"correo de DCP-o-matic (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Kernel deinterlacer"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr ""
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr ""
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr ""
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Linear blend deinterlacer"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Linear interpolating deinterlacer"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Median deinterlacer"
+
+#: src/lib/filter.cc:74 src/lib/filter.cc:85 src/lib/filter.cc:86
+#: src/lib/filter.cc:87 src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Miscelánea"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Motion compensating deinterlacer"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr ""
+
+#: src/lib/filter.cc:84 src/lib/filter.cc:88 src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Reducción de ruido"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (ejecución %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Overcomplete wavelet denoiser"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Policy"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Anuncio de servicio público"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Clasificación"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr ""
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr ""
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "error SSH (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Cortometraje"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+#, fuzzy
+msgid "Sound file: %1"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Filtro telecine"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Temporal noise reducer"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Test"
+
+#: src/lib/job.cc:78
+msgid ""
+"The drive that the film is stored on is low in disc space. Free some more "
+"space and try again."
+msgstr ""
+"En el dispositivo donde se encuentra la película queda poco espacio. Libere "
+"espacio en el disco y pruebe de nuevo."
+
+#: src/lib/film.cc:364
+msgid ""
+"This film was created with an older version of DCP-o-matic, and "
+"unfortunately it cannot be loaded into this version. You will need to "
+"create a new Film, re-add your content and set it up again. Sorry!"
+msgstr ""
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Trailer"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Codificar %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Transitional"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Error desconocido"
+
+#: src/lib/ffmpeg_decoder.cc:264
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Formato de audio desconocido (%1)"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Máscara de desenfoque Gaussiano"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Vertical deblocking filter"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Vertical deblocking filter A"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "Esperando"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Yet Another Deinterlacing Filter"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr ""
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "no puede contener barras"
+
+#: src/lib/util.cc:494
+msgid "connect timed out"
+msgstr "tiempo de conexión agotado"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "conectando"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "contenido"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "tipo de contenido"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "copiando %1"
+
+#: src/lib/exceptions.cc:36
+#, fuzzy
+msgid "could not create file %1"
+msgstr "No se pudo escribir el fichero remoto (%1)"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "no se encontró el decodificador de audio"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "no se pudo encontrar información del flujo"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "no se pudo encontrar decodificador de subtítutlos"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "no se pudo encontrar decodificador de vídeo"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/exceptions.cc:29
+#, fuzzy
+msgid "could not open file %1"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "no se pudo abrir el fichero para lectura"
+
+#: src/lib/exceptions.cc:44
+#, fuzzy
+msgid "could not read from file %1 (%2)"
+msgstr "No se pudo crear la carpeta remota %1 (%2)"
+
+#: src/lib/resampler.cc:76 src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "no se pudo ejecutar el conversor de velocidad"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "no se pudo abrir la sesión SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "no se pudo abrir la sesión SSH"
+
+#: src/lib/exceptions.cc:50
+#, fuzzy
+msgid "could not write to file %1 (%2)"
+msgstr "No se pudo escribir el fichero remoto (%1)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "fotogramas por segundo"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "hora"
+
+#: src/lib/util.cc:143 src/lib/util.cc:148
+msgid "hours"
+msgstr "horas"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minuto"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minutos"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "falta la clave %1 en el par clave-valor"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr ""
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "todavía no se soportan subtítulos en múltiples partes"
+
+#: src/lib/film.cc:232 src/lib/film.cc:281
+msgid "name"
+msgstr "nombre"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "todavía no se soportan subtítulos que no son en mapas de bits"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "pendiente"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "segundos"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1.66 en Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 en Flat"
+
+#, fuzzy
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 en Flat"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 en Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "Codificación A/B %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr ""
+#~ "No se puede redimensionar el sonido porque no se encuentra libswresample"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Examinar contenido de %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat sin deformación"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope sin deformación"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "no se pudo leer el fichero externo de audio"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "los ficheros externos de sonido tienen duraciones diferentes"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "los ficheros externos de sonido deben ser mono"
+
+#~ msgid "format"
+#~ msgstr "formato"
+
+#~ msgid "no still image files found"
+#~ msgstr "no se encuentran imágenes fijas"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "imagen fija"
+
+#~ msgid "video"
+#~ msgstr "vídeo"
+
+#~ msgid "1.33"
+#~ msgstr "1.33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Fuente escalada a 1.19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Fuente escalada a 1.33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Fuente escalada a 1.33:1 con bandas hasta Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Fuente escalada a 1.375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Fuente escalada a 1.37:1 (Academy)"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Fuente escalada a 1.66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Fuente escalada a 1.66:1 con bandas hasta Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Fuente escalada a 1.78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Fuente escalada a 1.78:1 con bandas hasta Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Fuente escalada a Flat (1.85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Fuente escalada a Scope (2.39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr "Fuente escalada a Flat conservando el ratio de aspecto"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr "Fuente escalada a Scope conservando el ratio de aspecto"
+
+#~ msgid "adding to queue of %1"
+#~ msgstr "añadiendo a la cola de %1"
diff --git a/src/lib/po/fr_FR.po b/src/lib/po/fr_FR.po
new file mode 100644
index 000000000..d13472546
--- /dev/null
+++ b/src/lib/po/fr_FR.po
@@ -0,0 +1,664 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic FRENCH\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-07-16 23:11+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr "%1 canaux, %2kHz, %3 samples"
+
+#: src/lib/ffmpeg_content.cc:193
+msgid "%1 frames; %2 frames per second"
+msgstr "%1 images ; %2 images par seconde"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr "%1x%2 pixels (%3:1)"
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1.19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1.375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1.66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "Débruitage 3D"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr "4:3"
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Advertisement"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Une erreur s'est produite lors du traitement du fichier %1."
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analyse du son de %1"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Area"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bicubique"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilinéaire"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr "Annulé"
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr "Format du pixel %1 non géré par %2"
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr "Centre"
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Copier le DCP dans le TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "Connexion au serveur %1 (%2) impossible"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "Création du dossier distant %1 (%2) impossible"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "Ouverture de %1 pour envoi impossible"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "Démarrage de session SCP (%1) impossible"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "Écriture vers fichier distant (%1) impossible"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Désentrelacement cubique interpolé"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "Le DCP et la source ont les mêmes cadences.\n"
+
+#: src/lib/util.cc:733
+#, fuzzy
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "La cadence du DCP sera %1%% par rapport à la source.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "Le DCP utilisera une image sur deux de la source.\n"
+
+#: src/lib/filter.cc:68
+#: src/lib/filter.cc:69
+#: src/lib/filter.cc:70
+#: src/lib/filter.cc:71
+#: src/lib/filter.cc:72
+#: src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "De-bloc"
+
+#: src/lib/filter.cc:75
+#: src/lib/filter.cc:76
+#: src/lib/filter.cc:77
+#: src/lib/filter.cc:78
+#: src/lib/filter.cc:79
+#: src/lib/filter.cc:80
+#: src/lib/filter.cc:81
+#: src/lib/filter.cc:82
+#: src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "Désentrelacement"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Filtre anti bourdonnement"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Chaque image source sera dupliquée dans le DCP.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Erreur (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Examen du contenu"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Filtre dé-bloc horizontal 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Filtre dé-bloc vertical 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "Désentrelaceur FFMPEG"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "Désentrelaceur passe-bas FIR"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "L'authentification du serveur (%1) a échouée"
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Bilinéaire rapide"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Feature"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Forcer la quantification"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr "Pleine matrice"
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussien"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Corrections des bandes du dégradé"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Débruiteur 3D haute qualité"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Filtre dé-bloc horizontal"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Filtre dé-bloc horizontal"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr "Image : %1"
+
+#: src/lib/job.cc:96
+#: src/lib/job.cc:105
+msgid "It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr "Erreur indéterminée. Merci de rapporter le problème à la liste DCP-o-matic (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Désentrelaceur noyau"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr "Gauche"
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr "Arrière gauche"
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr "Basses fréquences"
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Désentrelaceur par mélange interpolé"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Désentrelaceur linéaire interpolé"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Désentrelaceur médian"
+
+#: src/lib/filter.cc:74
+#: src/lib/filter.cc:85
+#: src/lib/filter.cc:86
+#: src/lib/filter.cc:87
+#: src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Divers"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Désentrelaceur par compensation de mouvement"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr "Film : %1"
+
+#: src/lib/filter.cc:84
+#: src/lib/filter.cc:88
+#: src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Réduction de bruit"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (processus %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Réduction de bruit par ondelettes"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Policy"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Public Service Announcement"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Classification"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr "Droite"
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr "Arrière droite"
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "Erreur SSH (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Short"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+msgid "Sound file: %1"
+msgstr "Fichier son : %1"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Filtre télécinéma"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Réduction de bruit temporel"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Test"
+
+#: src/lib/job.cc:78
+msgid "The drive that the film is stored on is low in disc space. Free some more space and try again."
+msgstr "Le disque contenant le film est plein. Libérez de l'espace et essayez à nouveau."
+
+#: src/lib/film.cc:364
+msgid "This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!"
+msgstr "Ce projet a été créé avec une ancienne version de DCP-o-matic, chargement impossible. Créez un nouveau projet, ajoutez du contenu et reparamétrez. Désolé !"
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Trailer"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Transcodage %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Transitional"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Erreur inconnue"
+
+#: src/lib/ffmpeg_decoder.cc:264
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Échantillonnage audio (%1) inconnu"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Adoucissement et flou Gaussien"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Filtre dé-bloc vertical"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Filtre dé-bloc vertical A"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "En cours"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Un autre filtre de désentrelacement"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr "Ajoutez un contenu pour créer le DCP"
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "slash interdit"
+
+#: src/lib/util.cc:494
+msgid "connect timed out"
+msgstr "temps de connexion expiré"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "connexion"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "contenu"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "type de contenu"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "copie de %1"
+
+#: src/lib/exceptions.cc:36
+msgid "could not create file %1"
+msgstr "Écriture vers fichier distant (%1) impossible"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "décodeur audio introuvable"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "information du flux introuvable"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "décodeur de sous-titre introuvable"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "décodeur vidéo introuvable"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "lecture du fichier impossible"
+
+#: src/lib/exceptions.cc:29
+msgid "could not open file %1"
+msgstr "lecture du fichier (%1) impossible"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "lecture du fichier impossible"
+
+#: src/lib/exceptions.cc:44
+msgid "could not read from file %1 (%2)"
+msgstr "lecture du fichier impossible %1 (%2)"
+
+#: src/lib/resampler.cc:76
+#: src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "conversion de la fréquence d'échantillonnage impossible"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "démarrage de session SCP (%1) impossible"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "démarrage de session SSH impossible"
+
+#: src/lib/exceptions.cc:50
+msgid "could not write to file %1 (%2)"
+msgstr "Écriture vers fichier distant (%1) impossible (%2)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "images par seconde"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "heure"
+
+#: src/lib/util.cc:143
+#: src/lib/util.cc:148
+msgid "hours"
+msgstr "heures"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minute"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minutes"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "clé %1 non sélectionnée"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr "paramètre %1 manquant"
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "sous-titres en plusieurs parties non supportés"
+
+#: src/lib/film.cc:232
+#: src/lib/film.cc:281
+msgid "name"
+msgstr "nom"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "sous-titres non-bitmap non supportés actuellement"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "restant"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "secondes"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1.66 dans Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 dans Flat"
+
+#, fuzzy
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 dans Scope"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 dans Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "Transcodage A/B %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr "Ré-échantillonnage du son impossible : libswresample est absent"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Examen du contenu de %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat sans déformation"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope sans déformation"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "lecture du fichier audio externe impossible"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "Les fichiers audio externes ont des durées différentes"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "les fichiers audio externes doivent être en mono"
+
+#~ msgid "format"
+#~ msgstr "format"
+
+#~ msgid "no still image files found"
+#~ msgstr "aucune image fixe trouvée"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "fixe"
+
+#~ msgid "video"
+#~ msgstr "vidéo"
+
+#~ msgid "1.33"
+#~ msgstr "1.33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Source mise à l'échelle en 1.19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Source mise à l'échelle en 1.33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Source mise à l'échelle en 1.33:1 puis contenue dans Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Source mise à l'échelle en 1.375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Source mise à l'échelle en 1.37:1 (ratio \"academy\")"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Source mise à l'échelle en 1.66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Source mise à l'échelle en 1.66:1 puis contenue dans Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Source mise à l'échelle en 1.78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Source mise à l'échelle en 1.78:1 puis contenue dans Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Source mise à l'échelle en Flat (1.85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Source mise à l'échelle en Scope (2.39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr "Source réduite en Flat afin de préserver ses dimensions"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr "Source réduite en Scope afin de préserver ses dimensions"
+
+#~ msgid "adding to queue of %1"
+#~ msgstr "Mise en file d'attente de %1"
+
+#~ msgid "decoder sleeps with queue of %1"
+#~ msgstr "décodeur en veille avec %1 en file d'attente"
+
+#~ msgid "decoder wakes with queue of %1"
+#~ msgstr "reprise du décodage avec %1 en file d'attente"
diff --git a/src/lib/po/it_IT.po b/src/lib/po/it_IT.po
new file mode 100644
index 000000000..53a34e020
--- /dev/null
+++ b/src/lib/po/it_IT.po
@@ -0,0 +1,659 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: IT VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-28 10:26+0100\n"
+"Last-Translator: Maci <macibro@gmail.com>\n"
+"Language-Team: \n"
+"Language: Italiano\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr ""
+
+#: src/lib/ffmpeg_content.cc:193
+#, fuzzy
+msgid "%1 frames; %2 frames per second"
+msgstr "fotogrammi al secondo"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr ""
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1.19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1.375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1.66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "Riduttore di rumore 3D"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr "4:3"
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Pubblicità"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Errore durante l'elaborazione del file %1."
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analizzo l'audio di %1"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Area"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bicubica"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilineare"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr "Cancellato"
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr "Non posso gestire il formato di pixel %1 durante %2"
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr "Centro"
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Copia del DCP al TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "Non posso connetermi al server %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "Non posso creare la directory remota %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "Non posso aprire %1 da inviare"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "Non posso avviare la sessione SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "Non posso scrivere il file remoto (%1)"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Deinterlacciatore cubico interpolato"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "Il DCP e il sorgente hanno la stessa frequenza.\n"
+
+#: src/lib/util.cc:733
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "Il DCP andrà al %1%% della velocità del sorgente.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "Il DCP userà ogni altro fotogramma del sorgente.\n"
+
+#: src/lib/filter.cc:68 src/lib/filter.cc:69 src/lib/filter.cc:70
+#: src/lib/filter.cc:71 src/lib/filter.cc:72 src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "Sbloccaggio"
+
+#: src/lib/filter.cc:75 src/lib/filter.cc:76 src/lib/filter.cc:77
+#: src/lib/filter.cc:78 src/lib/filter.cc:79 src/lib/filter.cc:80
+#: src/lib/filter.cc:81 src/lib/filter.cc:82 src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "De-interlacciamento"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Filtro deringing"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Ogni fotogramma del sorgente sarà raddoppiato nel DCP.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Errore (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Esamino il contenuto"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Filtro di sblocco sperimentale orizzontale 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Filtro di sblocco sperimentale verticale 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "Deinterlacciatore FFMPEG"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "Deinterlacciatore FIR low-pass"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "Autenticazione col server fallita (%1) "
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Bilineare rapida"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Caratteristica"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Forza quantizzatore"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr ""
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussiana"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Gradiente debander"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Riduttore di rumore 3D di alta qualità"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Filtro sblocco orizzontale"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Filtro A sblocco orizzontale"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr ""
+
+#: src/lib/job.cc:96 src/lib/job.cc:105
+msgid ""
+"It is not known what caused this error. The best idea is to report the "
+"problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr ""
+"Non sappiamo cosa ha causato questo errore. La cosa migliore è inviare un "
+"report del problema alla mailing list di DCP-o-matic (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Deinterlacciatore Kernel"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr "Sinistro"
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr "Surround sinistro"
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr "Lfe(sub)"
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Deinterlacciatore lineare miscelato"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Deinterlacciatore lineare interpolato"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Deinterlacciatore mediano"
+
+#: src/lib/filter.cc:74 src/lib/filter.cc:85 src/lib/filter.cc:86
+#: src/lib/filter.cc:87 src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Varie"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Dinterlacciatore compensativo di movimento"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr ""
+
+#: src/lib/filter.cc:84 src/lib/filter.cc:88 src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Riduzione del rumore"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (eseguito in %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Overcomplete wavelet denoiser"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Politica"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Annuncio di pubblico servizio"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Punteggio"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr "Destro"
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr "Surround destro"
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "Errore SSH (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Corto"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+#, fuzzy
+msgid "Sound file: %1"
+msgstr "non riesco ad aprire %1"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Filtro telecinema"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Riduttore temporale di rumore"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Prova"
+
+#: src/lib/job.cc:78
+msgid ""
+"The drive that the film is stored on is low in disc space. Free some more "
+"space and try again."
+msgstr ""
+"Sul disco dove è memorizzato il film non c'è abbastanza spazio. Liberare "
+"altro spazio e riprovare."
+
+#: src/lib/film.cc:364
+msgid ""
+"This film was created with an older version of DCP-o-matic, and "
+"unfortunately it cannot be loaded into this version. You will need to "
+"create a new Film, re-add your content and set it up again. Sorry!"
+msgstr ""
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Prossimamente"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Transcodifica %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Di transizione"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Errore sconosciuto"
+
+#: src/lib/ffmpeg_decoder.cc:264
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Formato di campionamento audio non riconosciuto (%1)"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Maschera unsharp e sfocatura Gaussiana"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Filtro di sblocco verticale"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Filtro A di sblocco verticale"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "Aspetta"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Altro filtro di deinterlacciamento"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr ""
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "non può contenere barre"
+
+#: src/lib/util.cc:494
+msgid "connect timed out"
+msgstr "connessione scaduta"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "mi sto connettendo"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "contenuto"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "tipo di contenuto"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "copia %1"
+
+#: src/lib/exceptions.cc:36
+msgid "could not create file %1"
+msgstr "Non posso scrivere il file remoto (%1)"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "non riesco a trovare il decoder audio"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "non riesco a trovare informazioni sullo streaming"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "non riesco a trovare il decoder dei sottotitoli"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "non riesco a trovare il decoder video"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "non riesco ad aprire il file per leggerlo"
+
+#: src/lib/exceptions.cc:29
+msgid "could not open file %1"
+msgstr "non riesco ad aprire %1"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "non riesco ad aprire il file per leggerlo"
+
+#: src/lib/exceptions.cc:44
+msgid "could not read from file %1 (%2)"
+msgstr "non posso leggere dal file %1 (%2)"
+
+#: src/lib/resampler.cc:76 src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "non riesco a eseguire il convertitore della frequenza di campionamento"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "non posso avviare la sessione SCP (%1)"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "non posso avviare la sessione SSH"
+
+#: src/lib/exceptions.cc:50
+msgid "could not write to file %1 (%2)"
+msgstr "non posso scrivere il file (%1)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "fotogrammi al secondo"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "ora"
+
+#: src/lib/util.cc:143 src/lib/util.cc:148
+msgid "hours"
+msgstr "ore"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minuto"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minuti"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "persa la chiave %1 tra i valori chiave"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr "persa la regolazione richiesta %1"
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "sottotitoli multi-part non ancora supportati"
+
+#: src/lib/film.cc:232 src/lib/film.cc:281
+msgid "name"
+msgstr "nome"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "sottotitoli non-bitmap non ancora supportati"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "restano"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "secondi"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1.66 all'interno di Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 all'interno di Flat"
+
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 all'interno di Scope"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 all'interno di Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "Transcodifica A/B %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr "Non posso ricampionare l'audio perchè libswresample non è presente"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Esamino il contenuto di %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat senza stiramento"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope senza stiramento"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "non riesco ad aprire il file dell'audio esterno per leggerlo"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "i files dell'audio esterno hanno durata diversa"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "i files dell'audio esterno devono essere mono"
+
+#~ msgid "format"
+#~ msgstr "formato"
+
+#~ msgid "no still image files found"
+#~ msgstr "file immagini statiche non trovati"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "ancora"
+
+#~ msgid "video"
+#~ msgstr "video"
+
+#~ msgid "1.33"
+#~ msgstr "1.33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Sorgente scalato a 1.19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Sorgente scalato a 1.33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Sorgente scalato a 1.33:1 e poi inviato come Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Sorgente scalato a 1.375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Sorgente scalato a 1.37:1 (Academy ratio)"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Sorgente scalato a 1.66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Sorgente scalato a 1.66:1 e poi inviato come Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Sorgente scalato a 1.78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Sorgente scalato a 1.78:1 e poi inviato come Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Sorgente scalato a Flat (1.85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Sorgente scalato a Scope (2.39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr ""
+#~ "Sorgente scalato per adattarsi a Flat mantentendo le sue proporzioni"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr ""
+#~ "Sorgente scalato per adattarsi a Scope mantentendo le sue proporzioni"
+
+#~ msgid "adding to queue of %1"
+#~ msgstr "aggiungo alla coda %1"
+
+#~ msgid "decoder sleeps with queue of %1"
+#~ msgstr "il decoder è in pausa con la coda di %1"
+
+#~ msgid "decoder wakes with queue of %1"
+#~ msgstr "il decoder riparte con la coda di %1"
diff --git a/src/lib/po/sv_SE.po b/src/lib/po/sv_SE.po
new file mode 100644
index 000000000..13989e58e
--- /dev/null
+++ b/src/lib/po/sv_SE.po
@@ -0,0 +1,656 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: DCP-o-matic\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-15 22:07+0100\n"
+"PO-Revision-Date: 2013-04-10 15:35+0100\n"
+"Last-Translator: Adam Klotblixt <adam.klotblixt@gmail.com>\n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.5\n"
+
+#: src/lib/sndfile_content.cc:70
+msgid "%1 channels, %2kHz, %3 samples"
+msgstr ""
+
+#: src/lib/ffmpeg_content.cc:193
+#, fuzzy
+msgid "%1 frames; %2 frames per second"
+msgstr "bilder per sekund"
+
+#: src/lib/video_content.cc:131
+msgid "%1x%2 pixels (%3:1)"
+msgstr ""
+
+#: src/lib/transcode_job.cc:81
+msgid "0%"
+msgstr "0%"
+
+#: src/lib/ratio.cc:47
+msgid "1.19"
+msgstr "1,19"
+
+#: src/lib/ratio.cc:50
+msgid "1.375"
+msgstr "1,375"
+
+#: src/lib/ratio.cc:51
+msgid "1.66"
+msgstr "1,66"
+
+#: src/lib/ratio.cc:52
+msgid "16:9"
+msgstr "16:9"
+
+#: src/lib/filter.cc:88
+msgid "3D denoiser"
+msgstr "3D brusreducering"
+
+#: src/lib/ratio.cc:48
+msgid "4:3"
+msgstr ""
+
+#: src/lib/ratio.cc:49
+msgid "Academy"
+msgstr "Academy"
+
+#: src/lib/dcp_content_type.cc:53
+msgid "Advertisement"
+msgstr "Reklam"
+
+#: src/lib/job.cc:72
+msgid "An error occurred whilst handling the file %1."
+msgstr "Ett fel inträffade vid hantering av filen %1"
+
+#: src/lib/analyse_audio_job.cc:53
+msgid "Analyse audio of %1"
+msgstr "Analysera %1s audio"
+
+#: src/lib/scaler.cc:64
+msgid "Area"
+msgstr "Yta"
+
+#: src/lib/scaler.cc:62
+msgid "Bicubic"
+msgstr "Bikubisk"
+
+#: src/lib/scaler.cc:69
+msgid "Bilinear"
+msgstr "Bilinjär"
+
+#: src/lib/job.cc:320
+msgid "Cancelled"
+msgstr "Avbruten"
+
+#: src/lib/exceptions.cc:60
+msgid "Cannot handle pixel format %1 during %2"
+msgstr "Kan inte hantera pixelformat %1 under %2"
+
+#: src/lib/util.cc:700
+msgid "Centre"
+msgstr "Mitt"
+
+#: src/lib/scp_dcp_job.cc:109
+msgid "Copy DCP to TMS"
+msgstr "Kopiera DCP till TMS"
+
+#: src/lib/scp_dcp_job.cc:128
+msgid "Could not connect to server %1 (%2)"
+msgstr "Kunde inte ansluta till server %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:150
+msgid "Could not create remote directory %1 (%2)"
+msgstr "Kunde inte skapa fjärrkatalog %1 (%2)"
+
+#: src/lib/scp_dcp_job.cc:175
+msgid "Could not open %1 to send"
+msgstr "Kunde inte öppna %1 för att skicka"
+
+#: src/lib/scp_dcp_job.cc:145
+msgid "Could not start SCP session (%1)"
+msgstr "Kunde inte starta SCP-session (%1)"
+
+#: src/lib/scp_dcp_job.cc:187
+msgid "Could not write to remote file (%1)"
+msgstr "Kunde inte skriva till fjärrfil (%1)"
+
+#: src/lib/filter.cc:77
+msgid "Cubic interpolating deinterlacer"
+msgstr "Kubiskt interpolerande avflätare"
+
+#: src/lib/util.cc:723
+msgid "DCP and source have the same rate.\n"
+msgstr "DCP och källa har samma bildfrekvens.\n"
+
+#: src/lib/util.cc:733
+msgid "DCP will run at %1%% of the source speed.\n"
+msgstr "DCP kommer att köras på %1%% av källans hastighet.\n"
+
+#: src/lib/util.cc:726
+msgid "DCP will use every other frame of the source.\n"
+msgstr "DCP kommer att använda varannan bild från källan.\n"
+
+#: src/lib/filter.cc:68 src/lib/filter.cc:69 src/lib/filter.cc:70
+#: src/lib/filter.cc:71 src/lib/filter.cc:72 src/lib/filter.cc:73
+msgid "De-blocking"
+msgstr "Kantighetsutjämning"
+
+#: src/lib/filter.cc:75 src/lib/filter.cc:76 src/lib/filter.cc:77
+#: src/lib/filter.cc:78 src/lib/filter.cc:79 src/lib/filter.cc:80
+#: src/lib/filter.cc:81 src/lib/filter.cc:82 src/lib/filter.cc:83
+msgid "De-interlacing"
+msgstr "Avflätning"
+
+#: src/lib/filter.cc:74
+msgid "Deringing filter"
+msgstr "Avringningsfilter"
+
+#: src/lib/dolby_cp750.cc:27
+msgid "Dolby CP750"
+msgstr "Dolby CP750"
+
+#: src/lib/util.cc:728
+msgid "Each source frame will be doubled in the DCP.\n"
+msgstr "Varje bild från källan kommer att användas två gånger i DCPn.\n"
+
+#: src/lib/job.cc:318
+msgid "Error (%1)"
+msgstr "Fel (%1)"
+
+#: src/lib/examine_content_job.cc:45
+msgid "Examine content"
+msgstr "Undersök innehållet"
+
+#: src/lib/filter.cc:72
+msgid "Experimental horizontal deblocking filter 1"
+msgstr "Experimentellt filter för horisontal kantighetsutjämning 1"
+
+#: src/lib/filter.cc:73
+msgid "Experimental vertical deblocking filter 1"
+msgstr "Experimentellt filter för vertikal kantighetsutjämning 1"
+
+#: src/lib/filter.cc:79
+msgid "FFMPEG deinterlacer"
+msgstr "FFMPEG avflätare"
+
+#: src/lib/filter.cc:80
+msgid "FIR low-pass deinterlacer"
+msgstr "FIR lågpass-avflätare"
+
+#: src/lib/scp_dcp_job.cc:138
+msgid "Failed to authenticate with server (%1)"
+msgstr "Misslyckades att autentisera med server (%1)"
+
+#: src/lib/scaler.cc:70
+msgid "Fast Bilinear"
+msgstr "Snabb bilinjär"
+
+#: src/lib/dcp_content_type.cc:44
+msgid "Feature"
+msgstr "Långfilm"
+
+#: src/lib/ratio.cc:53
+msgid "Flat"
+msgstr "Flat"
+
+#: src/lib/filter.cc:85
+msgid "Force quantizer"
+msgstr "Tvinga kvantiserare"
+
+#: src/lib/ratio.cc:55
+msgid "Full frame"
+msgstr ""
+
+#: src/lib/scaler.cc:65
+msgid "Gaussian"
+msgstr "Gaussisk"
+
+#: src/lib/filter.cc:86
+msgid "Gradient debander"
+msgstr "Gradientutjämnare"
+
+#: src/lib/filter.cc:89
+msgid "High quality 3D denoiser"
+msgstr "Högkvalitets 3D-brusreducering"
+
+#: src/lib/filter.cc:68
+msgid "Horizontal deblocking filter"
+msgstr "Filter för horisontal kantighetsutjämning"
+
+#: src/lib/filter.cc:70
+msgid "Horizontal deblocking filter A"
+msgstr "Filter för horisontal kantighetsutjämning A"
+
+#: src/lib/imagemagick_content.cc:50
+msgid "Image: %1"
+msgstr ""
+
+#: src/lib/job.cc:96 src/lib/job.cc:105
+msgid ""
+"It is not known what caused this error. The best idea is to report the "
+"problem to the DCP-o-matic mailing list (carl@dcpomatic.com)"
+msgstr ""
+"Det är inte känt vad som orsakade detta fel. Bästa sättet att rapportera "
+"problemet är till DCP-o-matics mejl-lista (carl@dcpomatic.com)"
+
+#: src/lib/filter.cc:82
+msgid "Kernel deinterlacer"
+msgstr "Kernel-avflätare"
+
+#: src/lib/scaler.cc:66
+msgid "Lanczos"
+msgstr "Lanczos"
+
+#: src/lib/util.cc:698
+msgid "Left"
+msgstr "Vänster"
+
+#: src/lib/util.cc:702
+msgid "Left surround"
+msgstr "Vänster surround"
+
+#: src/lib/util.cc:701
+msgid "Lfe (sub)"
+msgstr "Lfe (sub)"
+
+#: src/lib/filter.cc:75
+msgid "Linear blend deinterlacer"
+msgstr "Linjär blandningsavflätare"
+
+#: src/lib/filter.cc:76
+msgid "Linear interpolating deinterlacer"
+msgstr "Linjär interpolationsavflätare"
+
+#: src/lib/filter.cc:78
+msgid "Median deinterlacer"
+msgstr "Median-avflätare"
+
+#: src/lib/filter.cc:74 src/lib/filter.cc:85 src/lib/filter.cc:86
+#: src/lib/filter.cc:87 src/lib/filter.cc:90
+msgid "Misc"
+msgstr "Diverse"
+
+#: src/lib/filter.cc:81
+msgid "Motion compensating deinterlacer"
+msgstr "Rörelsekompenserande avflätare"
+
+#: src/lib/ffmpeg_content.cc:181
+msgid "Movie: %1"
+msgstr ""
+
+#: src/lib/filter.cc:84 src/lib/filter.cc:88 src/lib/filter.cc:89
+#: src/lib/filter.cc:91
+msgid "Noise reduction"
+msgstr "Brusreducering"
+
+#: src/lib/job.cc:316
+msgid "OK (ran for %1)"
+msgstr "OK (kördes %1)"
+
+#: src/lib/filter.cc:91
+msgid "Overcomplete wavelet denoiser"
+msgstr "Överkomplett wavelet-brusreducering"
+
+#: src/lib/dcp_content_type.cc:51
+msgid "Policy"
+msgstr "Policy"
+
+#: src/lib/dcp_content_type.cc:52
+msgid "Public Service Announcement"
+msgstr "Offentligt Servicemeddelande"
+
+#: src/lib/dcp_content_type.cc:49
+msgid "Rating"
+msgstr "Klassificeringsklipp"
+
+#: src/lib/util.cc:699
+msgid "Right"
+msgstr "Höger"
+
+#: src/lib/util.cc:703
+msgid "Right surround"
+msgstr "Höger surround"
+
+#: src/lib/scp_dcp_job.cc:133
+msgid "SSH error (%1)"
+msgstr "SSH fel (%1)"
+
+#: src/lib/ratio.cc:54
+msgid "Scope"
+msgstr "Scope"
+
+#: src/lib/dcp_content_type.cc:45
+msgid "Short"
+msgstr "Kortfilm"
+
+#: src/lib/scaler.cc:67
+msgid "Sinc"
+msgstr "Sinc"
+
+#: src/lib/sndfile_content.cc:57
+#, fuzzy
+msgid "Sound file: %1"
+msgstr "kunde inte öppna fil %1"
+
+#: src/lib/scaler.cc:68
+msgid "Spline"
+msgstr "Spline"
+
+#: src/lib/dcp_content_type.cc:50
+msgid "Teaser"
+msgstr "Teaser"
+
+#: src/lib/filter.cc:90
+msgid "Telecine filter"
+msgstr "Telecine-filter"
+
+#: src/lib/filter.cc:84
+msgid "Temporal noise reducer"
+msgstr "Temporal brusreducering"
+
+#: src/lib/dcp_content_type.cc:47
+msgid "Test"
+msgstr "Test"
+
+#: src/lib/job.cc:78
+msgid ""
+"The drive that the film is stored on is low in disc space. Free some more "
+"space and try again."
+msgstr ""
+"Enheten som filmen lagras på har för lite ledigt utrymme. Frigör utrymme och "
+"försök igen."
+
+#: src/lib/film.cc:364
+msgid ""
+"This film was created with an older version of DCP-o-matic, and "
+"unfortunately it cannot be loaded into this version. You will need to "
+"create a new Film, re-add your content and set it up again. Sorry!"
+msgstr ""
+
+#: src/lib/dcp_content_type.cc:46
+msgid "Trailer"
+msgstr "Trailer"
+
+#: src/lib/transcode_job.cc:50
+msgid "Transcode %1"
+msgstr "Konvertera %1"
+
+#: src/lib/dcp_content_type.cc:48
+msgid "Transitional"
+msgstr "Övergångsklipp"
+
+#: src/lib/job.cc:104
+msgid "Unknown error"
+msgstr "Okänt fel"
+
+# Svengelska
+#: src/lib/ffmpeg_decoder.cc:264
+#, fuzzy
+msgid "Unrecognised audio sample format (%1)"
+msgstr "Okänt audio-sampelformat (%1)"
+
+#: src/lib/filter.cc:87
+msgid "Unsharp mask and Gaussian blur"
+msgstr "Oskärpemask och Gaussisk suddighet"
+
+#: src/lib/filter.cc:69
+msgid "Vertical deblocking filter"
+msgstr "Filter för vertikal kantighetsutjämning"
+
+#: src/lib/filter.cc:71
+msgid "Vertical deblocking filter A"
+msgstr "Filter för vertikal kantighetsutjämning A"
+
+#: src/lib/scp_dcp_job.cc:101
+msgid "Waiting"
+msgstr "Väntar"
+
+#: src/lib/scaler.cc:63
+msgid "X"
+msgstr "X"
+
+# Filtret heter så, ska ej översättas
+#: src/lib/filter.cc:83
+msgid "Yet Another Deinterlacing Filter"
+msgstr "Yet Another Deinterlacing Filter"
+
+#: src/lib/film.cc:273
+msgid "You must add some content to the DCP before creating it"
+msgstr ""
+
+#: src/lib/film.cc:232
+msgid "cannot contain slashes"
+msgstr "får inte innehålla snedstreck"
+
+# Svengelska
+#: src/lib/util.cc:494
+#, fuzzy
+msgid "connect timed out"
+msgstr "uppkopplingen tajmade ur"
+
+#: src/lib/scp_dcp_job.cc:119
+msgid "connecting"
+msgstr "kopplar upp"
+
+#: src/lib/film.cc:269
+#, fuzzy
+msgid "container"
+msgstr "innehåll"
+
+#: src/lib/film.cc:277
+msgid "content type"
+msgstr "innehållstyp"
+
+#: src/lib/scp_dcp_job.cc:168
+msgid "copying %1"
+msgstr "kopierar %1"
+
+#: src/lib/exceptions.cc:36
+msgid "could not create file %1"
+msgstr "kunde inte skapa fil %1"
+
+#: src/lib/ffmpeg.cc:128
+msgid "could not find audio decoder"
+msgstr "kunde inte hitta audio-avkodare"
+
+#: src/lib/ffmpeg.cc:76
+msgid "could not find stream information"
+msgstr "kunde inte hitta information om strömmen"
+
+#: src/lib/ffmpeg_decoder.cc:498
+msgid "could not find subtitle decoder"
+msgstr "kunde inte hitta undertext-avkodare"
+
+#: src/lib/ffmpeg.cc:107
+msgid "could not find video decoder"
+msgstr "kunde inte hitta video-avkodare"
+
+#: src/lib/sndfile_decoder.cc:45
+#, fuzzy
+msgid "could not open audio file for reading"
+msgstr "kunde inte öppna fil för läsning"
+
+#: src/lib/exceptions.cc:29
+msgid "could not open file %1"
+msgstr "kunde inte öppna fil %1"
+
+#: src/lib/dcp_video_frame.cc:263
+msgid "could not open file for reading"
+msgstr "kunde inte öppna fil för läsning"
+
+#: src/lib/exceptions.cc:44
+msgid "could not read from file %1 (%2)"
+msgstr "kunde inte läsa från fil %1 (%2)"
+
+#: src/lib/resampler.cc:76 src/lib/resampler.cc:96
+msgid "could not run sample-rate converter"
+msgstr "kunde inte köra sampelhastighetskonverteraren"
+
+#: src/lib/scp_dcp_job.cc:86
+msgid "could not start SCP session (%1)"
+msgstr "kunde inte starta SCP-session (%1)"
+
+#: src/lib/scp_dcp_job.cc:52
+msgid "could not start SSH session"
+msgstr "kunde inte starta SSH-session"
+
+#: src/lib/exceptions.cc:50
+msgid "could not write to file %1 (%2)"
+msgstr "kunde inte skriva till fil %1 (%2)"
+
+#: src/lib/transcode_job.cc:94
+msgid "frames per second"
+msgstr "bilder per sekund"
+
+#: src/lib/util.cc:146
+msgid "hour"
+msgstr "timme"
+
+#: src/lib/util.cc:143 src/lib/util.cc:148
+msgid "hours"
+msgstr "timmar"
+
+#: src/lib/util.cc:153
+msgid "minute"
+msgstr "minut"
+
+#: src/lib/util.cc:155
+msgid "minutes"
+msgstr "minuter"
+
+#: src/lib/util.cc:621
+msgid "missing key %1 in key-value set"
+msgstr "saknad nyckel %1 i nyckel-värde grupp"
+
+#: src/lib/exceptions.cc:54
+msgid "missing required setting %1"
+msgstr "saknad nödvändig inställning %1"
+
+#: src/lib/ffmpeg_decoder.cc:530
+msgid "multi-part subtitles not yet supported"
+msgstr "undertexter i flera delar stöds inte ännu"
+
+#: src/lib/film.cc:232 src/lib/film.cc:281
+msgid "name"
+msgstr "namn"
+
+#: src/lib/ffmpeg_decoder.cc:545
+msgid "non-bitmap subtitles not yet supported"
+msgstr "icke-rastergrafiska undertexter stöds inte ännu"
+
+#. / TRANSLATORS: remaining here follows an amount of time that is remaining
+#. / on an operation.
+#: src/lib/job.cc:313
+msgid "remaining"
+msgstr "återstående tid"
+
+#: src/lib/util.cc:158
+msgid "seconds"
+msgstr "sekunder"
+
+#~ msgid "1.66 within Flat"
+#~ msgstr "1,66 innanför Flat"
+
+#~ msgid "16:9 within Flat"
+#~ msgstr "16:9 innanför Flat"
+
+#~ msgid "16:9 within Scope"
+#~ msgstr "16:9 innanför Scope"
+
+#~ msgid "4:3 within Flat"
+#~ msgstr "4:3 innanför Flat"
+
+#~ msgid "A/B transcode %1"
+#~ msgstr "A/B konvertera %1"
+
+#~ msgid "Cannot resample audio as libswresample is not present"
+#~ msgstr ""
+#~ "Kan inte omsampla ljudet eftersom libswresample inte finns tillgängligt"
+
+#~ msgid "Examine content of %1"
+#~ msgstr "Undersök innehållet i %1"
+
+#~ msgid "Flat without stretch"
+#~ msgstr "Flat utan utsträckning"
+
+#~ msgid "Rec 709"
+#~ msgstr "Rec 709"
+
+#~ msgid "Scope without stretch"
+#~ msgstr "Scope utan utsträckning"
+
+#~ msgid "could not open external audio file for reading"
+#~ msgstr "kunde inte öppna extern audio-fil för läsning"
+
+#~ msgid "external audio files have differing lengths"
+#~ msgstr "externa audio-filer har olika längder"
+
+#~ msgid "external audio files must be mono"
+#~ msgstr "externa audio-filer måste vara mono"
+
+#~ msgid "format"
+#~ msgstr "format"
+
+#~ msgid "no still image files found"
+#~ msgstr "inga stillbildsfiler hittade"
+
+#~ msgid "sRGB"
+#~ msgstr "sRGB"
+
+#~ msgid "still"
+#~ msgstr "stillbild"
+
+#~ msgid "video"
+#~ msgstr "video"
+
+#~ msgid "1.33"
+#~ msgstr "1,33"
+
+#~ msgid "Source scaled to 1.19:1"
+#~ msgstr "Källan skalad till 1,19:1"
+
+#~ msgid "Source scaled to 1.33:1"
+#~ msgstr "Källan skalad till 1,33:1"
+
+#~ msgid "Source scaled to 1.33:1 then pillarboxed to Flat"
+#~ msgstr "Källan skalad till 1,33:1, med sorgkanter innanför Flat"
+
+#~ msgid "Source scaled to 1.375:1"
+#~ msgstr "Källan skalad till 1,375:1"
+
+#~ msgid "Source scaled to 1.37:1 (Academy ratio)"
+#~ msgstr "Källan skalad till 1,37:1 (Academy-förhållande)"
+
+#~ msgid "Source scaled to 1.66:1"
+#~ msgstr "Källan skalad till 1,66:1"
+
+#~ msgid "Source scaled to 1.66:1 then pillarboxed to Flat"
+#~ msgstr "Källan skalad till 1,66:1, med sorgkanter innanför Flat"
+
+#~ msgid "Source scaled to 1.78:1"
+#~ msgstr "Källan skalad till 1,78:1"
+
+#~ msgid "Source scaled to 1.78:1 then pillarboxed to Flat"
+#~ msgstr "Källan skalad till 1,78:1, med sorgkanter innanför Flat"
+
+#~ msgid "Source scaled to Flat (1.85:1)"
+#~ msgstr "Källan skalad till Flat (1,85:1)"
+
+#~ msgid "Source scaled to Scope (2.39:1)"
+#~ msgstr "Källan skalad till Scope (2,39:1)"
+
+#~ msgid "Source scaled to fit Flat preserving its aspect ratio"
+#~ msgstr ""
+#~ "Källan skalad för att rymmas inom Flat utan att ändra bildförhållandet"
+
+#~ msgid "Source scaled to fit Scope preserving its aspect ratio"
+#~ msgstr ""
+#~ "Källan skalad för att rymmas inom Scope utan att ändra bildförhållandet"
diff --git a/src/lib/ffmpeg_compatibility.h b/src/lib/position.h
index 772d22c33..8768bf5a8 100644
--- a/src/lib/ffmpeg_compatibility.h
+++ b/src/lib/position.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,15 +17,30 @@
*/
-struct AVFilterInOut;
+#ifndef DCPOMATIC_POSITION_H
+#define DCPOMATIC_POSITION_H
+
+/** @struct Position
+ * @brief A position.
+ */
+template <class T>
+class Position
+{
+public:
+ Position ()
+ : x (0)
+ , y (0)
+ {}
+
+ Position (T x_, T y_)
+ : x (x_)
+ , y (y_)
+ {}
+
+ /** x coordinate */
+ T x;
+ /** y coordinate */
+ T y;
+};
-extern AVFilter* get_sink ();
-extern AVFilterInOut* avfilter_inout_alloc ();
-
-#ifndef HAVE_AV_PIXEL_FORMAT
-#define AVPixelFormat PixelFormat
-#endif
-
-#ifndef HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP
-extern int64_t av_frame_get_best_effort_timestamp (AVFrame const *);
#endif
diff --git a/src/lib/processor.h b/src/lib/processor.h
deleted file mode 100644
index 19d7c4b0c..000000000
--- a/src/lib/processor.h
+++ /dev/null
@@ -1,98 +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/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)
- {}
-
- virtual ~Processor() {}
-
- /** 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/ratio.cc b/src/lib/ratio.cc
new file mode 100644
index 000000000..5988b3418
--- /dev/null
+++ b/src/lib/ratio.cc
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libdcp/types.h>
+#include "ratio.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::vector;
+
+vector<Ratio const *> Ratio::_ratios;
+
+libdcp::Size
+Ratio::size (libdcp::Size full_frame) const
+{
+ if (_ratio < static_cast<float>(full_frame.width) / full_frame.height) {
+ return libdcp::Size (full_frame.height * _ratio, full_frame.height);
+ } else {
+ return libdcp::Size (full_frame.width, full_frame.width / _ratio);
+ }
+
+ return libdcp::Size ();
+}
+
+
+void
+Ratio::setup_ratios ()
+{
+ _ratios.push_back (new Ratio (float(1285) / 1080, "119", _("1.19"), "F"));
+ _ratios.push_back (new Ratio (float(1436) / 1080, "133", _("4:3"), "F"));
+ _ratios.push_back (new Ratio (float(1480) / 1080, "137", _("Academy"), "F"));
+ _ratios.push_back (new Ratio (float(1485) / 1080, "138", _("1.375"), "F"));
+ _ratios.push_back (new Ratio (float(1793) / 1080, "166", _("1.66"), "F"));
+ _ratios.push_back (new Ratio (float(1920) / 1080, "178", _("16:9"), "F"));
+ _ratios.push_back (new Ratio (float(1998) / 1080, "185", _("Flat"), "F"));
+ _ratios.push_back (new Ratio (float(2048) / 858, "239", _("Scope"), "S"));
+ _ratios.push_back (new Ratio (float(2048) / 1080, "full-frame", _("Full frame"), "C"));
+}
+
+Ratio const *
+Ratio::from_id (string i)
+{
+ vector<Ratio const *>::iterator j = _ratios.begin ();
+ while (j != _ratios.end() && (*j)->id() != i) {
+ ++j;
+ }
+
+ if (j == _ratios.end ()) {
+ return 0;
+ }
+
+ return *j;
+}
diff --git a/src/lib/ratio.h b/src/lib/ratio.h
new file mode 100644
index 000000000..c331edabe
--- /dev/null
+++ b/src/lib/ratio.h
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_RATIO_H
+#define DCPOMATIC_RATIO_H
+
+#include <vector>
+#include <boost/utility.hpp>
+#include <libdcp/util.h>
+
+class Ratio : public boost::noncopyable
+{
+public:
+ Ratio (float ratio, std::string id, std::string n, std::string d)
+ : _ratio (ratio)
+ , _id (id)
+ , _nickname (n)
+ , _dci_name (d)
+ {}
+
+ libdcp::Size size (libdcp::Size) const;
+
+ std::string id () const {
+ return _id;
+ }
+
+ std::string nickname () const {
+ return _nickname;
+ }
+
+ std::string dci_name () const {
+ return _dci_name;
+ }
+
+ float ratio () const {
+ return _ratio;
+ }
+
+ static void setup_ratios ();
+ static Ratio const * from_id (std::string i);
+ static std::vector<Ratio const *> all () {
+ return _ratios;
+ }
+
+private:
+ float _ratio;
+ /** id for use in metadata */
+ std::string _id;
+ /** nickname (e.g. Flat, Scope) */
+ std::string _nickname;
+ std::string _dci_name;
+
+ static std::vector<Ratio const *> _ratios;
+};
+
+#endif
diff --git a/src/lib/rect.h b/src/lib/rect.h
new file mode 100644
index 000000000..6f4709c08
--- /dev/null
+++ b/src/lib/rect.h
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_RECT_H
+#define DCPOMATIC_RECT_H
+
+#include "position.h"
+
+/* Put this inside a namespace as Apple put a Rect in the global namespace */
+
+namespace dcpomatic
+{
+
+/** @struct Rect
+ * @brief A rectangle.
+ */
+template <class T>
+class Rect
+{
+public:
+
+ Rect ()
+ : x (0)
+ , y (0)
+ , width (0)
+ , height (0)
+ {}
+
+ Rect (T x_, T y_, T w_, T h_)
+ : x (x_)
+ , y (y_)
+ , width (w_)
+ , height (h_)
+ {}
+
+ T x;
+ T y;
+ T width;
+ T height;
+
+ Position<T> position () const {
+ return Position<T> (x, y);
+ }
+
+ Rect<T> intersection (Rect<T> const & other) const {
+ T const tx = max (x, other.x);
+ T const ty = max (y, other.y);
+
+ return Rect (
+ tx, ty,
+ min (x + width, other.x + other.width) - tx,
+ min (y + height, other.y + other.height) - ty
+ );
+ }
+
+ bool contains (Position<T> p) const {
+ return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
+ }
+};
+
+}
+
+#endif
diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc
new file mode 100644
index 000000000..7bc933fd0
--- /dev/null
+++ b/src/lib/resampler.cc
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern "C" {
+#include "libavutil/channel_layout.h"
+}
+#include "resampler.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using std::pair;
+using std::make_pair;
+using boost::shared_ptr;
+
+Resampler::Resampler (int in, int out, int channels)
+ : _in_rate (in)
+ , _out_rate (out)
+ , _channels (channels)
+{
+ /* We will be using planar float data when we call the
+ resampler. As far as I can see, the audio channel
+ layout is not necessary for our purposes; it seems
+ only to be used get the number of channels and
+ decide if rematrixing is needed. It won't be, since
+ input and output layouts are the same.
+ */
+
+ _swr_context = swr_alloc_set_opts (
+ 0,
+ av_get_default_channel_layout (_channels),
+ AV_SAMPLE_FMT_FLTP,
+ _out_rate,
+ av_get_default_channel_layout (_channels),
+ AV_SAMPLE_FMT_FLTP,
+ _in_rate,
+ 0, 0
+ );
+
+ swr_init (_swr_context);
+}
+
+Resampler::~Resampler ()
+{
+ swr_free (&_swr_context);
+}
+
+pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
+Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+{
+ AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
+
+ /* Compute the resampled frames count and add 32 for luck */
+ int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
+ shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
+
+ int const resampled_frames = swr_convert (
+ _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) in->data(), in->frames()
+ );
+
+ if (resampled_frames < 0) {
+ throw EncodeError (_("could not run sample-rate converter"));
+ }
+
+ resampled->set_frames (resampled_frames);
+ return make_pair (resampled, resamp_time);
+}
+
+shared_ptr<const AudioBuffers>
+Resampler::flush ()
+{
+ shared_ptr<AudioBuffers> out (new AudioBuffers (_channels, 0));
+ int out_offset = 0;
+ int64_t const pass_size = 256;
+ shared_ptr<AudioBuffers> pass (new AudioBuffers (_channels, 256));
+
+ while (1) {
+ int const frames = swr_convert (_swr_context, (uint8_t **) pass->data(), pass_size, 0, 0);
+
+ if (frames < 0) {
+ throw EncodeError (_("could not run sample-rate converter"));
+ }
+
+ if (frames == 0) {
+ break;
+ }
+
+ out->ensure_size (out_offset + frames);
+ out->copy_from (pass.get(), frames, 0, out_offset);
+ out_offset += frames;
+ out->set_frames (out_offset);
+ }
+
+ return out;
+}
diff --git a/src/lib/delay_line.h b/src/lib/resampler.h
index fa2870ae7..69ec83ba9 100644
--- a/src/lib/delay_line.h
+++ b/src/lib/resampler.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,21 +18,27 @@
*/
#include <boost/shared_ptr.hpp>
-#include "processor.h"
+#include <boost/utility.hpp>
+extern "C" {
+#include <libswresample/swresample.h>
+}
+#include "types.h"
+#include "audio_content.h"
class AudioBuffers;
-/** A delay line for audio */
-class DelayLine : public AudioProcessor
+class Resampler : public boost::noncopyable
{
public:
- DelayLine (Log* log, int channels, int frames);
-
- 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;
+ Resampler (int, int, int);
+ ~Resampler ();
+
+ std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ boost::shared_ptr<const AudioBuffers> flush ();
+
+private:
+ SwrContext* _swr_context;
+ int _in_rate;
+ int _out_rate;
+ int _channels;
};
diff --git a/src/lib/scaler.cc b/src/lib/scaler.cc
index c81456a15..40a0f05b9 100644
--- a/src/lib/scaler.cc
+++ b/src/lib/scaler.cc
@@ -28,6 +28,8 @@ extern "C" {
}
#include "scaler.h"
+#include "i18n.h"
+
using namespace std;
vector<Scaler const *> Scaler::_scalers;
@@ -57,15 +59,15 @@ Scaler::all ()
void
Scaler::setup_scalers ()
{
- _scalers.push_back (new Scaler (SWS_BICUBIC, "bicubic", "Bicubic"));
- _scalers.push_back (new Scaler (SWS_X, "x", "X"));
- _scalers.push_back (new Scaler (SWS_AREA, "area", "Area"));
- _scalers.push_back (new Scaler (SWS_GAUSS, "gauss", "Gaussian"));
- _scalers.push_back (new Scaler (SWS_LANCZOS, "lanczos", "Lanczos"));
- _scalers.push_back (new Scaler (SWS_SINC, "sinc", "Sinc"));
- _scalers.push_back (new Scaler (SWS_SPLINE, "spline", "Spline"));
- _scalers.push_back (new Scaler (SWS_BILINEAR, "bilinear", "Bilinear"));
- _scalers.push_back (new Scaler (SWS_FAST_BILINEAR, "fastbilinear", "Fast Bilinear"));
+ _scalers.push_back (new Scaler (SWS_BICUBIC, N_("bicubic"), _("Bicubic")));
+ _scalers.push_back (new Scaler (SWS_X, N_("x"), _("X")));
+ _scalers.push_back (new Scaler (SWS_AREA, N_("area"), _("Area")));
+ _scalers.push_back (new Scaler (SWS_GAUSS, N_("gauss"), _("Gaussian")));
+ _scalers.push_back (new Scaler (SWS_LANCZOS, N_("lanczos"), _("Lanczos")));
+ _scalers.push_back (new Scaler (SWS_SINC, N_("sinc"), _("Sinc")));
+ _scalers.push_back (new Scaler (SWS_SPLINE, N_("spline"), _("Spline")));
+ _scalers.push_back (new Scaler (SWS_BILINEAR, N_("bilinear"), _("Bilinear")));
+ _scalers.push_back (new Scaler (SWS_FAST_BILINEAR, N_("fastbilinear"), _("Fast Bilinear")));
}
/** @param id One of our ids.
diff --git a/src/lib/scaler.h b/src/lib/scaler.h
index c80f4b7db..6a039edd8 100644
--- a/src/lib/scaler.h
+++ b/src/lib/scaler.h
@@ -21,16 +21,17 @@
* @brief A class to describe one of FFmpeg's software scalers.
*/
-#ifndef DVDOMATIC_SCALER_H
-#define DVDOMATIC_SCALER_H
+#ifndef DCPOMATIC_SCALER_H
+#define DCPOMATIC_SCALER_H
#include <string>
#include <vector>
+#include <boost/utility.hpp>
/** @class Scaler
* @brief Class to describe one of FFmpeg's software scalers
*/
-class Scaler
+class Scaler : public boost::noncopyable
{
public:
Scaler (int f, std::string i, std::string n);
diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc
index 22129f56c..528d393a3 100644
--- a/src/lib/scp_dcp_job.cc
+++ b/src/lib/scp_dcp_job.cc
@@ -34,6 +34,8 @@
#include "log.h"
#include "film.h"
+#include "i18n.h"
+
using std::string;
using std::stringstream;
using std::min;
@@ -47,7 +49,7 @@ public:
{
session = ssh_new ();
if (session == 0) {
- throw NetworkError ("Could not start SSH session");
+ throw NetworkError (_("could not start SSH session"));
}
}
@@ -81,7 +83,7 @@ public:
{
scp = ssh_scp_new (s, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, Config::instance()->tms_path().c_str ());
if (!scp) {
- throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (s)));
+ throw NetworkError (String::compose (_("could not start SCP session (%1)"), ssh_get_error (s)));
}
}
@@ -94,9 +96,9 @@ public:
};
-SCPDCPJob::SCPDCPJob (shared_ptr<Film> f, shared_ptr<Job> req)
- : Job (f, req)
- , _status ("Waiting")
+SCPDCPJob::SCPDCPJob (shared_ptr<const Film> f)
+ : Job (f)
+ , _status (_("Waiting"))
{
}
@@ -104,17 +106,17 @@ SCPDCPJob::SCPDCPJob (shared_ptr<Film> f, shared_ptr<Job> req)
string
SCPDCPJob::name () const
{
- return "Copy DCP to TMS";
+ return _("Copy DCP to TMS");
}
void
SCPDCPJob::run ()
{
- _film->log()->log ("SCP DCP job starting");
+ _film->log()->log (N_("SCP DCP job starting"));
SSHSession ss;
- set_status ("connecting");
+ set_status (_("connecting"));
ssh_options_set (ss.session, SSH_OPTIONS_HOST, Config::instance()->tms_ip().c_str ());
ssh_options_set (ss.session, SSH_OPTIONS_USER, Config::instance()->tms_user().c_str ());
@@ -123,29 +125,29 @@ SCPDCPJob::run ()
int r = ss.connect ();
if (r != SSH_OK) {
- throw NetworkError (String::compose ("Could not connect to server %1 (%2)", Config::instance()->tms_ip(), ssh_get_error (ss.session)));
+ throw NetworkError (String::compose (_("Could not connect to server %1 (%2)"), Config::instance()->tms_ip(), ssh_get_error (ss.session)));
}
int const state = ssh_is_server_known (ss.session);
if (state == SSH_SERVER_ERROR) {
- throw NetworkError (String::compose ("SSH error (%1)", ssh_get_error (ss.session)));
+ throw NetworkError (String::compose (_("SSH error (%1)"), ssh_get_error (ss.session)));
}
r = ssh_userauth_password (ss.session, 0, Config::instance()->tms_password().c_str ());
if (r != SSH_AUTH_SUCCESS) {
- throw NetworkError (String::compose ("Failed to authenticate with server (%1)", ssh_get_error (ss.session)));
+ throw NetworkError (String::compose (_("Failed to authenticate with server (%1)"), ssh_get_error (ss.session)));
}
SSHSCP sc (ss.session);
r = ssh_scp_init (sc.scp);
if (r != SSH_OK) {
- throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (ss.session)));
+ throw NetworkError (String::compose (_("Could not start SCP session (%1)"), ssh_get_error (ss.session)));
}
r = ssh_scp_push_directory (sc.scp, _film->dcp_name().c_str(), S_IRWXU);
if (r != SSH_OK) {
- throw NetworkError (String::compose ("Could not create remote directory %1 (%2)", _film->dcp_name(), ssh_get_error (ss.session)));
+ throw NetworkError (String::compose (_("Could not create remote directory %1 (%2)"), _film->dcp_name(), ssh_get_error (ss.session)));
}
string const dcp_dir = _film->dir (_film->dcp_name());
@@ -161,33 +163,30 @@ SCPDCPJob::run ()
for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (dcp_dir); i != boost::filesystem::directory_iterator(); ++i) {
- /* Aah, the sweet smell of progress */
-#if BOOST_FILESYSTEM_VERSION == 3
string const leaf = boost::filesystem::path(*i).leaf().generic_string ();
-#else
- string const leaf = i->leaf ();
-#endif
- set_status ("copying " + leaf);
+ set_status (String::compose (_("copying %1"), leaf));
boost::uintmax_t to_do = boost::filesystem::file_size (*i);
ssh_scp_push_file (sc.scp, leaf.c_str(), to_do, S_IRUSR | S_IWUSR);
- FILE* f = fopen (boost::filesystem::path (*i).string().c_str(), "rb");
+ FILE* f = fopen (boost::filesystem::path (*i).string().c_str(), N_("rb"));
if (f == 0) {
- throw NetworkError (String::compose ("Could not open %1 to send", *i));
+ throw NetworkError (String::compose (_("Could not open %1 to send"), *i));
}
while (to_do > 0) {
int const t = min (to_do, buffer_size);
size_t const read = fread (buffer, 1, t, f);
if (read != size_t (t)) {
+ fclose (f);
throw ReadFileError (boost::filesystem::path (*i).string());
}
r = ssh_scp_write (sc.scp, buffer, t);
if (r != SSH_OK) {
- throw NetworkError (String::compose ("Could not write to remote file (%1)", ssh_get_error (ss.session)));
+ fclose (f);
+ throw NetworkError (String::compose (_("Could not write to remote file (%1)"), ssh_get_error (ss.session)));
}
to_do -= t;
bytes_transferred += t;
@@ -199,7 +198,7 @@ SCPDCPJob::run ()
}
set_progress (1);
- set_status ("");
+ set_status (N_(""));
set_state (FINISHED_OK);
}
@@ -210,7 +209,7 @@ SCPDCPJob::status () const
stringstream s;
s << Job::status ();
if (!_status.empty ()) {
- s << "; " << _status;
+ s << N_("; ") << _status;
}
return s.str ();
}
diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h
index 5d0bfe7b4..bdc83af18 100644
--- a/src/lib/scp_dcp_job.h
+++ b/src/lib/scp_dcp_job.h
@@ -26,7 +26,7 @@
class SCPDCPJob : public Job
{
public:
- SCPDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<Job> req);
+ SCPDCPJob (boost::shared_ptr<const Film>);
std::string name () const;
void run ();
@@ -34,7 +34,7 @@ public:
private:
void set_status (std::string);
-
+
mutable boost::mutex _status_mutex;
std::string _status;
};
diff --git a/src/lib/server.cc b/src/lib/server.cc
index bea75cff8..0212dbbed 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -28,13 +28,16 @@
#include <iostream>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
+#include <boost/scoped_array.hpp>
+#include <libcxml/cxml.h>
#include "server.h"
#include "util.h"
#include "scaler.h"
#include "image.h"
#include "dcp_video_frame.h"
#include "config.h"
-#include "subtitle.h"
+
+#include "i18n.h"
using std::string;
using std::stringstream;
@@ -45,34 +48,41 @@ using boost::algorithm::is_any_of;
using boost::algorithm::split;
using boost::thread;
using boost::bind;
+using boost::scoped_array;
+using boost::optional;
+using libdcp::Size;
+
+ServerDescription::ServerDescription (shared_ptr<const cxml::Node> node)
+{
+ _host_name = node->string_child ("HostName");
+ _threads = node->number_child<int> ("Threads");
+}
+
+void
+ServerDescription::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("HostName")->add_child_text (_host_name);
+ root->add_child("Threads")->add_child_text (boost::lexical_cast<string> (_threads));
+}
/** Create a server description from a string of metadata returned from as_metadata().
* @param v Metadata.
* @return ServerDescription, or 0.
*/
-ServerDescription *
+optional<ServerDescription>
ServerDescription::create_from_metadata (string v)
{
vector<string> b;
- split (b, v, is_any_of (" "));
+ split (b, v, is_any_of (N_(" ")));
if (b.size() != 2) {
- return 0;
+ return optional<ServerDescription> ();
}
- return new ServerDescription (b[0], atoi (b[1].c_str ()));
+ return ServerDescription (b[0], atoi (b[1].c_str ()));
}
-/** @return Description of this server as text */
-string
-ServerDescription::as_metadata () const
-{
- stringstream s;
- s << _host_name << " " << _threads;
- return s.str ();
-}
-
-Server::Server (Log* log)
+Server::Server (shared_ptr<Log> log)
: _log (log)
{
@@ -81,53 +91,26 @@ Server::Server (Log* log)
int
Server::process (shared_ptr<Socket> socket)
{
- char buffer[512];
- socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
- socket->consume (strlen (buffer) + 1);
+ uint32_t length = socket->read_uint32 ();
+ scoped_array<char> buffer (new char[length]);
+ socket->read (reinterpret_cast<uint8_t*> (buffer.get()), length);
- stringstream s (buffer);
- multimap<string, string> kv = read_key_value (s);
-
- if (get_required_string (kv, "encode") != "please") {
+ stringstream s (buffer.get());
+ shared_ptr<cxml::Document> xml (new cxml::Document ("EncodingRequest"));
+ xml->read_stream (s);
+ if (xml->number_child<int> ("Version") != SERVER_LINK_VERSION) {
+ _log->log ("Mismatched server/client versions");
return -1;
}
- Size in_size (get_required_int (kv, "input_width"), get_required_int (kv, "input_height"));
- int pixel_format_int = get_required_int (kv, "input_pixel_format");
- Size out_size (get_required_int (kv, "output_width"), get_required_int (kv, "output_height"));
- int padding = get_required_int (kv, "padding");
- int subtitle_offset = get_required_int (kv, "subtitle_offset");
- float subtitle_scale = get_required_float (kv, "subtitle_scale");
- string scaler_id = get_required_string (kv, "scaler");
- int frame = get_required_int (kv, "frame");
- int frames_per_second = get_required_int (kv, "frames_per_second");
- string post_process = get_optional_string (kv, "post_process");
- int colour_lut_index = get_required_int (kv, "colour_lut");
- int j2k_bandwidth = get_required_int (kv, "j2k_bandwidth");
- Position subtitle_position (get_optional_int (kv, "subtitle_x"), get_optional_int (kv, "subtitle_y"));
- Size subtitle_size (get_optional_int (kv, "subtitle_width"), get_optional_int (kv, "subtitle_height"));
-
- /* This checks that colour_lut_index is within range */
- colour_lut_index_to_name (colour_lut_index);
-
- PixelFormat pixel_format = (PixelFormat) pixel_format_int;
- Scaler const * scaler = Scaler::from_id (scaler_id);
-
- shared_ptr<Image> image (new SimpleImage (pixel_format, in_size, true));
-
- image->read_from_socket (socket);
+ libdcp::Size size (
+ xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
+ );
- shared_ptr<Subtitle> sub;
- if (subtitle_size.width && subtitle_size.height) {
- shared_ptr<Image> subtitle_image (new SimpleImage (PIX_FMT_RGBA, subtitle_size, true));
- subtitle_image->read_from_socket (socket);
- sub.reset (new Subtitle (subtitle_position, subtitle_image));
- }
+ shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, true));
- DCPVideoFrame dcp_video_frame (
- image, sub, out_size, padding, subtitle_offset, subtitle_scale,
- scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log
- );
+ image->read_from_socket (socket);
+ DCPVideoFrame dcp_video_frame (image, xml, _log);
shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
try {
@@ -135,13 +118,13 @@ Server::process (shared_ptr<Socket> socket)
} catch (std::exception& e) {
_log->log (String::compose (
"Send failed; frame %1, data size %2, pixel format %3, image size %4x%5, %6 components",
- frame, encoded->size(), image->pixel_format(), image->size().width, image->size().height, image->components()
+ dcp_video_frame.frame(), encoded->size(), image->pixel_format(), image->size().width, image->size().height, image->components()
)
);
throw;
}
- return frame;
+ return dcp_video_frame.frame ();
}
void
@@ -166,7 +149,7 @@ Server::worker_thread ()
try {
frame = process (socket);
} catch (std::exception& e) {
- _log->log (String::compose ("Error: %1", e.what()));
+ _log->log (String::compose (N_("Error: %1"), e.what()));
}
socket.reset ();
@@ -176,7 +159,7 @@ Server::worker_thread ()
if (frame >= 0) {
struct timeval end;
gettimeofday (&end, 0);
- _log->log (String::compose ("Encoded frame %1 in %2", frame, seconds (end) - seconds (start)));
+ _log->log (String::compose (N_("Encoded frame %1 in %2"), frame, seconds (end) - seconds (start)));
}
_worker_condition.notify_all ();
@@ -186,7 +169,7 @@ Server::worker_thread ()
void
Server::run (int num_threads)
{
- _log->log (String::compose ("Server starting with %1 threads", num_threads));
+ _log->log (String::compose (N_("Server starting with %1 threads"), num_threads));
for (int i = 0; i < num_threads; ++i) {
_worker_threads.push_back (new thread (bind (&Server::worker_thread, this)));
diff --git a/src/lib/server.h b/src/lib/server.h
index 32ba8dc4b..77b51d079 100644
--- a/src/lib/server.h
+++ b/src/lib/server.h
@@ -17,6 +17,9 @@
*/
+#ifndef DCPOMATIC_SERVER_H
+#define DCPOMATIC_SERVER_H
+
/** @file src/server.h
* @brief Class to describe a server to which we can send
* encoding work, and a class to implement such a server.
@@ -26,16 +29,27 @@
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/thread/condition.hpp>
+#include <boost/optional.hpp>
+#include <libxml++/libxml++.h>
#include "log.h"
class Socket;
+namespace cxml {
+ class Node;
+}
+
/** @class ServerDescription
* @brief Class to describe a server to which we can send encoding work.
*/
class ServerDescription
{
public:
+ ServerDescription ()
+ : _host_name ("")
+ , _threads (1)
+ {}
+
/** @param h Server host name or IP address in string form.
* @param t Number of threads to use on the server.
*/
@@ -44,6 +58,10 @@ public:
, _threads (t)
{}
+ ServerDescription (boost::shared_ptr<const cxml::Node>);
+
+ /* Default copy constructor is fine */
+
/** @return server's host name or IP address in string form */
std::string host_name () const {
return _host_name;
@@ -62,9 +80,9 @@ public:
_threads = t;
}
- std::string as_metadata () const;
+ void as_xml (xmlpp::Node *) const;
- static ServerDescription * create_from_metadata (std::string v);
+ static boost::optional<ServerDescription> create_from_metadata (std::string);
private:
/** server's host name */
@@ -73,10 +91,10 @@ private:
int _threads;
};
-class Server
+class Server : public boost::noncopyable
{
public:
- Server (Log* log);
+ Server (boost::shared_ptr<Log> log);
void run (int num_threads);
@@ -88,5 +106,7 @@ private:
std::list<boost::shared_ptr<Socket> > _queue;
boost::mutex _worker_mutex;
boost::condition _worker_condition;
- Log* _log;
+ boost::shared_ptr<Log> _log;
};
+
+#endif
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
new file mode 100644
index 000000000..d57cf04e3
--- /dev/null
+++ b/src/lib/sndfile_content.cc
@@ -0,0 +1,170 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "film.h"
+#include "compose.hpp"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , AudioContent (f, p)
+ , _audio_channels (0)
+ , _audio_length (0)
+ , _audio_frame_rate (0)
+{
+
+}
+
+SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , AudioContent (f, node)
+ , _audio_mapping (node->node_child ("AudioMapping"))
+{
+ _audio_channels = node->number_child<int> ("AudioChannels");
+ _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+ _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
+}
+
+string
+SndfileContent::summary () const
+{
+ /* Get the string() here so that the name does not have quotes around it */
+ return String::compose (_("%1 [audio]"), path().filename().string());
+}
+
+string
+SndfileContent::technical_summary () const
+{
+ return Content::technical_summary() + " - "
+ + AudioContent::technical_summary ()
+ + " - sndfile";
+}
+
+string
+SndfileContent::information () const
+{
+ if (_audio_frame_rate == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (
+ _("%1 channels, %2kHz, %3 samples"),
+ audio_channels(),
+ content_audio_frame_rate() / 1000.0,
+ audio_length()
+ );
+
+ return s.str ();
+}
+
+bool
+SndfileContent::valid_file (boost::filesystem::path f)
+{
+ /* XXX: more extensions */
+ string ext = f.extension().string();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+ return (ext == ".wav" || ext == ".aif" || ext == ".aiff");
+}
+
+void
+SndfileContent::examine (shared_ptr<Job> job)
+{
+ job->set_progress_unknown ();
+ Content::examine (job);
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ SndfileDecoder dec (film, shared_from_this());
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_channels = dec.audio_channels ();
+ _audio_length = dec.audio_length ();
+ _audio_frame_rate = dec.audio_frame_rate ();
+ }
+
+ signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+ signal_changed (AudioContentProperty::AUDIO_LENGTH);
+ signal_changed (AudioContentProperty::AUDIO_FRAME_RATE);
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ /* XXX: do this in signal_changed...? */
+ _audio_mapping = AudioMapping (_audio_channels);
+ _audio_mapping.make_default ();
+ }
+
+ signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
+
+void
+SndfileContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("Sndfile");
+ Content::as_xml (node);
+ AudioContent::as_xml (node);
+
+ node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (audio_channels ()));
+ node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length ()));
+ node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (content_audio_frame_rate ()));
+ _audio_mapping.as_xml (node->add_child("AudioMapping"));
+}
+
+Time
+SndfileContent::full_length () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ return film->audio_frames_to_time (audio_length ());
+}
+
+int
+SndfileContent::output_audio_frame_rate () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ return film->audio_frame_rate ();
+}
+
+void
+SndfileContent::set_audio_mapping (AudioMapping m)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_mapping = m;
+ }
+
+ signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
new file mode 100644
index 000000000..191d62527
--- /dev/null
+++ b/src/lib/sndfile_content.h
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SNDFILE_CONTENT_H
+#define DCPOMATIC_SNDFILE_CONTENT_H
+
+extern "C" {
+#include <libavutil/audioconvert.h>
+}
+#include "audio_content.h"
+
+namespace cxml {
+ class Node;
+}
+
+class SndfileContent : public AudioContent
+{
+public:
+ SndfileContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<SndfileContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *) const;
+ Time full_length () const;
+
+ /* AudioContent */
+ int audio_channels () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_channels;
+ }
+
+ AudioContent::Frame audio_length () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_length;
+ }
+
+ int content_audio_frame_rate () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_frame_rate;
+ }
+
+ int output_audio_frame_rate () const;
+
+ AudioMapping audio_mapping () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _audio_mapping;
+ }
+
+ void set_audio_mapping (AudioMapping);
+
+ static bool valid_file (boost::filesystem::path);
+
+private:
+ int _audio_channels;
+ AudioContent::Frame _audio_length;
+ int _audio_frame_rate;
+ AudioMapping _audio_mapping;
+};
+
+#endif
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
new file mode 100644
index 000000000..1fc1ecaf2
--- /dev/null
+++ b/src/lib/sndfile_decoder.cc
@@ -0,0 +1,120 @@
+/*
+ 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 <iostream>
+#include <sndfile.h>
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "film.h"
+#include "exceptions.h"
+#include "audio_buffers.h"
+
+#include "i18n.h"
+
+using std::vector;
+using std::string;
+using std::min;
+using std::cout;
+using boost::shared_ptr;
+
+SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
+ : Decoder (f)
+ , AudioDecoder (f)
+ , _sndfile_content (c)
+ , _deinterleave_buffer (0)
+{
+ _info.format = 0;
+ _sndfile = sf_open (_sndfile_content->path().string().c_str(), SFM_READ, &_info);
+ if (!_sndfile) {
+ throw DecodeError (_("could not open audio file for reading"));
+ }
+
+ _done = 0;
+ _remaining = _info.frames;
+}
+
+SndfileDecoder::~SndfileDecoder ()
+{
+ sf_close (_sndfile);
+ delete[] _deinterleave_buffer;
+}
+
+void
+SndfileDecoder::pass ()
+{
+ /* 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 = _sndfile_content->content_audio_frame_rate() / 2;
+ sf_count_t const this_time = min (block, _remaining);
+
+ int const channels = _sndfile_content->audio_channels ();
+
+ shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time));
+
+ if (_sndfile_content->audio_channels() == 1) {
+ /* No de-interleaving required */
+ sf_read_float (_sndfile, data->data(0), this_time);
+ } else {
+ /* Deinterleave */
+ if (!_deinterleave_buffer) {
+ _deinterleave_buffer = new float[block * channels];
+ }
+ sf_readf_float (_sndfile, _deinterleave_buffer, this_time);
+ vector<float*> out_ptr (channels);
+ for (int i = 0; i < channels; ++i) {
+ out_ptr[i] = data->data(i);
+ }
+ float* in_ptr = _deinterleave_buffer;
+ for (int i = 0; i < this_time; ++i) {
+ for (int j = 0; j < channels; ++j) {
+ *out_ptr[j]++ = *in_ptr++;
+ }
+ }
+ }
+
+ data->set_frames (this_time);
+ audio (data, _done);
+ _done += this_time;
+ _remaining -= this_time;
+}
+
+int
+SndfileDecoder::audio_channels () const
+{
+ return _info.channels;
+}
+
+AudioContent::Frame
+SndfileDecoder::audio_length () const
+{
+ return _info.frames;
+}
+
+int
+SndfileDecoder::audio_frame_rate () const
+{
+ return _info.samplerate;
+}
+
+bool
+SndfileDecoder::done () const
+{
+ return _audio_position >= _sndfile_content->audio_length ();
+}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
new file mode 100644
index 000000000..77fa6d177
--- /dev/null
+++ b/src/lib/sndfile_decoder.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <sndfile.h>
+#include "decoder.h"
+#include "audio_decoder.h"
+
+class SndfileContent;
+
+class SndfileDecoder : public AudioDecoder
+{
+public:
+ SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+ ~SndfileDecoder ();
+
+ void pass ();
+ bool done () const;
+
+ int audio_channels () const;
+ AudioContent::Frame audio_length () const;
+ int audio_frame_rate () const;
+
+private:
+ boost::shared_ptr<const SndfileContent> _sndfile_content;
+ SNDFILE* _sndfile;
+ SF_INFO _info;
+ AudioContent::Frame _done;
+ AudioContent::Frame _remaining;
+ float* _deinterleave_buffer;
+};
diff --git a/src/lib/sound_processor.h b/src/lib/sound_processor.h
index 2edf38840..8f2652243 100644
--- a/src/lib/sound_processor.h
+++ b/src/lib/sound_processor.h
@@ -21,16 +21,17 @@
* @brief A class to describe a sound processor.
*/
-#ifndef DVDOMATIC_SOUND_PROCESSOR_H
-#define DVDOMATIC_SOUND_PROCESSOR_H
+#ifndef DCPOMATIC_SOUND_PROCESSOR_H
+#define DCPOMATIC_SOUND_PROCESSOR_H
#include <string>
#include <vector>
+#include <boost/utility.hpp>
/** @class SoundProcessor
* @brief Class to describe a sound processor.
*/
-class SoundProcessor
+class SoundProcessor : public boost::noncopyable
{
public:
SoundProcessor (std::string i, std::string n);
diff --git a/src/lib/stack.cpp b/src/lib/stack.cpp
new file mode 100644
index 000000000..a8183d344
--- /dev/null
+++ b/src/lib/stack.cpp
@@ -0,0 +1,461 @@
+// Copyright 2007 Edd Dawson.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+#include <cassert>
+#include <cstring>
+#include <cstdlib>
+#include <iomanip>
+#include <ostream>
+#include <stdexcept>
+#include <sstream>
+
+#include "stack.hpp"
+
+#if defined(_WIN32)
+# include <windows.h>
+# include <imagehlp.h>
+
+# if defined(__MINGW32__)
+# define PACKAGE 1
+# define PACKAGE_VERSION 1
+# include <bfd.h> // link against libbfd and libiberty
+# include <psapi.h> // link against psapi
+# include <cxxabi.h>
+# endif
+
+#elif defined(__GNUC__)
+# include <dlfcn.h>
+# include <cxxabi.h>
+#endif
+
+namespace
+{
+ const char * const unknown_function = "[unknown function]";
+ const char * const unknown_module = "[unknown module]";
+
+#if defined(__GNUC__)
+ std::string demangle(const char *name)
+ {
+ if (!name)
+ return unknown_function;
+
+ int status = 0;
+ char *d = 0;
+ std::string ret = name;
+ try
+ {
+ if ((d = abi::__cxa_demangle(name, 0, 0, &status)))
+ ret = d;
+ }
+ catch (const std::bad_alloc &) { }
+
+ std::free(d);
+ return ret;
+ }
+#endif
+
+#if defined(_WIN32)
+
+ // Derive from this to disallow copying of your class.
+ // c.f. boost::noncopyable
+ class uncopyable
+ {
+ protected:
+ uncopyable() { }
+
+ private:
+ uncopyable(const uncopyable &);
+ uncopyable &operator= (const uncopyable &);
+ };
+
+#if defined(__MINGW32__)
+
+ // Provides a means to translate a program counter offset in to the name of the corresponding function.
+ class bfd_context : uncopyable
+ {
+ private:
+ struct find_data
+ {
+ std::string func;
+ unsigned int line;
+ asymbol **symbol_table;
+ bfd_vma counter;
+ };
+
+ public:
+ bfd_context() :
+ abfd_(0),
+ sec_(0),
+ symbol_table_(0)
+ {
+ char procname[MAX_PATH];
+ GetModuleFileNameA(NULL, procname, sizeof procname);
+
+ bfd_init();
+ abfd_ = bfd_openr(procname, 0);
+ if (!abfd_)
+ throw std::runtime_error("Failed to parse object data for the executable");
+
+ char **formats = 0;
+ bool b1 = bfd_check_format(abfd_, bfd_object);
+ bool b2 = bfd_check_format_matches(abfd_, bfd_object, &formats);
+ bool b3 = bfd_get_file_flags(abfd_) & HAS_SYMS;
+
+ if (!(b1 && b2 && b3))
+ {
+ bfd_close(abfd_);
+ free(formats);
+ throw std::runtime_error("Failed to parse object data for the executable");
+ }
+ free(formats);
+
+ // Load symbol table
+ unsigned dummy = 0;
+ if (bfd_read_minisymbols(abfd_, FALSE, reinterpret_cast<void **>(&symbol_table_), &dummy) == 0 &&
+ bfd_read_minisymbols(abfd_, TRUE, reinterpret_cast<void **>(&symbol_table_), &dummy) < 0)
+ {
+ free(symbol_table_);
+ bfd_close(abfd_);
+ throw std::runtime_error("Failed to parse object data for the executable");
+ }
+ }
+
+ ~bfd_context()
+ {
+ free(symbol_table_);
+ bfd_close(abfd_);
+ }
+
+ std::pair<std::string, unsigned int> get_function_name_and_line(DWORD offset)
+ {
+ find_data data;
+ data.symbol_table = symbol_table_;
+ data.counter = offset;
+
+ bfd_map_over_sections(abfd_, &find_function_name_in_section, &data);
+
+ return std::make_pair(data.func, data.line);
+ }
+
+ private:
+ static void find_function_name_in_section(bfd *abfd, asection *sec, void *opaque_data)
+ {
+ assert(sec);
+ assert(opaque_data);
+ find_data &data = *static_cast<find_data *>(opaque_data);
+
+ if (!data.func.empty()) return; // already found it
+
+ if (!(bfd_get_section_flags(abfd, sec) & SEC_ALLOC)) return;
+
+ bfd_vma vma = bfd_get_section_vma(abfd, sec);
+ if (data.counter < vma || vma + bfd_get_section_size(sec) <= data.counter) return;
+
+ const char *func = 0;
+ const char *file = 0;
+ unsigned line = 0;
+
+ if (bfd_find_nearest_line(abfd, sec, data.symbol_table, data.counter - vma, &file, &func, &line) && func) {
+ data.func = demangle(func);
+ data.line = line;
+ }
+ }
+
+ private:
+ bfd *abfd_;
+ asection *sec_;
+ asymbol **symbol_table_;
+ };
+
+#endif // __MINGW32__
+
+ // g++ spouts warnings if you use {0} to initialize PODs. So we use this instead:
+ const struct
+ {
+ template<typename POD>
+ operator POD () const { POD p; std::memset(&p, 0, sizeof p); return p; }
+ }
+ empty_pod = { };
+
+ // Wraps a FARPROC. Implicitly convertible to any kind of pointer-to-function.
+ // Avoids having reinterpret casts all over the place.
+ struct auto_cast_function_ptr
+ {
+ auto_cast_function_ptr(FARPROC f) : fptr_(f) { }
+
+ template<typename FuncPtr>
+ operator FuncPtr() const { return reinterpret_cast<FuncPtr>(fptr_); }
+
+ FARPROC fptr_;
+ };
+
+ // A wrapper around a DLL. Can dynamically get function pointers with the function() function!
+ class windows_dll : uncopyable
+ {
+ public:
+ explicit windows_dll(const std::string &libname) :
+ name_(libname),
+ lib_(LoadLibraryA(name_.c_str()))
+ {
+ if (!lib_) throw std::runtime_error("Failed to load dll " + name_);
+ }
+
+ ~windows_dll() { FreeLibrary(lib_); }
+
+ const std::string &name() const { return name_; }
+
+ auto_cast_function_ptr function(const char *func_name) const
+ {
+ FARPROC proc = GetProcAddress(lib_, func_name);
+ if (!proc) throw std::runtime_error(std::string("failed to load function ") + func_name + " from library " + name_);
+
+ return proc;
+ }
+
+ private:
+ std::string name_;
+ HMODULE lib_;
+ };
+
+ // An object that makes sure debugging symbols are available
+ class symbol_context : uncopyable
+ {
+ public:
+ symbol_context()
+ {
+ if (!SymInitialize(GetCurrentProcess(), 0, TRUE))
+ throw std::runtime_error("Failed to initialize symbol context");
+ }
+ ~symbol_context() { SymCleanup(GetCurrentProcess()); }
+ };
+
+ // A simple Windows mutex class. Use a lock object to lock the mutex for the duration of a scope.
+ class mutex : uncopyable
+ {
+ public:
+ mutex() { InitializeCriticalSection(&cs_); }
+ ~mutex() { DeleteCriticalSection(&cs_); }
+
+ private:
+ friend class lock;
+ void lock() { EnterCriticalSection(&cs_); }
+ void unlock() { LeaveCriticalSection(&cs_); }
+
+ CRITICAL_SECTION cs_;
+ }
+ g_fill_frames_mtx;
+
+ // A lock for the mutex
+ class lock : uncopyable
+ {
+ public:
+ lock(mutex &m) : m_(m) { m.lock(); }
+ ~lock() { m_.unlock(); }
+ private:
+ mutex &m_;
+ };
+
+
+ void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
+ {
+ lock lk(g_fill_frames_mtx);
+
+ symbol_context sc;
+#ifdef __MINGW32__
+ bfd_context bfdc;
+#endif
+
+ STACKFRAME frame = empty_pod;
+ CONTEXT context = empty_pod;
+ context.ContextFlags = CONTEXT_FULL;
+
+ windows_dll kernel32("kernel32.dll");
+ void (WINAPI *RtlCaptureContext_)(CONTEXT*) = kernel32.function("RtlCaptureContext");
+
+ RtlCaptureContext_(&context);
+
+#if defined(_M_AMD64)
+ frame.AddrPC.Offset = context.Rip;
+ frame.AddrPC.Mode = AddrModeFlat;
+ frame.AddrStack.Offset = context.Rsp;
+ frame.AddrStack.Mode = AddrModeFlat;
+ frame.AddrFrame.Offset = context.Rbp;
+ frame.AddrFrame.Mode = AddrModeFlat;
+#else
+ frame.AddrPC.Offset = context.Eip;
+ frame.AddrPC.Mode = AddrModeFlat;
+ frame.AddrStack.Offset = context.Esp;
+ frame.AddrStack.Mode = AddrModeFlat;
+ frame.AddrFrame.Offset = context.Ebp;
+ frame.AddrFrame.Mode = AddrModeFlat;
+#endif
+
+ HANDLE process = GetCurrentProcess();
+ HANDLE thread = GetCurrentThread();
+
+ bool skip = true;
+ bool has_limit = limit != 0;
+ char symbol_buffer[sizeof(IMAGEHLP_SYMBOL) + 255];
+ char module_name_raw[MAX_PATH];
+
+#if defined(_M_AMD64)
+ const DWORD machine = IMAGE_FILE_MACHINE_AMD64;
+#else
+ const DWORD machine = IMAGE_FILE_MACHINE_I386;
+#endif
+
+ while(StackWalk(machine, process, thread, &frame, &context, 0, SymFunctionTableAccess, SymGetModuleBase, 0))
+ {
+ if (skip)
+ {
+ skip = false;
+ continue;
+ }
+
+ if (has_limit && limit-- == 0) break;
+
+ IMAGEHLP_SYMBOL *symbol = reinterpret_cast<IMAGEHLP_SYMBOL *>(symbol_buffer);
+ symbol->SizeOfStruct = (sizeof *symbol) + 255;
+ symbol->MaxNameLength = 254;
+
+#if defined(_WIN64)
+ DWORD64 module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
+#else
+ DWORD module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
+#endif
+ std::string module_name = unknown_module;
+ if (module_base && GetModuleFileNameA(reinterpret_cast<HINSTANCE>(module_base), module_name_raw, MAX_PATH))
+ module_name = module_name_raw;
+
+#if defined(__MINGW32__)
+ std::pair<std::string, unsigned int> func_and_line = bfdc.get_function_name_and_line(frame.AddrPC.Offset);
+
+ if (func_and_line.first.empty())
+ {
+#if defined(_WIN64)
+ DWORD64 dummy = 0;
+#else
+ DWORD dummy = 0;
+#endif
+ BOOL got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
+ func_and_line.first = got_symbol ? symbol->Name : unknown_function;
+ }
+#else
+ DWORD dummy = 0;
+ BOOL got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
+ std::string func = got_symbol ? symbol->Name : unknown_function;
+#endif
+
+ dbg::stack_frame f(reinterpret_cast<const void *>(frame.AddrPC.Offset), func_and_line.first, func_and_line.second, module_name);
+ frames.push_back(f);
+ }
+ }
+#elif defined(__GNUC__)
+# if defined(__i386__) || defined(__amd64__)
+
+ void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
+ {
+ // Based on code found at:
+ // http://www.tlug.org.za/wiki/index.php/Obtaining_a_stack_trace_in_C_upon_SIGSEGV
+
+ Dl_info info;
+ void **frame = static_cast<void **>(__builtin_frame_address(0));
+ void **bp = static_cast<void **>(*frame);
+ void *ip = frame[1];
+
+ bool has_limit = limit != 0;
+ bool skip = true;
+
+ while(bp && ip && dladdr(ip, &info))
+ {
+ if (skip)
+ skip = false;
+ else
+ {
+ if (has_limit && limit-- == 0) break;
+ frames.push_back(dbg::stack_frame(ip, demangle(info.dli_sname), info.dli_fname));
+
+ if(info.dli_sname && !std::strcmp(info.dli_sname, "main")) break;
+ }
+
+ ip = bp[1];
+ bp = static_cast<void**>(bp[0]);
+ }
+ }
+
+# elif defined(__ppc__)
+
+ void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
+ {
+ // Based on code found at:
+ // http://www.informit.com/articles/article.aspx?p=606582&seqNum=4&rl=1
+
+ void *ip = __builtin_return_address(0);
+ void **frame = static_cast<void **>(__builtin_frame_address(1));
+ bool has_limit = limit != 0;
+ Dl_info info;
+
+ do
+ {
+ if (has_limit && limit-- == 0) break;
+
+ if (dladdr(ip, &info))
+ frames.push_back(dbg::stack_frame(ip, demangle(info.dli_sname), info.dli_fname));
+
+ if (frame && (frame = static_cast<void**>(*frame))) ip = *(frame + 2);
+ }
+ while (frame && ip);
+ }
+
+# else
+ // GNU, but not x86, x64 nor PPC
+# error "Sorry but dbg::stack is not supported on this architecture"
+# endif
+#else
+ // Unsupported compiler
+# error "Sorry but dbg::stack is not supported on this compiler"
+#endif
+
+} // close anonymous namespace
+
+
+
+namespace dbg
+{
+ stack_frame::stack_frame(const void *instruction, const std::string &function, unsigned int line, const std::string &module) :
+ instruction(instruction),
+ function(function),
+ line(line),
+ module(module)
+ {
+ }
+
+ std::ostream &operator<< (std::ostream &out, const stack_frame &frame)
+ {
+ return out << frame.instruction << ": " << frame.function << ":" << frame.line << " in " << frame.module;
+ }
+
+ stack::stack(depth_type limit)
+ {
+ fill_frames(frames_, limit);
+ }
+
+ stack::const_iterator stack::begin() const
+ {
+ return frames_.begin();
+ }
+
+ stack::const_iterator stack::end() const
+ {
+ return frames_.end();
+ }
+
+ stack::depth_type stack::depth() const
+ {
+ return frames_.size();
+ }
+
+} // close namespace dbg
+
diff --git a/src/lib/stack.hpp b/src/lib/stack.hpp
new file mode 100644
index 000000000..73a13bf85
--- /dev/null
+++ b/src/lib/stack.hpp
@@ -0,0 +1,58 @@
+// Copyright 2007 Edd Dawson.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef STACK_HPP_0022_01092007
+#define STACK_HPP_0022_01092007
+
+#include <string>
+#include <list>
+#include <iosfwd>
+
+namespace dbg
+{
+ //! stack_frame objects are collected by a stack object. They contain information about the instruction pointer,
+ //! the name of the corresponding function and the "module" (executable or library) in which the function resides.
+ struct stack_frame
+ {
+ stack_frame(const void *instruction, const std::string &function, unsigned int line, const std::string &module);
+
+ const void *instruction;
+ std::string function;
+ unsigned int line;
+ std::string module;
+ };
+
+ //! Allows you to write a stack_frame object to an std::ostream
+ std::ostream &operator<< (std::ostream &out, const stack_frame &frame);
+
+ //! Instantiate a dbg::stack object to collect information about the current call stack. Once created, a stack object
+ //! may be freely copied about and will continue to contain the information about the scope in which collection occurred.
+ class stack
+ {
+ public:
+ typedef std::list<stack_frame>::size_type depth_type;
+ typedef std::list<stack_frame>::const_iterator const_iterator;
+
+ //! Collect information about the current call stack. Information on the most recent frames will be collected
+ //! up to the specified limit. 0 means unlimited.
+ //! An std::runtime_error may be thrown on failure.
+ stack(depth_type limit = 0);
+
+ //! Returns an iterator referring to the "top" stack frame
+ const_iterator begin() const;
+
+ //! Returns an iterator referring to one past the "bottom" stack frame
+ const_iterator end() const;
+
+ //! Returns the number of frames collected
+ depth_type depth() const;
+
+ private:
+ std::list<stack_frame> frames_;
+ };
+
+} // close namespace dbg
+
+#endif // STACK_HPP_0022_01092007
diff --git a/src/lib/still_image.h b/src/lib/still_image.h
new file mode 100644
index 000000000..366d69331
--- /dev/null
+++ b/src/lib/still_image.h
@@ -0,0 +1,40 @@
+/*
+ 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 DCPOMATIC_STILL_IMAGE_H
+#define DCPOMATIC_STILL_IMAGE_H
+
+class StillImageContent;
+
+class StillImage
+{
+public:
+ StillImage (boost::shared_ptr<const StillImageContent> c)
+ : _still_image_content (c)
+ {}
+
+ boost::shared_ptr<const StillImageContent> content () const {
+ return _still_image_content;
+ }
+
+protected:
+ boost::shared_ptr<const StillImageContent> _still_image_content;
+};
+
+#endif
diff --git a/src/lib/still_image_content.cc b/src/lib/still_image_content.cc
new file mode 100644
index 000000000..0cf80b546
--- /dev/null
+++ b/src/lib/still_image_content.cc
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "still_image_content.h"
+#include "still_image_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+
+StillImageContent::StillImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , VideoContent (f, p)
+{
+
+}
+
+StillImageContent::StillImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , VideoContent (f, node)
+{
+
+}
+
+string
+StillImageContent::summary () const
+{
+ /* Get the string() here so that the name does not have quotes around it */
+ return String::compose (_("%1 [still]"), path().filename().string());
+}
+
+string
+StillImageContent::technical_summary () const
+{
+ return Content::technical_summary() + " - "
+ + VideoContent::technical_summary() + " - "
+ + "still";
+}
+
+void
+StillImageContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("StillImage");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+}
+
+void
+StillImageContent::examine (shared_ptr<Job> job)
+{
+ Content::examine (job);
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ shared_ptr<StillImageExaminer> examiner (new StillImageExaminer (film, shared_from_this()));
+
+ take_from_video_examiner (examiner);
+ set_video_length (Config::instance()->default_still_length() * video_frame_rate());
+}
+
+void
+StillImageContent::set_video_length (VideoContent::Frame len)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _video_length = len;
+ }
+
+ signal_changed (ContentProperty::LENGTH);
+}
+
+Time
+StillImageContent::full_length () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+ return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
+}
+
+string
+StillImageContent::identifier () const
+{
+ stringstream s;
+ s << VideoContent::identifier ();
+ s << "_" << video_length();
+ return s.str ();
+}
diff --git a/src/lib/still_image_content.h b/src/lib/still_image_content.h
new file mode 100644
index 000000000..ccd7fbc03
--- /dev/null
+++ b/src/lib/still_image_content.h
@@ -0,0 +1,52 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_STILL_IMAGE_CONTENT_H
+#define DCPOMATIC_STILL_IMAGE_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+ class Node;
+}
+
+/** A single image which is to be held on screen for some time (i.e. a slide) */
+class StillImageContent : public VideoContent
+{
+public:
+ StillImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ StillImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<StillImageContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<StillImageContent> (Content::shared_from_this ());
+ };
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ void as_xml (xmlpp::Node *) const;
+ Time full_length () const;
+
+ std::string identifier () const;
+
+ void set_video_length (VideoContent::Frame);
+};
+
+#endif
diff --git a/src/lib/still_image_decoder.cc b/src/lib/still_image_decoder.cc
new file mode 100644
index 000000000..6e82f9a55
--- /dev/null
+++ b/src/lib/still_image_decoder.cc
@@ -0,0 +1,89 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/filesystem.hpp>
+#include <Magick++.h>
+#include "still_image_content.h"
+#include "still_image_decoder.h"
+#include "image.h"
+#include "film.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
+
+StillImageDecoder::StillImageDecoder (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
+ : Decoder (f)
+ , VideoDecoder (f, c)
+ , StillImage (c)
+{
+
+}
+
+void
+StillImageDecoder::pass ()
+{
+ if (_video_position >= _still_image_content->video_length ()) {
+ return;
+ }
+
+ if (_image) {
+ video (_image, true, _video_position);
+ return;
+ }
+
+ Magick::Image* magick_image = new Magick::Image (_still_image_content->path().string ());
+ _video_size = libdcp::Size (magick_image->columns(), magick_image->rows());
+
+ _image.reset (new Image (PIX_FMT_RGB24, _video_size.get(), true));
+
+ using namespace MagickCore;
+
+ uint8_t* p = _image->data()[0];
+ for (int y = 0; y < _video_size->height; ++y) {
+ uint8_t* q = p;
+ for (int x = 0; x < _video_size->width; ++x) {
+ Magick::Color c = magick_image->pixelColor (x, y);
+ *q++ = c.redQuantum() * 255 / QuantumRange;
+ *q++ = c.greenQuantum() * 255 / QuantumRange;
+ *q++ = c.blueQuantum() * 255 / QuantumRange;
+ }
+ p += _image->stride()[0];
+ }
+
+ delete magick_image;
+
+ video (_image, false, _video_position);
+}
+
+void
+StillImageDecoder::seek (VideoContent::Frame frame, bool)
+{
+ _video_position = frame;
+}
+
+bool
+StillImageDecoder::done () const
+{
+ return _video_position >= _still_image_content->video_length ();
+}
diff --git a/src/lib/still_image_decoder.h b/src/lib/still_image_decoder.h
new file mode 100644
index 000000000..db41b0357
--- /dev/null
+++ b/src/lib/still_image_decoder.h
@@ -0,0 +1,44 @@
+/*
+ Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "video_decoder.h"
+#include "still_image.h"
+
+namespace Magick {
+ class Image;
+}
+
+class StillImageContent;
+
+class StillImageDecoder : public VideoDecoder, public StillImage
+{
+public:
+ StillImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>);
+
+ /* Decoder */
+
+ void pass ();
+ void seek (VideoContent::Frame, bool);
+ bool done () const;
+
+private:
+ boost::shared_ptr<Image> _image;
+ mutable boost::optional<libdcp::Size> _video_size;
+};
+
diff --git a/src/lib/still_image_examiner.cc b/src/lib/still_image_examiner.cc
new file mode 100644
index 000000000..5f45d6365
--- /dev/null
+++ b/src/lib/still_image_examiner.cc
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <Magick++.h>
+#include "still_image_content.h"
+#include "still_image_examiner.h"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+StillImageExaminer::StillImageExaminer (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
+ : StillImage (c)
+ , _film (f)
+{
+ using namespace MagickCore;
+ Magick::Image* image = new Magick::Image (_still_image_content->path().string());
+ _video_size = libdcp::Size (image->columns(), image->rows());
+ delete image;
+}
+
+libdcp::Size
+StillImageExaminer::video_size () const
+{
+ return _video_size;
+}
+
+int
+StillImageExaminer::video_length () const
+{
+ return _still_image_content->video_length ();
+}
+
+float
+StillImageExaminer::video_frame_rate () const
+{
+ boost::shared_ptr<const Film> f = _film.lock ();
+ if (!f) {
+ return 24;
+ }
+
+ return f->video_frame_rate ();
+}
+
diff --git a/src/lib/check_hashes_job.h b/src/lib/still_image_examiner.h
index c41af9d3f..fa3456e8a 100644
--- a/src/lib/check_hashes_job.h
+++ b/src/lib/still_image_examiner.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,27 +17,25 @@
*/
-#include "job.h"
+#include "still_image.h"
+#include "video_examiner.h"
-class DecodeOptions;
-class EncodeOptions;
+namespace Magick {
+ class Image;
+}
-class CheckHashesJob : public Job
+class StillImageContent;
+
+class StillImageExaminer : public StillImage, public VideoExaminer
{
public:
- CheckHashesJob (
- boost::shared_ptr<Film> f,
- boost::shared_ptr<const DecodeOptions> od,
- boost::shared_ptr<const EncodeOptions> oe,
- boost::shared_ptr<Job> req
- );
+ StillImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>);
- std::string name () const;
- void run ();
- std::string status () const;
+ float video_frame_rate () const;
+ libdcp::Size video_size () const;
+ VideoContent::Frame video_length () const;
private:
- boost::shared_ptr<const DecodeOptions> _decode_opt;
- boost::shared_ptr<const EncodeOptions> _encode_opt;
- int _bad;
+ boost::weak_ptr<const Film> _film;
+ libdcp::Size _video_size;
};
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
deleted file mode 100644
index 4f12f41b9..000000000
--- a/src/lib/stream.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <sstream>
-#include "compose.hpp"
-#include "stream.h"
-#include "ffmpeg_decoder.h"
-#include "external_audio_decoder.h"
-
-using std::string;
-using std::stringstream;
-using boost::shared_ptr;
-using boost::optional;
-
-/** Construct a SubtitleStream from a value returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- */
-SubtitleStream::SubtitleStream (string t, boost::optional<int>)
-{
- stringstream n (t);
- n >> _id;
-
- size_t const s = t.find (' ');
- if (s != string::npos) {
- _name = t.substr (s + 1);
- }
-}
-
-/** @return A canonical string representation of this stream */
-string
-SubtitleStream::to_string () const
-{
- return String::compose ("%1 %2", _id, _name);
-}
-
-/** Create a SubtitleStream from a value returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- */
-shared_ptr<SubtitleStream>
-SubtitleStream::create (string t, optional<int> v)
-{
- return shared_ptr<SubtitleStream> (new SubtitleStream (t, v));
-}
-
-/** Create an AudioStream from a string returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- * @return AudioStream, or 0.
- */
-shared_ptr<AudioStream>
-audio_stream_factory (string t, optional<int> v)
-{
- shared_ptr<AudioStream> s;
-
- s = FFmpegAudioStream::create (t, v);
- if (!s) {
- s = ExternalAudioStream::create (t, v);
- }
-
- return s;
-}
-
-/** Create a SubtitleStream from a string returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- * @return SubtitleStream, or 0.
- */
-shared_ptr<SubtitleStream>
-subtitle_stream_factory (string t, optional<int> v)
-{
- return SubtitleStream::create (t, v);
-}
diff --git a/src/lib/stream.h b/src/lib/stream.h
deleted file mode 100644
index 16b06e4bc..000000000
--- a/src/lib/stream.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/lib/stream.h
- * @brief Representations of audio and subtitle streams.
- *
- * Some content may have multiple `streams' of audio and/or subtitles; perhaps
- * for multiple languages, or for stereo / surround mixes. These classes represent
- * those streams, and know about their details.
- */
-
-#ifndef DVDOMATIC_STREAM_H
-#define DVDOMATIC_STREAM_H
-
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/optional.hpp>
-extern "C" {
-#include <libavutil/audioconvert.h>
-}
-
-/** @class Stream
- * @brief Parent class for streams.
- */
-class Stream
-{
-public:
- virtual ~Stream () {}
- virtual std::string to_string () const = 0;
-};
-
-/** @class AudioStream
- * @brief A stream of audio data.
- */
-struct AudioStream : public Stream
-{
-public:
- AudioStream (int r, int64_t l)
- : _sample_rate (r)
- , _channel_layout (l)
- {}
-
- /* Only used for backwards compatibility for state file version < 1 */
- void set_sample_rate (int s) {
- _sample_rate = s;
- }
-
- int channels () const {
- return av_get_channel_layout_nb_channels (_channel_layout);
- }
-
- int sample_rate () const {
- return _sample_rate;
- }
-
- int64_t channel_layout () const {
- return _channel_layout;
- }
-
-protected:
- AudioStream ()
- : _sample_rate (0)
- , _channel_layout (0)
- {}
-
- int _sample_rate;
- int64_t _channel_layout;
-};
-
-/** @class SubtitleStream
- * @brief A stream of subtitle data.
- */
-class SubtitleStream : public Stream
-{
-public:
- SubtitleStream (std::string n, int i)
- : _name (n)
- , _id (i)
- {}
-
- std::string to_string () const;
-
- std::string name () const {
- return _name;
- }
-
- int id () const {
- return _id;
- }
-
- static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v);
-
-private:
- friend class stream_test;
-
- SubtitleStream (std::string t, boost::optional<int> v);
-
- std::string _name;
- int _id;
-};
-
-boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version);
-boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version);
-
-#endif
diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc
deleted file mode 100644
index c52d3ac66..000000000
--- a/src/lib/subtitle.cc
+++ /dev/null
@@ -1,147 +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/subtitle.cc
- * @brief Representations of subtitles.
- */
-
-#include "subtitle.h"
-#include "image.h"
-#include "exceptions.h"
-
-using namespace std;
-using namespace boost;
-
-/** Construct a TimedSubtitle. This is a subtitle image, position,
- * and a range of time over which it should be shown.
- * @param sub AVSubtitle to read.
- */
-TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
-{
- assert (sub.rects > 0);
-
- /* Subtitle PTS in seconds (within the source, not taking into account any of the
- source that we may have chopped off for the DCP)
- */
- double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
-
- /* hence start time for this sub */
- _from = packet_time + (double (sub.start_display_time) / 1e3);
- _to = packet_time + (double (sub.end_display_time) / 1e3);
-
- if (sub.num_rects > 1) {
- throw DecodeError ("multi-part subtitles not yet supported");
- }
-
- AVSubtitleRect const * rect = sub.rects[0];
-
- if (rect->type != SUBTITLE_BITMAP) {
- throw DecodeError ("non-bitmap subtitles not yet supported");
- }
-
- shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGBA, Size (rect->w, rect->h), true));
-
- /* Start of the first line in the subtitle */
- uint8_t* sub_p = rect->pict.data[0];
- /* sub_p looks up into a RGB palette which is here */
- uint32_t const * palette = (uint32_t *) rect->pict.data[1];
- /* Start of the output data */
- uint32_t* out_p = (uint32_t *) image->data()[0];
-
- for (int y = 0; y < rect->h; ++y) {
- uint8_t* sub_line_p = sub_p;
- uint32_t* out_line_p = out_p;
- for (int x = 0; x < rect->w; ++x) {
- *out_line_p++ = palette[*sub_line_p++];
- }
- sub_p += rect->pict.linesize[0];
- out_p += image->stride()[0] / sizeof (uint32_t);
- }
-
- _subtitle.reset (new Subtitle (Position (rect->x, rect->y), image));
-}
-
-/** @param t Time in seconds from the start of the source */
-bool
-TimedSubtitle::displayed_at (double t) const
-{
- return t >= _from && t <= _to;
-}
-
-/** Construct a subtitle, which is an image and a position.
- * @param p Position within the (uncropped) source frame.
- * @param i Image of the subtitle (should be RGBA).
- */
-Subtitle::Subtitle (Position p, shared_ptr<Image> i)
- : _position (p)
- , _image (i)
-{
-
-}
-
-/** Given the area of a subtitle, work out the area it should
- * take up when its video frame is scaled up, and it is optionally
- * itself scaled and offset.
- * @param target_x_scale the x scaling of the video frame that the subtitle is in.
- * @param target_y_scale the y scaling of the video frame that the subtitle is in.
- * @param sub_area The area of the subtitle within the original source.
- * @param subtitle_offset y offset to apply to the subtitle position (+ve is down)
- * in the coordinate space of the source.
- * @param subtitle_scale scaling factor to apply to the subtitle image.
- */
-Rect
-subtitle_transformed_area (
- float target_x_scale, float target_y_scale,
- Rect sub_area, int subtitle_offset, float subtitle_scale
- )
-{
- Rect tx;
-
- sub_area.y += subtitle_offset;
-
- /* We will scale the subtitle by the same amount as the video frame, and also by the additional
- subtitle_scale
- */
- tx.width = sub_area.width * target_x_scale * subtitle_scale;
- tx.height = sub_area.height * target_y_scale * subtitle_scale;
-
- /* Then we need a corrective translation, consisting of two parts:
- *
- * 1. that which is the result of the scaling of the subtitle by target_x_scale and target_y_scale; this will be
- * sub_area.x * target_x_scale and sub_area.y * target_y_scale.
- *
- * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
- * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
- * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
- *
- * Combining these two translations gives these expressions.
- */
-
- tx.x = target_x_scale * (sub_area.x + (sub_area.width * (1 - subtitle_scale) / 2));
- tx.y = target_y_scale * (sub_area.y + (sub_area.height * (1 - subtitle_scale) / 2));
-
- return tx;
-}
-
-/** @return area that this subtitle takes up, in the original uncropped source's coordinate space */
-Rect
-Subtitle::area () const
-{
- return Rect (_position.x, _position.y, _image->size().width, _image->size().height);
-}
diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h
deleted file mode 100644
index 38ba4e70e..000000000
--- a/src/lib/subtitle.h
+++ /dev/null
@@ -1,81 +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/subtitle.h
- * @brief Representations of subtitles.
- */
-
-#include <list>
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-
-struct AVSubtitle;
-class Image;
-
-/** A subtitle, consisting of an image and a position */
-class Subtitle
-{
-public:
- Subtitle (Position p, boost::shared_ptr<Image> i);
-
- void set_position (Position p) {
- _position = p;
- }
-
- Position position () const {
- return _position;
- }
-
- boost::shared_ptr<Image> image () const {
- return _image;
- }
-
- Rect area () const;
-
-private:
- Position _position;
- boost::shared_ptr<Image> _image;
-};
-
-Rect
-subtitle_transformed_area (
- float target_x_scale, float target_y_scale,
- Rect sub_area, int subtitle_offset, float subtitle_scale
- );
-
-/** A Subtitle class with details of the time over which it should be shown */
-class TimedSubtitle
-{
-public:
- TimedSubtitle (AVSubtitle const &);
-
- bool displayed_at (double t) const;
-
- boost::shared_ptr<Subtitle> subtitle () const {
- return _subtitle;
- }
-
-private:
- /** the subtitle */
- boost::shared_ptr<Subtitle> _subtitle;
- /** display from time in seconds from the start of the film */
- double _from;
- /** display to time in seconds from the start of the film */
- double _to;
-};
diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc
new file mode 100644
index 000000000..9fefbbfcd
--- /dev/null
+++ b/src/lib/subtitle_content.cc
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "subtitle_content.h"
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const SubtitleContentProperty::SUBTITLE_OFFSET = 500;
+int const SubtitleContentProperty::SUBTITLE_SCALE = 501;
+
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , _subtitle_offset (0)
+ , _subtitle_scale (1)
+{
+
+}
+
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , _subtitle_offset (0)
+ , _subtitle_scale (1)
+{
+ _subtitle_offset = node->number_child<float> ("SubtitleOffset");
+ _subtitle_scale = node->number_child<float> ("SubtitleScale");
+}
+
+void
+SubtitleContent::as_xml (xmlpp::Node* root) const
+{
+ root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset));
+ root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
+}
+
+void
+SubtitleContent::set_subtitle_offset (double o)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _subtitle_offset = o;
+ }
+ signal_changed (SubtitleContentProperty::SUBTITLE_OFFSET);
+}
+
+void
+SubtitleContent::set_subtitle_scale (double s)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _subtitle_scale = s;
+ }
+ signal_changed (SubtitleContentProperty::SUBTITLE_SCALE);
+}
diff --git a/src/lib/subtitle_content.h b/src/lib/subtitle_content.h
new file mode 100644
index 000000000..c29485fee
--- /dev/null
+++ b/src/lib/subtitle_content.h
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBTITLE_CONTENT_H
+#define DCPOMATIC_SUBTITLE_CONTENT_H
+
+#include "content.h"
+
+class SubtitleContentProperty
+{
+public:
+ static int const SUBTITLE_OFFSET;
+ static int const SUBTITLE_SCALE;
+};
+
+class SubtitleContent : public virtual Content
+{
+public:
+ SubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ SubtitleContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ void set_subtitle_offset (double);
+ void set_subtitle_scale (double);
+
+ double subtitle_offset () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _subtitle_offset;
+ }
+
+ double subtitle_scale () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _subtitle_scale;
+ }
+
+private:
+ friend class ffmpeg_pts_offset_test;
+
+ /** y offset for placing subtitles, as a proportion of the container height;
+ +ve is further down the frame, -ve is further up.
+ */
+ double _subtitle_offset;
+ /** scale factor to apply to subtitles */
+ double _subtitle_scale;
+};
+
+#endif
diff --git a/src/lib/gain.cc b/src/lib/subtitle_decoder.cc
index cec3b3c62..c06f3d718 100644
--- a/src/lib/gain.cc
+++ b/src/lib/subtitle_decoder.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,29 +17,23 @@
*/
-#include "gain.h"
+#include <boost/shared_ptr.hpp>
+#include "subtitle_decoder.h"
using boost::shared_ptr;
-/** @param gain gain in dB */
-Gain::Gain (Log* log, float gain)
- : AudioProcessor (log)
- , _gain (gain)
+SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
+ : Decoder (f)
{
}
+
+/** Called by subclasses when a subtitle is ready.
+ * Image may be 0 to say that there is no current subtitle.
+ */
void
-Gain::process_audio (shared_ptr<AudioBuffers> b)
+SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
{
- 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);
+ Subtitle (image, rect, from, to);
}
diff --git a/src/lib/audio_source.h b/src/lib/subtitle_decoder.h
index 5a1510d3c..eeeadbd3f 100644
--- a/src/lib/audio_source.h
+++ b/src/lib/subtitle_decoder.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,26 +17,22 @@
*/
-/** @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>
+#include "decoder.h"
+#include "rect.h"
+#include "types.h"
-class AudioBuffers;
-class AudioSink;
+class Film;
+class TimedSubtitle;
+class Image;
-/** A class that emits audio data */
-class AudioSource
+class SubtitleDecoder : public virtual Decoder
{
public:
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>)> Audio;
+ SubtitleDecoder (boost::shared_ptr<const Film>);
- void connect_audio (boost::shared_ptr<AudioSink>);
-};
+ boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
-#endif
+protected:
+ void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+};
diff --git a/src/lib/timer.cc b/src/lib/timer.cc
index a45e80dcb..69a7e3aa9 100644
--- a/src/lib/timer.cc
+++ b/src/lib/timer.cc
@@ -26,6 +26,8 @@
#include "timer.h"
#include "util.h"
+#include "i18n.h"
+
using namespace std;
/** @param n Name to use when giving output */
@@ -40,7 +42,7 @@ PeriodTimer::~PeriodTimer ()
{
struct timeval stop;
gettimeofday (&stop, 0);
- cout << "T: " << _name << ": " << (seconds (stop) - seconds (_start)) << "\n";
+ cout << N_("T: ") << _name << N_(": ") << (seconds (stop) - seconds (_start)) << N_("\n");
}
/** @param n Name to use when giving output.
@@ -80,10 +82,10 @@ StateTimer::~StateTimer ()
}
- set_state ("");
+ set_state (N_(""));
- cout << _name << ":\n";
+ cout << _name << N_(":\n");
for (map<string, double>::iterator i = _totals.begin(); i != _totals.end(); ++i) {
- cout << "\t" << i->first << " " << i->second << "\n";
+ cout << N_("\t") << i->first << " " << i->second << N_("\n");
}
}
diff --git a/src/lib/timer.h b/src/lib/timer.h
index f509a7492..4a5aa12de 100644
--- a/src/lib/timer.h
+++ b/src/lib/timer.h
@@ -22,8 +22,8 @@
* @brief Some timing classes for debugging and profiling.
*/
-#ifndef DVDOMATIC_TIMER_H
-#define DVDOMATIC_TIMER_H
+#ifndef DCPOMATIC_TIMER_H
+#define DCPOMATIC_TIMER_H
#include <string>
#include <map>
@@ -53,7 +53,7 @@ private:
* spends in one of a set of states.
*
* Once constructed, the caller can call set_state() whenever
- * its state changes. When StateTimer is destroyed, it will
+ * its state changes. When StateTimer is destroyed, it will
* output (to cout) a summary of the time spent in each state.
*/
class StateTimer
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index dfb9b1071..c9ec2053d 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -25,10 +25,10 @@
#include <iomanip>
#include "transcode_job.h"
#include "film.h"
-#include "format.h"
#include "transcoder.h"
#include "log.h"
-#include "encoder.h"
+
+#include "i18n.h"
using std::string;
using std::stringstream;
@@ -37,13 +37,9 @@ using std::setprecision;
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 DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
- : Job (f, req)
- , _decode_opt (od)
- , _encode_opt (oe)
+TranscodeJob::TranscodeJob (shared_ptr<const Film> f)
+ : Job (f)
{
}
@@ -51,7 +47,7 @@ TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions>
string
TranscodeJob::name () const
{
- return String::compose ("Transcode %1", _film->name());
+ return String::compose (_("Transcode %1"), _film->name());
}
void
@@ -59,22 +55,20 @@ TranscodeJob::run ()
{
try {
- _film->log()->log ("Transcode job starting");
- _film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay()));
+ _film->log()->log (N_("Transcode job starting"));
- _encoder.reset (new Encoder (_film, _encode_opt));
- Transcoder w (_film, _decode_opt, this, _encoder);
- w.go ();
+ _transcoder.reset (new Transcoder (_film, shared_from_this ()));
+ _transcoder->go ();
set_progress (1);
set_state (FINISHED_OK);
- _film->log()->log ("Transcode job completed successfully");
+ _film->log()->log (N_("Transcode job completed successfully"));
} catch (std::exception& e) {
set_progress (1);
set_state (FINISHED_ERROR);
- _film->log()->log (String::compose ("Transcode job failed (%1)", e.what()));
+ _film->log()->log (String::compose (N_("Transcode job failed (%1)"), e.what()));
throw;
}
@@ -83,16 +77,11 @@ TranscodeJob::run ()
string
TranscodeJob::status () const
{
- if (!_encoder) {
- return "0%";
+ if (!_transcoder) {
+ return _("0%");
}
- if (_encoder->skipping () && !finished ()) {
- return "skipping already-encoded frames";
- }
-
-
- float const fps = _encoder->current_frames_per_second ();
+ float const fps = _transcoder->current_encoding_rate ();
if (fps == 0) {
return Job::status ();
}
@@ -102,7 +91,12 @@ TranscodeJob::status () const
s << Job::status ();
if (!finished ()) {
- s << "; " << fixed << setprecision (1) << fps << " frames per second";
+ if (_transcoder->state() == Encoder::TRANSCODING) {
+ s << "; " << fixed << setprecision (1) << fps << N_(" ") << _("frames per second");
+ } else {
+ /* TRANSLATORS: this means `computing a hash' as in a digest of a block of data */
+ s << "; " << _("hashing");
+ }
}
return s.str ();
@@ -111,16 +105,17 @@ TranscodeJob::status () const
int
TranscodeJob::remaining_time () const
{
- float fps = _encoder->current_frames_per_second ();
- if (fps == 0) {
+ if (!_transcoder) {
return 0;
}
+
+ float fps = _transcoder->current_encoding_rate ();
- if (!_film->dcp_length()) {
+ if (fps == 0) {
return 0;
}
- /* We assume that dcp_length() is valid, if it is set */
- SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame();
+ /* Compute approximate proposed length here, as it's only here that we need it */
+ OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
return left / fps;
}
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
index 97f655e15..9128206d2 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -24,9 +24,7 @@
#include <boost/shared_ptr.hpp>
#include "job.h"
-class Encoder;
-class DecodeOptions;
-class EncodeOptions;
+class Transcoder;
/** @class TranscodeJob
* @brief A job which transcodes from one format to another.
@@ -34,17 +32,14 @@ class EncodeOptions;
class TranscodeJob : public Job
{
public:
- TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> od, boost::shared_ptr<const EncodeOptions> oe, boost::shared_ptr<Job> req);
+ TranscodeJob (boost::shared_ptr<const Film> f);
std::string name () const;
void run ();
std::string status () const;
-protected:
+private:
int remaining_time () const;
-private:
- boost::shared_ptr<const DecodeOptions> _decode_opt;
- boost::shared_ptr<const EncodeOptions> _encode_opt;
- boost::shared_ptr<Encoder> _encoder;
+ boost::shared_ptr<Transcoder> _transcoder;
};
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 87a1fb3f2..63ba77939 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -28,101 +28,71 @@
#include <boost/signals2.hpp>
#include "transcoder.h"
#include "encoder.h"
-#include "decoder_factory.h"
#include "film.h"
-#include "matcher.h"
-#include "delay_line.h"
-#include "options.h"
-#include "gain.h"
#include "video_decoder.h"
#include "audio_decoder.h"
+#include "player.h"
+#include "job.h"
using std::string;
-using std::cout;
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+static void
+video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, Eyes eyes, ColourConversion conversion, bool same)
+{
+ shared_ptr<Encoder> e = encoder.lock ();
+ if (e) {
+ e->process_video (image, eyes, conversion, same);
+ }
+}
+
+static void
+audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
+{
+ shared_ptr<Encoder> e = encoder.lock ();
+ if (e) {
+ e->process_audio (audio);
+ }
+}
+
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
- * @param o Decode options.
* @param j Job that we are running under, or 0.
* @param e Encoder to use.
*/
-Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
: _job (j)
- , _encoder (e)
- , _decoders (decoder_factory (f, o, j))
+ , _player (f->make_player ())
+ , _encoder (new Encoder (f, 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.video->set_subtitle_stream (f->subtitle_stream ());
- if (_decoders.audio) {
- _decoders.audio->set_audio_stream (f->audio_stream ());
- }
-
- if (_matcher) {
- _decoders.video->connect_video (_matcher);
- _matcher->connect_video (_encoder);
- } else {
- _decoders.video->connect_video (_encoder);
- }
-
- if (_matcher && _delay_line && _decoders.audio) {
- _decoders.audio->connect_audio (_delay_line);
- _delay_line->connect_audio (_matcher);
- _matcher->connect_audio (_gain);
- _gain->connect_audio (_encoder);
- }
+ _player->Video.connect (bind (video_proxy, _encoder, _1, _2, _3, _4));
+ _player->Audio.connect (bind (audio_proxy, _encoder, _1));
}
-/** Run the decoder, passing its output to the encoder, until the decoder
- * has no more data to present.
- */
void
Transcoder::go ()
{
_encoder->process_begin ();
- try {
- bool done[2] = { false, false };
-
- while (1) {
- if (!done[0]) {
- done[0] = _decoders.video->pass ();
- _decoders.video->set_progress ();
- }
+ while (!_player->pass ()) {}
+ _encoder->process_end ();
+}
- if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
- done[1] = _decoders.audio->pass ();
- } else {
- done[1] = true;
- }
+float
+Transcoder::current_encoding_rate () const
+{
+ return _encoder->current_encoding_rate ();
+}
- if (done[0] && done[1]) {
- break;
- }
- }
-
- } catch (...) {
- _encoder->process_end ();
- throw;
- }
-
- if (_delay_line) {
- _delay_line->process_end ();
- }
- if (_matcher) {
- _matcher->process_end ();
- }
- if (_gain) {
- _gain->process_end ();
- }
- _encoder->process_end ();
+int
+Transcoder::video_frames_out () const
+{
+ return _encoder->video_frames_out ();
+}
+
+Encoder::State
+Transcoder::state () const
+{
+ return _encoder->state ();
}
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index b50113742..7bf214a88 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -17,58 +17,33 @@
*/
-/** @file src/transcoder.h
- * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
- *
- * A decoder is selected according to the content type, and the encoder can be specified
- * as a parameter to the constructor.
- */
-
-#include "decoder_factory.h"
+#include "types.h"
+#include "encoder.h"
class Film;
class Job;
class Encoder;
-class FilmState;
-class Matcher;
class VideoFilter;
-class Gain;
-class VideoDecoder;
-class AudioDecoder;
-class DelayLine;
-class EncodeOptions;
-class DecodeOptions;
+class Player;
-/** @class Transcoder
- * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
- *
- * A decoder is selected according to the content type, and the encoder can be specified
- * as a parameter to the constructor.
- */
-class Transcoder
+/** @class Transcoder */
+class Transcoder : public boost::noncopyable
{
public:
Transcoder (
- boost::shared_ptr<Film> f,
- boost::shared_ptr<const DecodeOptions> o,
- Job* j,
- boost::shared_ptr<Encoder> e
+ boost::shared_ptr<const Film> f,
+ boost::shared_ptr<Job> j
);
void go ();
- boost::shared_ptr<VideoDecoder> video_decoder () const {
- return _decoders.video;
- }
+ float current_encoding_rate () const;
+ Encoder::State state () const;
+ int video_frames_out () const;
-protected:
+private:
/** A Job that is running this Transcoder, or 0 */
- Job* _job;
- /** The encoder that we will use */
+ boost::shared_ptr<Job> _job;
+ boost::shared_ptr<Player> _player;
boost::shared_ptr<Encoder> _encoder;
- /** The decoders that we will use */
- Decoders _decoders;
- boost::shared_ptr<Matcher> _matcher;
- boost::shared_ptr<DelayLine> _delay_line;
- boost::shared_ptr<Gain> _gain;
};
diff --git a/src/lib/types.cc b/src/lib/types.cc
new file mode 100644
index 000000000..bc4f5f8d9
--- /dev/null
+++ b/src/lib/types.cc
@@ -0,0 +1,67 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "types.h"
+
+using std::max;
+using std::min;
+using std::string;
+
+bool operator== (Crop const & a, Crop const & b)
+{
+ return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
+}
+
+bool operator!= (Crop const & a, Crop const & b)
+{
+ return !(a == b);
+}
+
+/** @param r Resolution.
+ * @return Untranslated string representation.
+ */
+string
+resolution_to_string (Resolution r)
+{
+ switch (r) {
+ case RESOLUTION_2K:
+ return "2K";
+ case RESOLUTION_4K:
+ return "4K";
+ }
+
+ assert (false);
+ return "";
+}
+
+
+Resolution
+string_to_resolution (string s)
+{
+ if (s == "2K") {
+ return RESOLUTION_2K;
+ }
+
+ if (s == "4K") {
+ return RESOLUTION_4K;
+ }
+
+ assert (false);
+ return RESOLUTION_2K;
+}
diff --git a/src/lib/types.h b/src/lib/types.h
new file mode 100644
index 000000000..01560ba81
--- /dev/null
+++ b/src/lib/types.h
@@ -0,0 +1,103 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_TYPES_H
+#define DCPOMATIC_TYPES_H
+
+#include <vector>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <libdcp/util.h>
+
+class Content;
+class AudioBuffers;
+
+/** The version number of the protocol used to communicate
+ * with servers. Intended to be bumped when incompatibilities
+ * are introduced.
+ */
+#define SERVER_LINK_VERSION 1
+
+typedef int64_t Time;
+#define TIME_MAX INT64_MAX
+#define TIME_HZ ((Time) 96000)
+typedef int64_t OutputAudioFrame;
+typedef int OutputVideoFrame;
+typedef std::vector<boost::shared_ptr<Content> > ContentList;
+
+template<class T>
+struct TimedAudioBuffers
+{
+ TimedAudioBuffers ()
+ : time (0)
+ {}
+
+ TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
+ : audio (a)
+ , time (t)
+ {}
+
+ boost::shared_ptr<AudioBuffers> audio;
+ T time;
+};
+
+enum VideoFrameType
+{
+ VIDEO_FRAME_TYPE_2D,
+ VIDEO_FRAME_TYPE_3D_LEFT_RIGHT
+};
+
+enum Eyes
+{
+ EYES_BOTH,
+ EYES_LEFT,
+ EYES_RIGHT,
+ EYES_COUNT
+};
+
+/** @struct Crop
+ * @brief A description of the crop of an image or video.
+ */
+struct Crop
+{
+ Crop () : left (0), right (0), top (0), bottom (0) {}
+ Crop (int l, int r, int t, int b) : left (l), right (r), top (t), bottom (b) {}
+
+ /** Number of pixels to remove from the left-hand side */
+ int left;
+ /** Number of pixels to remove from the right-hand side */
+ int right;
+ /** Number of pixels to remove from the top */
+ int top;
+ /** Number of pixels to remove from the bottom */
+ int bottom;
+};
+
+extern bool operator== (Crop const & a, Crop const & b);
+extern bool operator!= (Crop const & a, Crop const & b);
+
+enum Resolution {
+ RESOLUTION_2K,
+ RESOLUTION_4K
+};
+
+std::string resolution_to_string (Resolution);
+Resolution string_to_resolution (std::string);
+
+#endif
diff --git a/src/lib/ui_signaller.h b/src/lib/ui_signaller.h
index 221bcbe95..7e0f57513 100644
--- a/src/lib/ui_signaller.h
+++ b/src/lib/ui_signaller.h
@@ -17,8 +17,8 @@
*/
-#ifndef DVDOMATIC_UI_SIGNALLER_H
-#define DVDOMATIC_UI_SIGNALLER_H
+#ifndef DCPOMATIC_UI_SIGNALLER_H
+#define DCPOMATIC_UI_SIGNALLER_H
#include <boost/bind.hpp>
#include <boost/asio.hpp>
@@ -27,7 +27,7 @@
/** A class to allow signals to be emitted from non-UI threads and handled
* by a UI thread.
*/
-class UISignaller
+class UISignaller : public boost::noncopyable
{
public:
/** Create a UISignaller. Must be called from the UI thread */
@@ -60,7 +60,10 @@ public:
}
/** This should wake the UI and make it call ui_idle() */
- virtual void wake_ui () = 0;
+ virtual void wake_ui () {
+ /* This is only a sensible implementation when there is no GUI... */
+ ui_idle ();
+ }
private:
/** A io_service which is used as the conduit for messages */
diff --git a/src/lib/util.cc b/src/lib/util.cc
index ef6f46575..b8bc1fc9e 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -26,7 +26,8 @@
#include <iomanip>
#include <iostream>
#include <fstream>
-#ifdef DVDOMATIC_POSIX
+#include <climits>
+#ifdef DCPOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
#endif
@@ -38,6 +39,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
+#include <glib.h>
#include <openjpeg.h>
#include <openssl/md5.h>
#include <magick/MagickCore.h>
@@ -55,15 +57,44 @@ extern "C" {
#include "util.h"
#include "exceptions.h"
#include "scaler.h"
-#include "format.h"
#include "dcp_content_type.h"
#include "filter.h"
#include "sound_processor.h"
+#include "config.h"
+#include "ratio.h"
+#include "job.h"
+#ifdef DCPOMATIC_WINDOWS
+#include "stack.hpp"
+#endif
-using namespace std;
-using namespace boost;
-
-thread::id ui_thread;
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::setfill;
+using std::ostream;
+using std::endl;
+using std::vector;
+using std::hex;
+using std::setw;
+using std::ifstream;
+using std::ios;
+using std::min;
+using std::max;
+using std::list;
+using std::multimap;
+using std::istream;
+using std::numeric_limits;
+using std::pair;
+using std::ofstream;
+using boost::shared_ptr;
+using boost::thread;
+using boost::lexical_cast;
+using boost::optional;
+using libdcp::Size;
+
+static boost::thread::id ui_thread;
+static boost::filesystem::path backtrace_file;
/** Convert some number of seconds to a string representation
* in hours, minutes and seconds.
@@ -81,11 +112,11 @@ seconds_to_hms (int s)
m -= (h * 60);
stringstream hms;
- hms << h << ":";
+ hms << h << N_(":");
hms.width (2);
- hms << setfill ('0') << m << ":";
+ hms << std::setfill ('0') << m << N_(":");
hms.width (2);
- hms << setfill ('0') << s;
+ hms << std::setfill ('0') << s;
return hms.str ();
}
@@ -105,40 +136,40 @@ seconds_to_approximate_hms (int s)
if (h > 0) {
if (m > 30) {
- ap << (h + 1) << " hours";
+ ap << (h + 1) << N_(" ") << _("hours");
} else {
if (h == 1) {
- ap << "1 hour";
+ ap << N_("1 ") << _("hour");
} else {
- ap << h << " hours";
+ ap << h << N_(" ") << _("hours");
}
}
} else if (m > 0) {
if (m == 1) {
- ap << "1 minute";
+ ap << N_("1 ") << _("minute");
} else {
- ap << m << " minutes";
+ ap << m << N_(" ") << _("minutes");
}
} else {
- ap << s << " seconds";
+ ap << s << N_(" ") << _("seconds");
}
return ap.str ();
}
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
/** @param l Mangled C++ identifier.
* @return Demangled version.
*/
static string
demangle (string l)
{
- string::size_type const b = l.find_first_of ("(");
+ string::size_type const b = l.find_first_of (N_("("));
if (b == string::npos) {
return l;
}
- string::size_type const p = l.find_last_of ("+");
+ string::size_type const p = l.find_last_of (N_("+"));
if (p == string::npos) {
return l;
}
@@ -172,16 +203,12 @@ void
stacktrace (ostream& out, int levels)
{
void *array[200];
- size_t size;
- char **strings;
- size_t i;
-
- size = backtrace (array, 200);
- strings = backtrace_symbols (array, size);
+ size_t size = backtrace (array, 200);
+ char** strings = backtrace_symbols (array, size);
if (strings) {
- for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
- out << " " << demangle (strings[i]) << endl;
+ for (size_t i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
+ out << N_(" ") << demangle (strings[i]) << "\n";
}
free (strings);
@@ -196,7 +223,7 @@ static string
ffmpeg_version_to_string (int v)
{
stringstream s;
- s << ((v & 0xff0000) >> 16) << "." << ((v & 0xff00) >> 8) << "." << (v & 0xff);
+ s << ((v & 0xff0000) >> 16) << N_(".") << ((v & 0xff00) >> 8) << N_(".") << (v & 0xff);
return s.str ();
}
@@ -205,16 +232,16 @@ string
dependency_version_summary ()
{
stringstream s;
- s << "libopenjpeg " << opj_version () << ", "
- << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", "
- << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", "
- << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", "
- << "libavutil " << ffmpeg_version_to_string (avutil_version()) << ", "
- << "libpostproc " << ffmpeg_version_to_string (postproc_version()) << ", "
- << "libswscale " << ffmpeg_version_to_string (swscale_version()) << ", "
- << MagickVersion << ", "
- << "libssh " << ssh_version (0) << ", "
- << "libdcp " << libdcp::version << " git " << libdcp::git_commit;
+ s << N_("libopenjpeg ") << opj_version () << N_(", ")
+ << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+ << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+ << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+ << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+ << N_("libpostproc ") << ffmpeg_version_to_string (postproc_version()) << N_(", ")
+ << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+ << MagickVersion << N_(", ")
+ << N_("libssh ") << ssh_version (0) << N_(", ")
+ << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
return s.str ();
}
@@ -225,33 +252,82 @@ seconds (struct timeval t)
return t.tv_sec + (double (t.tv_usec) / 1e6);
}
-/** Call the required functions to set up DVD-o-matic's static arrays, etc.
+#ifdef DCPOMATIC_WINDOWS
+LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
+{
+ dbg::stack s;
+ ofstream f (backtrace_file.string().c_str());
+ std::copy(s.begin(), s.end(), std::ostream_iterator<dbg::stack_frame>(f, "\n"));
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+#endif
+
+/** Call the required functions to set up DCP-o-matic's static arrays, etc.
* Must be called from the UI thread, if there is one.
*/
void
-dvdomatic_setup ()
+dcpomatic_setup ()
{
- libdcp::init ();
+#ifdef DCPOMATIC_WINDOWS
+ backtrace_file /= g_get_user_config_dir ();
+ backtrace_file /= "backtrace.txt";
+ SetUnhandledExceptionFilter(exception_handler);
+#endif
+
+ avfilter_register_all ();
- Format::setup_formats ();
+ Ratio::setup_ratios ();
DCPContentType::setup_dcp_content_types ();
Scaler::setup_scalers ();
Filter::setup_filters ();
SoundProcessor::setup_sound_processors ();
- ui_thread = this_thread::get_id ();
+ ui_thread = boost::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)
+#ifdef DCPOMATIC_WINDOWS
+boost::filesystem::path
+mo_path ()
{
- stringstream s;
- s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y;
- return s.str ();
+ wchar_t buffer[512];
+ GetModuleFileName (0, buffer, 512 * sizeof(wchar_t));
+ boost::filesystem::path p (buffer);
+ p = p.parent_path ();
+ p = p.parent_path ();
+ p /= "locale";
+ return p;
+}
+#endif
+
+void
+dcpomatic_setup_gettext_i18n (string lang)
+{
+#ifdef DCPOMATIC_POSIX
+ lang += ".UTF8";
+#endif
+
+ if (!lang.empty ()) {
+ /* Override our environment language; this is essential on
+ Windows.
+ */
+ char cmd[64];
+ snprintf (cmd, sizeof(cmd), "LANGUAGE=%s", lang.c_str ());
+ putenv (cmd);
+ snprintf (cmd, sizeof(cmd), "LANG=%s", lang.c_str ());
+ putenv (cmd);
+ }
+
+ setlocale (LC_ALL, "");
+ textdomain ("libdcpomatic");
+
+#ifdef DCPOMATIC_WINDOWS
+ bindtextdomain ("libdcpomatic", mo_path().string().c_str());
+ bind_textdomain_codeset ("libdcpomatic", "UTF8");
+#endif
+
+#ifdef DCPOMATIC_POSIX
+ bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
+#endif
}
/** @param s A string.
@@ -266,7 +342,7 @@ split_at_spaces_considering_quotes (string s)
for (string::size_type i = 0; i < s.length(); ++i) {
if (s[i] == ' ' && !in_quotes) {
out.push_back (c);
- c = "";
+ c = N_("");
} else if (s[i] == '"') {
in_quotes = !in_quotes;
} else {
@@ -289,7 +365,7 @@ md5_digest (void const * data, int size)
stringstream s;
for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
- s << hex << setfill('0') << setw(2) << ((int) digest[i]);
+ s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
}
return s.str ();
@@ -299,16 +375,16 @@ md5_digest (void const * data, int size)
* @return MD5 digest of file's contents.
*/
string
-md5_digest (string file)
+md5_digest (boost::filesystem::path file)
{
- ifstream f (file.c_str(), ios::binary);
+ ifstream f (file.string().c_str(), std::ios::binary);
if (!f.good ()) {
- throw OpenFileError (file);
+ throw OpenFileError (file.string());
}
- f.seekg (0, ios::end);
+ f.seekg (0, std::ios::end);
int bytes = f.tellg ();
- f.seekg (0, ios::beg);
+ f.seekg (0, std::ios::beg);
int const buffer_size = 64 * 1024;
char buffer[buffer_size];
@@ -327,275 +403,197 @@ md5_digest (string file)
stringstream s;
for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
- s << hex << setfill('0') << setw(2) << ((int) digest[i]);
+ s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
}
return s.str ();
}
-/** @param fps Arbitrary frames-per-second value.
- * @return DCPFrameRate for this frames-per-second.
- */
-DCPFrameRate
-dcp_frame_rate (float fps)
+/** @param job Optional job for which to report progress */
+string
+md5_digest_directory (boost::filesystem::path directory, shared_ptr<Job> job)
{
- DCPFrameRate dfr;
+ int const buffer_size = 64 * 1024;
+ char buffer[buffer_size];
- dfr.run_fast = (fps != rint (fps));
- dfr.frames_per_second = rint (fps);
- dfr.skip = 1;
+ MD5_CTX md5_context;
+ MD5_Init (&md5_context);
- /* XXX: somewhat arbitrary */
- if (fps == 50) {
- dfr.frames_per_second = 25;
- dfr.skip = 2;
+ int files = 0;
+ if (job) {
+ for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
+ ++files;
+ }
}
- return dfr;
-}
+ int j = 0;
+ for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
+ ifstream f (i->path().string().c_str(), std::ios::binary);
+ if (!f.good ()) {
+ throw OpenFileError (i->path().string());
+ }
+
+ f.seekg (0, std::ios::end);
+ int bytes = f.tellg ();
+ f.seekg (0, std::ios::beg);
+
+ while (bytes > 0) {
+ int const t = min (bytes, buffer_size);
+ f.read (buffer, t);
+ MD5_Update (&md5_context, buffer, t);
+ bytes -= t;
+ }
-/** @param An arbitrary sampling rate.
- * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz).
- */
-int
-dcp_audio_sample_rate (int fs)
-{
- if (fs <= 48000) {
- return 48000;
+ if (job) {
+ job->set_progress (float (j) / files);
+ ++j;
+ }
}
- return 96000;
-}
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5_Final (digest, &md5_context);
-int
-dcp_audio_channels (int f)
-{
- if (f == 1) {
- /* The source is mono, so to put the mono channel into
- the centre we need to generate a 5.1 soundtrack.
- */
- return 6;
+ stringstream s;
+ for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
+ s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
}
- return f;
+ return s.str ();
}
-
-bool operator== (Size const & a, Size const & b)
+static bool
+about_equal (float a, float b)
{
- return (a.width == b.width && a.height == b.height);
-}
+ /* A film of F seconds at f FPS will be Ff frames;
+ Consider some delta FPS d, so if we run the same
+ film at (f + d) FPS it will last F(f + d) seconds.
-bool operator!= (Size const & a, Size const & b)
-{
- return !(a == b);
-}
+ Hence the difference in length over the length of the film will
+ be F(f + d) - Ff frames
+ = Ff + Fd - Ff frames
+ = Fd frames
+ = Fd/f seconds
+
+ So if we accept a difference of 1 frame, ie 1/f seconds, we can
+ say that
-bool operator== (Crop const & a, Crop const & b)
-{
- return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
-}
+ 1/f = Fd/f
+ ie 1 = Fd
+ ie d = 1/F
+
+ So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+ FPS error is 1/F ~= 0.0001 ~= 10-e4
+ */
-bool operator!= (Crop const & a, Crop const & b)
-{
- return !(a == b);
+ return (fabs (a - b) < 1e-4);
}
-/** @param index Colour LUT index.
- * @return Human-readable name.
+/** @param An arbitrary audio frame rate.
+ * @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
*/
-string
-colour_lut_index_to_name (int index)
+int
+dcp_audio_frame_rate (int fs)
{
- switch (index) {
- case 0:
- return "sRGB";
- case 1:
- return "Rec 709";
+ if (fs <= 48000) {
+ return 48000;
}
- assert (false);
- return "";
+ return 96000;
}
-Socket::Socket ()
+Socket::Socket (int timeout)
: _deadline (_io_service)
, _socket (_io_service)
- , _buffer_data (0)
+ , _timeout (timeout)
{
- _deadline.expires_at (posix_time::pos_infin);
+ _deadline.expires_at (boost::posix_time::pos_infin);
check ();
}
void
Socket::check ()
{
- if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) {
+ if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
_socket.close ();
- _deadline.expires_at (posix_time::pos_infin);
+ _deadline.expires_at (boost::posix_time::pos_infin);
}
_deadline.async_wait (boost::bind (&Socket::check, this));
}
-/** Blocking connect with timeout.
+/** Blocking connect.
* @param endpoint End-point to connect to.
- * @param timeout Time-out in seconds.
*/
void
-Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint, int timeout)
+Socket::connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint)
{
- _deadline.expires_from_now (posix_time::seconds (timeout));
- system::error_code ec = asio::error::would_block;
- _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1);
+ _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ boost::system::error_code ec = boost::asio::error::would_block;
+ _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
do {
_io_service.run_one();
- } while (ec == asio::error::would_block);
+ } while (ec == boost::asio::error::would_block);
if (ec || !_socket.is_open ()) {
- throw NetworkError ("connect timed out");
+ throw NetworkError (_("connect timed out"));
}
}
-/** Blocking write with timeout.
+/** Blocking write.
* @param data Buffer to write.
* @param size Number of bytes to write.
- * @param timeout Time-out, in seconds.
*/
void
-Socket::write (uint8_t const * data, int size, int timeout)
+Socket::write (uint8_t const * data, int size)
{
- _deadline.expires_from_now (posix_time::seconds (timeout));
- system::error_code ec = asio::error::would_block;
+ _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ boost::system::error_code ec = boost::asio::error::would_block;
- asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+ boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
+
do {
_io_service.run_one ();
- } while (ec == asio::error::would_block);
-
- if (ec) {
- throw NetworkError ("write timed out");
- }
-}
-
-/** Blocking read with timeout.
- * @param data Buffer to read to.
- * @param size Number of bytes to read.
- * @param timeout Time-out, in seconds.
- */
-int
-Socket::read (uint8_t* data, int size, int timeout)
-{
- _deadline.expires_from_now (posix_time::seconds (timeout));
- system::error_code ec = asio::error::would_block;
+ } while (ec == boost::asio::error::would_block);
- int amount_read = 0;
-
- _socket.async_read_some (
- asio::buffer (data, size),
- (lambda::var(ec) = lambda::_1, lambda::var(amount_read) = lambda::_2)
- );
-
- do {
- _io_service.run_one ();
- } while (ec == asio::error::would_block);
-
if (ec) {
- amount_read = 0;
+ throw NetworkError (ec.message ());
}
-
- return amount_read;
}
-/** Mark some data as being `consumed', so that it will not be returned
- * as data again.
- * @param size Amount of data to consume, in bytes.
- */
void
-Socket::consume (int size)
+Socket::write (uint32_t v)
{
- assert (_buffer_data >= size);
-
- _buffer_data -= size;
- if (_buffer_data > 0) {
- /* Shift still-valid data to the start of the buffer */
- memmove (_buffer, _buffer + size, _buffer_data);
- }
+ v = htonl (v);
+ write (reinterpret_cast<uint8_t*> (&v), 4);
}
-/** Read a definite amount of data from our socket, and mark
- * it as consumed.
- * @param data Where to put the data.
+/** Blocking read.
+ * @param data Buffer to read to.
* @param size Number of bytes to read.
*/
void
-Socket::read_definite_and_consume (uint8_t* data, int size, int timeout)
-{
- int const from_buffer = min (_buffer_data, size);
- if (from_buffer > 0) {
- /* Get data from our buffer */
- memcpy (data, _buffer, from_buffer);
- consume (from_buffer);
- /* Update our output state */
- data += from_buffer;
- size -= from_buffer;
- }
-
- /* read() the rest */
- while (size > 0) {
- int const n = read (data, size, timeout);
- if (n <= 0) {
- throw NetworkError ("could not read");
- }
-
- data += n;
- size -= n;
- }
-}
-
-/** Read as much data as is available, up to some limit.
- * @param data Where to put the data.
- * @param size Maximum amount of data to read.
- */
-void
-Socket::read_indefinite (uint8_t* data, int size, int timeout)
+Socket::read (uint8_t* data, int size)
{
- assert (size < int (sizeof (_buffer)));
+ _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+ boost::system::error_code ec = boost::asio::error::would_block;
- /* Amount of extra data we need to read () */
- int to_read = size - _buffer_data;
- while (to_read > 0) {
- /* read as much of it as we can (into our buffer) */
- int const n = read (_buffer + _buffer_data, to_read, timeout);
- if (n <= 0) {
- throw NetworkError ("could not read");
- }
+ boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
- to_read -= n;
- _buffer_data += n;
+ do {
+ _io_service.run_one ();
+ } while (ec == boost::asio::error::would_block);
+
+ if (ec) {
+ throw NetworkError (ec.message ());
}
-
- assert (_buffer_data >= size);
-
- /* copy data into the output buffer */
- assert (size >= _buffer_data);
- memcpy (data, _buffer, size);
}
-/** @param other A Rect.
- * @return The intersection of this with `other'.
- */
-Rect
-Rect::intersection (Rect const & other) const
+uint32_t
+Socket::read_uint32 ()
{
- int const tx = max (x, other.x);
- int const ty = max (y, other.y);
-
- return Rect (
- tx, ty,
- min (x + width, other.x + other.width) - tx,
- min (y + height, other.y + other.height) - ty
- );
+ uint32_t v;
+ read (reinterpret_cast<uint8_t *> (&v), 4);
+ return ntohl (v);
}
/** Round a number up to the nearest multiple of another number.
@@ -611,12 +609,6 @@ stride_round_up (int c, int const * stride, int t)
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
@@ -658,13 +650,13 @@ string
get_required_string (multimap<string, string> const & kv, string k)
{
if (kv.count (k) > 1) {
- throw StringError ("unexpected multiple keys in key-value set");
+ throw StringError (N_("unexpected multiple keys in key-value set"));
}
multimap<string, string>::const_iterator i = kv.find (k);
if (i == kv.end ()) {
- throw StringError (String::compose ("missing key %1 in key-value set", k));
+ throw StringError (String::compose (_("missing key %1 in key-value set"), k));
}
return i->second;
@@ -688,12 +680,12 @@ string
get_optional_string (multimap<string, string> const & kv, string k)
{
if (kv.count (k) > 1) {
- throw StringError ("unexpected multiple keys in key-value set");
+ throw StringError (N_("unexpected multiple keys in key-value set"));
}
multimap<string, string>::const_iterator i = kv.find (k);
if (i == kv.end ()) {
- return "";
+ return N_("");
}
return i->second;
@@ -703,7 +695,7 @@ int
get_optional_int (multimap<string, string> const & kv, string k)
{
if (kv.count (k) > 1) {
- throw StringError ("unexpected multiple keys in key-value set");
+ throw StringError (N_("unexpected multiple keys in key-value set"));
}
multimap<string, string>::const_iterator i = kv.find (k);
@@ -714,193 +706,97 @@ 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)
- , _allocated_frames (frames)
-{
- _data = new float*[_channels];
- for (int i = 0; i < _channels; ++i) {
- _data[i] = new float[frames];
- }
-}
-
-/** Copy constructor.
- * @param other Other AudioBuffers; data is copied.
- */
-AudioBuffers::AudioBuffers (AudioBuffers const & other)
- : _channels (other._channels)
- , _frames (other._frames)
- , _allocated_frames (other._frames)
-{
- _data = new float*[_channels];
- for (int i = 0; i < _channels; ++i) {
- _data[i] = new float[_frames];
- memcpy (_data[i], other._data[i], _frames * sizeof (float));
- }
-}
-
-/** AudioBuffers destructor */
-AudioBuffers::~AudioBuffers ()
+/** Trip an assert if the caller is not in the UI thread */
+void
+ensure_ui_thread ()
{
- for (int i = 0; i < _channels; ++i) {
- delete[] _data[i];
- }
-
- delete[] _data;
+ assert (boost::this_thread::get_id() == ui_thread);
}
-/** @param c Channel index.
- * @return Buffer for this channel.
+/** @param v Content video frame.
+ * @param audio_sample_rate Source audio sample rate.
+ * @param frames_per_second Number of video frames per second.
+ * @return Equivalent number of audio frames for `v'.
*/
-float*
-AudioBuffers::data (int c) const
+int64_t
+video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
{
- assert (c >= 0 && c < _channels);
- return _data[c];
+ return ((int64_t) v * audio_sample_rate / frames_per_second);
}
-/** 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)
+string
+audio_channel_name (int c)
{
- assert (f <= _allocated_frames);
- _frames = f;
-}
+ assert (MAX_AUDIO_CHANNELS == 6);
-/** Make all samples on all channels silent */
-void
-AudioBuffers::make_silent ()
-{
- for (int i = 0; i < _channels; ++i) {
- make_silent (i);
- }
+ /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
+ enhancement channel (sub-woofer)./
+ */
+ string const channels[] = {
+ _("Left"),
+ _("Right"),
+ _("Centre"),
+ _("Lfe (sub)"),
+ _("Left surround"),
+ _("Right surround"),
+ };
+
+ return channels[c];
}
-/** Make all samples on a given channel silent.
- * @param c Channel.
- */
-void
-AudioBuffers::make_silent (int c)
+FrameRateConversion::FrameRateConversion (float source, int dcp)
+ : skip (false)
+ , repeat (false)
+ , change_speed (false)
{
- assert (c >= 0 && c < _channels);
-
- for (int i = 0; i < _frames; ++i) {
- _data[c][i] = 0;
+ if (fabs (source / 2.0 - dcp) < (fabs (source - dcp))) {
+ skip = true;
+ } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
+ repeat = true;
}
-}
-/** 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());
+ change_speed = !about_equal (source * factor(), dcp);
- 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);
+ if (!skip && !repeat && !change_speed) {
+ description = _("Content and DCP have the same rate.\n");
+ } else {
+ if (skip) {
+ description = _("DCP will use every other frame of the content.\n");
+ } else if (repeat) {
+ description = _("Each content frame will be doubled in the DCP.\n");
+ }
- for (int i = 0; i < _channels; ++i) {
- memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float));
+ if (change_speed) {
+ float const pc = dcp * 100 / (source * factor());
+ description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
+ }
}
}
-/** 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)
+LocaleGuard::LocaleGuard ()
+ : _old (0)
{
- if (frames == 0) {
- return;
- }
-
- assert (from >= 0);
- assert (from < _frames);
- assert (to >= 0);
- assert (to < _frames);
- assert (frames > 0);
- assert (frames <= _frames);
- assert ((from + frames) <= _frames);
- assert ((to + frames) <= _frames);
-
- for (int i = 0; i < _channels; ++i) {
- memmove (_data[i] + to, _data[i] + from, frames * sizeof(float));
- }
-}
+ char const * old = setlocale (LC_NUMERIC, 0);
-/** Trip an assert if the caller is not in the UI thread */
-void
-ensure_ui_thread ()
-{
- assert (this_thread::get_id() == ui_thread);
+ if (old) {
+ _old = strdup (old);
+ if (strcmp (_old, "C")) {
+ setlocale (LC_NUMERIC, "C");
+ }
+ }
}
-/** @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)
+LocaleGuard::~LocaleGuard ()
{
- return ((int64_t) v * audio_sample_rate / frames_per_second);
+ setlocale (LC_NUMERIC, _old);
+ free (_old);
}
-/** @param f Filename.
- * @return true if this file is a still image, false if it is something else.
- */
bool
-still_image_file (string f)
+valid_image_file (boost::filesystem::path f)
{
-#if BOOST_FILESYSTEM_VERSION == 3
- string ext = boost::filesystem::path(f).extension().string();
-#else
- string ext = boost::filesystem::path(f).extension();
-#endif
-
+ string ext = f.extension().string();
transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-
- return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png");
+ return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".tga");
}
-/** @return A pair containing CPU model name and the number of processors */
-pair<string, int>
-cpu_info ()
-{
- pair<string, int> info;
- info.second = 0;
-
-#ifdef DVDOMATIC_POSIX
- ifstream f ("/proc/cpuinfo");
- while (f.good ()) {
- string l;
- getline (f, l);
- if (boost::algorithm::starts_with (l, "model name")) {
- string::size_type const c = l.find (':');
- if (c != string::npos) {
- info.first = l.substr (c + 2);
- }
- } else if (boost::algorithm::starts_with (l, "processor")) {
- ++info.second;
- }
- }
-#endif
-
- return info;
-}
diff --git a/src/lib/util.h b/src/lib/util.h
index 024c40fb5..a83426206 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -22,172 +22,92 @@
* @brief Some utility functions and classes.
*/
-#ifndef DVDOMATIC_UTIL_H
-#define DVDOMATIC_UTIL_H
+#ifndef DCPOMATIC_UTIL_H
+#define DCPOMATIC_UTIL_H
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
+#include <boost/optional.hpp>
+#include <boost/filesystem.hpp>
+#include <libdcp/util.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
}
#include "compose.hpp"
+#include "types.h"
+#include "video_content.h"
-#ifdef DVDOMATIC_DEBUG
+#ifdef DCPOMATIC_DEBUG
#define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
#else
#define TIMING(...)
#endif
+#undef check
+
/** The maximum number of audio channels that we can cope with */
#define MAX_AUDIO_CHANNELS 6
-class Scaler;
+class Job;
extern std::string seconds_to_hms (int);
extern std::string seconds_to_approximate_hms (int);
extern void stacktrace (std::ostream &, int);
extern std::string dependency_version_summary ();
extern double seconds (struct timeval);
-extern void dvdomatic_setup ();
+extern void dcpomatic_setup ();
+extern void dcpomatic_setup_gettext_i18n (std::string);
extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
-extern std::string md5_digest (std::string);
+extern std::string md5_digest (boost::filesystem::path);
+extern std::string md5_digest_directory (boost::filesystem::path, boost::shared_ptr<Job>);
extern std::string md5_digest (void const *, int);
extern void ensure_ui_thread ();
+extern std::string audio_channel_name (int);
+extern bool valid_image_file (boost::filesystem::path);
+#ifdef DCPOMATIC_WINDOWS
+extern boost::filesystem::path mo_path ();
+#endif
-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, ///< content is still images
- VIDEO ///< content is a video
-};
-
-/** @class Size
- * @brief Representation of the size of something */
-struct Size
-{
- /** Construct a zero Size */
- Size ()
- : width (0)
- , height (0)
- {}
-
- /** @param w Width.
- * @param h Height.
- */
- Size (int w, int h)
- : width (w)
- , height (h)
- {}
-
- /** width */
- int width;
- /** height */
- int height;
-};
-
-extern bool operator== (Size const & a, Size const & b);
-extern bool operator!= (Size const & a, Size const & b);
-
-/** @struct Crop
- * @brief A description of the crop of an image or video.
- */
-struct Crop
-{
- Crop () : left (0), right (0), top (0), bottom (0) {}
-
- /** Number of pixels to remove from the left-hand side */
- int left;
- /** Number of pixels to remove from the right-hand side */
- int right;
- /** Number of pixels to remove from the top */
- int top;
- /** Number of pixels to remove from the bottom */
- int bottom;
-};
-
-extern bool operator== (Crop const & a, Crop const & b);
-extern bool operator!= (Crop const & a, Crop const & b);
-
-/** @struct Position
- * @brief A position.
- */
-struct Position
-{
- Position ()
- : x (0)
- , y (0)
- {}
-
- Position (int x_, int y_)
- : x (x_)
- , y (y_)
- {}
-
- /** x coordinate */
- int x;
- /** y coordinate */
- int y;
-};
-
-/** @struct Rect
- * @brief A rectangle.
- */
-struct Rect
+struct FrameRateConversion
{
- Rect ()
- : x (0)
- , y (0)
- , width (0)
- , height (0)
- {}
-
- Rect (int x_, int y_, int w_, int h_)
- : x (x_)
- , y (y_)
- , width (w_)
- , height (h_)
- {}
-
- int x;
- int y;
- int width;
- int height;
-
- Position position () const {
- return Position (x, y);
+ FrameRateConversion (float, int);
+
+ /** @return factor by which to multiply a source frame rate
+ to get the effective rate after any skip or repeat has happened.
+ */
+ float factor () const {
+ if (skip) {
+ return 0.5;
+ } else if (repeat) {
+ return 2;
+ }
+
+ return 1;
}
- Size size () const {
- return Size (width, height);
- }
+ /** true to skip every other frame */
+ bool skip;
+ /** true to repeat every frame once */
+ bool repeat;
+ /** true if this DCP will run its video faster or slower than the source
+ * without taking into account `repeat' nor `skip'.
+ * (e.g. change_speed will be true if
+ * source is 29.97fps, DCP is 30fps
+ * source is 14.50fps, DCP is 30fps
+ * but not if
+ * source is 15.00fps, DCP is 30fps
+ * source is 12.50fps, DCP is 25fps)
+ */
+ bool change_speed;
- Rect intersection (Rect const & other) const;
+ std::string description;
};
-extern std::string crop_string (Position, Size);
-extern int dcp_audio_sample_rate (int);
-extern DCPFrameRate dcp_frame_rate (float);
-extern int dcp_audio_channels (int);
-extern std::string colour_lut_index_to_name (int index);
+extern int dcp_audio_frame_rate (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);
@@ -197,90 +117,52 @@ extern std::string get_optional_string (std::multimap<std::string, std::string>
/** @class Socket
* @brief A class to wrap a boost::asio::ip::tcp::socket with some things
- * that are useful for DVD-o-matic.
+ * that are useful for DCP-o-matic.
*
* This class wraps some things that I could not work out how to do with boost;
- * most notably, sync read/write calls with timeouts, and the ability to peek into
- * data being read.
+ * most notably, sync read/write calls with timeouts.
*/
class Socket
{
public:
- Socket ();
+ Socket (int timeout = 30);
/** @return Our underlying socket */
boost::asio::ip::tcp::socket& socket () {
return _socket;
}
- void connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint, int timeout);
- void write (uint8_t const * data, int size, int timeout);
+ void connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint);
+
+ void write (uint32_t n);
+ void write (uint8_t const * data, int size);
- void read_definite_and_consume (uint8_t* data, int size, int timeout);
- void read_indefinite (uint8_t* data, int size, int timeout);
- void consume (int amount);
+ void read (uint8_t* data, int size);
+ uint32_t read_uint32 ();
private:
void check ();
- int read (uint8_t* data, int size, int timeout);
Socket (Socket const &);
boost::asio::io_service _io_service;
boost::asio::deadline_timer _deadline;
boost::asio::ip::tcp::socket _socket;
- /** a buffer for small reads */
- uint8_t _buffer[1024];
- /** amount of valid data in the buffer */
- int _buffer_data;
+ int _timeout;
};
-/** @class AudioBuffers
- * @brief A class to hold multi-channel audio data in float format.
- */
-class AudioBuffers
+extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+
+class LocaleGuard
{
public:
- AudioBuffers (int channels, int frames);
- AudioBuffers (AudioBuffers const &);
- ~AudioBuffers ();
-
- float** data () const {
- return _data;
- }
+ LocaleGuard ();
+ ~LocaleGuard ();
- float* data (int) const;
-
- int channels () const {
- return _channels;
- }
-
- int frames () const {
- return _frames;
- }
-
- 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;
+ char* _old;
};
-extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
-extern bool still_image_file (std::string);
-extern std::pair<std::string, int> cpu_info ();
#endif
diff --git a/src/lib/version.h b/src/lib/version.h
index 71639e3bc..b70be8343 100644
--- a/src/lib/version.h
+++ b/src/lib/version.h
@@ -1,3 +1,4 @@
-extern char const * dvdomatic_version;
-extern char const * dvdomatic_git_commit;
+extern char const * dcpomatic_version;
+extern char const * dcpomatic_git_commit;
+extern char const * dcpomatic_cxx_flags;
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
new file mode 100644
index 000000000..3f6e171a5
--- /dev/null
+++ b/src/lib/video_content.cc
@@ -0,0 +1,278 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iomanip>
+#include <libcxml/cxml.h>
+#include "video_content.h"
+#include "video_examiner.h"
+#include "ratio.h"
+#include "compose.hpp"
+#include "config.h"
+#include "colour_conversion.h"
+
+#include "i18n.h"
+
+int const VideoContentProperty::VIDEO_SIZE = 0;
+int const VideoContentProperty::VIDEO_FRAME_RATE = 1;
+int const VideoContentProperty::VIDEO_FRAME_TYPE = 2;
+int const VideoContentProperty::VIDEO_CROP = 3;
+int const VideoContentProperty::VIDEO_RATIO = 4;
+int const VideoContentProperty::COLOUR_CONVERSION = 5;
+
+using std::string;
+using std::stringstream;
+using std::setprecision;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::optional;
+
+VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+ : Content (f, s)
+ , _video_length (len)
+ , _video_frame_rate (0)
+ , _video_frame_type (VIDEO_FRAME_TYPE_2D)
+ , _ratio (Ratio::from_id ("185"))
+ , _colour_conversion (Config::instance()->colour_conversions().front().conversion)
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , _video_length (0)
+ , _video_frame_rate (0)
+ , _video_frame_type (VIDEO_FRAME_TYPE_2D)
+ , _ratio (Ratio::from_id ("185"))
+ , _colour_conversion (Config::instance()->colour_conversions().front().conversion)
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+{
+ _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+ _video_size.width = node->number_child<int> ("VideoWidth");
+ _video_size.height = node->number_child<int> ("VideoHeight");
+ _video_frame_rate = node->number_child<float> ("VideoFrameRate");
+ _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType"));
+ _crop.left = node->number_child<int> ("LeftCrop");
+ _crop.right = node->number_child<int> ("RightCrop");
+ _crop.top = node->number_child<int> ("TopCrop");
+ _crop.bottom = node->number_child<int> ("BottomCrop");
+ optional<string> r = node->optional_string_child ("Ratio");
+ if (r) {
+ _ratio = Ratio::from_id (r.get ());
+ }
+ _colour_conversion = ColourConversion (node->node_child ("ColourConversion"));
+}
+
+void
+VideoContent::as_xml (xmlpp::Node* node) const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
+ node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
+ node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
+ node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
+ node->add_child("VideoFrameType")->add_child_text (lexical_cast<string> (static_cast<int> (_video_frame_type)));
+ node->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
+ node->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
+ node->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
+ node->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
+ if (_ratio) {
+ node->add_child("Ratio")->add_child_text (_ratio->id ());
+ }
+ _colour_conversion.as_xml (node->add_child("ColourConversion"));
+}
+
+void
+VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
+{
+ /* These examiner calls could call other content methods which take a lock on the mutex */
+ libdcp::Size const vs = d->video_size ();
+ float const vfr = d->video_frame_rate ();
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _video_size = vs;
+ _video_frame_rate = vfr;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_SIZE);
+ signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
+}
+
+
+string
+VideoContent::information () const
+{
+ if (video_size().width == 0 || video_size().height == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (
+ _("%1x%2 pixels (%3:1)"),
+ video_size().width,
+ video_size().height,
+ setprecision (3), float (video_size().width) / video_size().height
+ );
+
+ return s.str ();
+}
+
+void
+VideoContent::set_left_crop (int c)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (_crop.left == c) {
+ return;
+ }
+
+ _crop.left = c;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_right_crop (int c)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ if (_crop.right == c) {
+ return;
+ }
+
+ _crop.right = c;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_top_crop (int c)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ if (_crop.top == c) {
+ return;
+ }
+
+ _crop.top = c;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_bottom_crop (int c)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ if (_crop.bottom == c) {
+ return;
+ }
+
+ _crop.bottom = c;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+void
+VideoContent::set_ratio (Ratio const * r)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ if (_ratio == r) {
+ return;
+ }
+
+ _ratio = r;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_RATIO);
+}
+
+/** @return string which includes everything about how this content looks */
+string
+VideoContent::identifier () const
+{
+ stringstream s;
+ s << Content::digest()
+ << "_" << crop().left
+ << "_" << crop().right
+ << "_" << crop().top
+ << "_" << crop().bottom
+ << "_" << colour_conversion().identifier ();
+
+ if (ratio()) {
+ s << "_" << ratio()->id ();
+ }
+
+ return s.str ();
+}
+
+void
+VideoContent::set_video_frame_type (VideoFrameType t)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _video_frame_type = t;
+ }
+
+ signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
+}
+
+string
+VideoContent::technical_summary () const
+{
+ return String::compose ("video: length %1, size %2x%3, rate %4", video_length(), video_size().width, video_size().height, video_frame_rate());
+}
+
+libdcp::Size
+VideoContent::video_size_after_3d_split () const
+{
+ libdcp::Size const s = video_size ();
+ switch (video_frame_type ()) {
+ case VIDEO_FRAME_TYPE_2D:
+ return s;
+ case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
+ return libdcp::Size (s.width / 2, s.height);
+ }
+
+ assert (false);
+}
+
+void
+VideoContent::set_colour_conversion (ColourConversion c)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _colour_conversion = c;
+ }
+
+ signal_changed (VideoContentProperty::COLOUR_CONVERSION);
+}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
new file mode 100644
index 000000000..72c72625b
--- /dev/null
+++ b/src/lib/video_content.h
@@ -0,0 +1,121 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_VIDEO_CONTENT_H
+#define DCPOMATIC_VIDEO_CONTENT_H
+
+#include "content.h"
+#include "colour_conversion.h"
+
+class VideoExaminer;
+class Ratio;
+
+class VideoContentProperty
+{
+public:
+ static int const VIDEO_SIZE;
+ static int const VIDEO_FRAME_RATE;
+ static int const VIDEO_FRAME_TYPE;
+ static int const VIDEO_CROP;
+ static int const VIDEO_RATIO;
+ static int const COLOUR_CONVERSION;
+};
+
+class VideoContent : public virtual Content
+{
+public:
+ typedef int Frame;
+
+ VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+ VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+ std::string technical_summary () const;
+ virtual std::string information () const;
+ virtual std::string identifier () const;
+
+ VideoContent::Frame video_length () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_length;
+ }
+
+ libdcp::Size video_size () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_size;
+ }
+
+ float video_frame_rate () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_frame_rate;
+ }
+
+ void set_video_frame_type (VideoFrameType);
+
+ void set_left_crop (int);
+ void set_right_crop (int);
+ void set_top_crop (int);
+ void set_bottom_crop (int);
+
+ void set_colour_conversion (ColourConversion);
+
+ VideoFrameType video_frame_type () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _video_frame_type;
+ }
+
+ Crop crop () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _crop;
+ }
+
+ void set_ratio (Ratio const *);
+
+ Ratio const * ratio () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _ratio;
+ }
+
+ ColourConversion colour_conversion () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _colour_conversion;
+ }
+
+ libdcp::Size video_size_after_3d_split () const;
+
+protected:
+ void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
+
+ VideoContent::Frame _video_length;
+
+private:
+ friend class ffmpeg_pts_offset_test;
+ friend class best_dcp_frame_rate_test_single;
+ friend class best_dcp_frame_rate_test_double;
+ friend class audio_sampling_rate_test;
+
+ libdcp::Size _video_size;
+ float _video_frame_rate;
+ VideoFrameType _video_frame_type;
+ Crop _crop;
+ Ratio const * _ratio;
+ ColourConversion _colour_conversion;
+};
+
+#endif
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index e0a7576ee..eaa4534e4 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -18,84 +18,37 @@
*/
#include "video_decoder.h"
-#include "subtitle.h"
-#include "film.h"
#include "image.h"
-#include "log.h"
-#include "options.h"
-#include "job.h"
+#include "i18n.h"
+
+using std::cout;
using boost::shared_ptr;
-using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
- : Decoder (f, o, j)
- , _video_frame (0)
- , _last_source_time (0)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
+ : Decoder (f)
+ , _video_content (c)
+ , _video_position (0)
{
}
-/** Called by subclasses to tell the world that some video data is ready.
- * We find a subtitle then emit it for listeners.
- * @param image frame to emit.
- * @param t Time of the frame within the source, in seconds.
- */
void
-VideoDecoder::emit_video (shared_ptr<Image> image, double t)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
{
- shared_ptr<Subtitle> sub;
- if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
- sub = _timed_subtitle->subtitle ();
+ switch (_video_content->video_frame_type ()) {
+ case VIDEO_FRAME_TYPE_2D:
+ Video (image, EYES_BOTH, same, frame);
+ break;
+ case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
+ {
+ int const half = image->size().width / 2;
+ Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
+ Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+ break;
}
-
- signal_video (image, false, sub);
- _last_source_time = t;
-}
-
-void
-VideoDecoder::repeat_last_video ()
-{
- if (!_last_image) {
- _last_image.reset (new SimpleImage (pixel_format(), native_size(), false));
- _last_image->make_black ();
}
-
- signal_video (_last_image, true, _last_subtitle);
-}
-
-void
-VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
-{
- TIMING ("Decoder emits %1", _video_frame);
- Video (image, same, sub);
- ++_video_frame;
-
- _last_image = image;
- _last_subtitle = sub;
-}
-
-void
-VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
-{
- _timed_subtitle = s;
- if (_timed_subtitle) {
- 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;
+ _video_position = frame + 1;
}
-void
-VideoDecoder::set_progress () const
-{
- if (_job && _film->length()) {
- _job->set_progress (float (_video_frame) / _film->length().get());
- }
-}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 7726d2057..142320a04 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -17,73 +17,41 @@
*/
-#ifndef DVDOMATIC_VIDEO_DECODER_H
-#define DVDOMATIC_VIDEO_DECODER_H
+#ifndef DCPOMATIC_VIDEO_DECODER_H
+#define DCPOMATIC_VIDEO_DECODER_H
-#include "video_source.h"
-#include "stream.h"
+#include <boost/signals2.hpp>
+#include <boost/shared_ptr.hpp>
#include "decoder.h"
+#include "video_content.h"
+#include "util.h"
-class VideoDecoder : public VideoSource, public virtual Decoder
+class VideoContent;
+class Image;
+
+class VideoDecoder : public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, 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;
- /** @return length (in source video frames), according to our content's header */
- virtual SourceFrame length () const = 0;
-
- virtual int time_base_numerator () const = 0;
- virtual int time_base_denominator () const = 0;
- virtual int sample_aspect_ratio_numerator () const = 0;
- virtual int sample_aspect_ratio_denominator () const = 0;
-
- virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
-
- void set_progress () const;
+ VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
+
+ /** Seek so that the next pass() will yield (approximately) the requested frame.
+ * Pass accurate = true to try harder to get close to the request.
+ */
+ virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
+
+ /** Emitted when a video frame is ready.
+ * First parameter is the video image.
+ * Second parameter is the eye(s) which should see this image.
+ * Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
+ * Fourth parameter is the frame within our source.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
- int video_frame () const {
- return _video_frame;
- }
-
- boost::shared_ptr<SubtitleStream> subtitle_stream () const {
- return _subtitle_stream;
- }
-
- std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
- return _subtitle_streams;
- }
-
- double last_source_time () const {
- return _last_source_time;
- }
-
protected:
-
- virtual PixelFormat pixel_format () const = 0;
-
- void emit_video (boost::shared_ptr<Image>, double);
- void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
- void repeat_last_video ();
-
- /** Subtitle stream to use when decoding */
- boost::shared_ptr<SubtitleStream> _subtitle_stream;
- /** Subtitle streams that this decoder's content has */
- std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
-
-private:
- void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
-
- int _video_frame;
- double _last_source_time;
-
- boost::shared_ptr<TimedSubtitle> _timed_subtitle;
- boost::shared_ptr<Image> _last_image;
- boost::shared_ptr<Subtitle> _last_subtitle;
+ void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ boost::shared_ptr<const VideoContent> _video_content;
+ VideoContent::Frame _video_position;
};
#endif
diff --git a/src/lib/audio_source.cc b/src/lib/video_examiner.h
index 53b0dda15..039c494b5 100644
--- a/src/lib/audio_source.cc
+++ b/src/lib/video_examiner.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,14 +17,15 @@
*/
-#include "audio_source.h"
-#include "audio_sink.h"
+#include <libdcp/types.h>
+#include "types.h"
+#include "video_content.h"
-using boost::shared_ptr;
-using boost::bind;
-
-void
-AudioSource::connect_audio (shared_ptr<AudioSink> s)
+class VideoExaminer
{
- Audio.connect (bind (&AudioSink::process_audio, s, _1));
-}
+public:
+ virtual ~VideoExaminer () {}
+ virtual float video_frame_rate () const = 0;
+ virtual libdcp::Size video_size () const = 0;
+ virtual VideoContent::Frame video_length () const = 0;
+};
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
deleted file mode 100644
index 893629160..000000000
--- a/src/lib/video_source.h
+++ /dev/null
@@ -1,52 +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/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 true if the image is the same as the last one that was emitted.
- * Third parameter is either 0 or a subtitle that should be on this frame.
- */
- boost::signals2::signal<void (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>)> Video;
-
- void connect_video (boost::shared_ptr<VideoSink>);
-};
-
-#endif
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
new file mode 100644
index 000000000..5f94d5d6b
--- /dev/null
+++ b/src/lib/writer.cc
@@ -0,0 +1,525 @@
+/*
+ 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 <fstream>
+#include <cerrno>
+#include <libdcp/picture_asset.h>
+#include <libdcp/sound_asset.h>
+#include <libdcp/picture_frame.h>
+#include <libdcp/reel.h>
+#include <libdcp/dcp.h>
+#include <libdcp/cpl.h>
+#include "writer.h"
+#include "compose.hpp"
+#include "film.h"
+#include "ratio.h"
+#include "log.h"
+#include "dcp_video_frame.h"
+#include "dcp_content_type.h"
+#include "player.h"
+#include "audio_mapping.h"
+#include "config.h"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::ifstream;
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+
+int const Writer::_maximum_frames_in_memory = 8;
+
+Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j)
+ : _film (f)
+ , _job (j)
+ , _first_nonexistant_frame (0)
+ , _thread (0)
+ , _finish (false)
+ , _queued_full_in_memory (0)
+ , _last_written_frame (-1)
+ , _last_written_eyes (EYES_RIGHT)
+ , _full_written (0)
+ , _fake_written (0)
+ , _repeat_written (0)
+ , _pushed_to_disk (0)
+{
+ /* Remove any old DCP */
+ boost::filesystem::remove_all (_film->dir (_film->dcp_name ()));
+
+ check_existing_picture_mxf ();
+
+ /* Create our picture asset in a subdirectory, named according to those
+ film's parameters which affect the video output. We will hard-link
+ it into the DCP later.
+ */
+
+ if (f->three_d ()) {
+ _picture_asset.reset (
+ new libdcp::StereoPictureAsset (
+ _film->internal_video_mxf_dir (),
+ _film->internal_video_mxf_filename (),
+ _film->video_frame_rate (),
+ _film->container()->size (_film->full_frame ())
+ )
+ );
+
+ } else {
+ _picture_asset.reset (
+ new libdcp::MonoPictureAsset (
+ _film->internal_video_mxf_dir (),
+ _film->internal_video_mxf_filename (),
+ _film->video_frame_rate (),
+ _film->container()->size (_film->full_frame ())
+ )
+ );
+
+ }
+
+ _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0, _film->interop ());
+
+ _sound_asset.reset (
+ new libdcp::SoundAsset (
+ _film->dir (_film->dcp_name()),
+ _film->audio_mxf_filename (),
+ _film->video_frame_rate (),
+ _film->audio_channels (),
+ _film->audio_frame_rate ()
+ )
+ );
+
+ _sound_asset_writer = _sound_asset->start_write (_film->interop ());
+
+ _thread = new boost::thread (boost::bind (&Writer::thread, this));
+
+ _job->descend (0.9);
+}
+
+void
+Writer::write (shared_ptr<const EncodedData> encoded, int frame, Eyes eyes)
+{
+ boost::mutex::scoped_lock lock (_mutex);
+
+ QueueItem qi;
+ qi.type = QueueItem::FULL;
+ qi.encoded = encoded;
+ qi.frame = frame;
+
+ if (_film->three_d() && eyes == EYES_BOTH) {
+ /* 2D material in a 3D DCP; fake the 3D */
+ qi.eyes = EYES_LEFT;
+ _queue.push_back (qi);
+ ++_queued_full_in_memory;
+ qi.eyes = EYES_RIGHT;
+ _queue.push_back (qi);
+ ++_queued_full_in_memory;
+ } else {
+ qi.eyes = eyes;
+ _queue.push_back (qi);
+ ++_queued_full_in_memory;
+ }
+
+ _condition.notify_all ();
+}
+
+void
+Writer::fake_write (int frame, Eyes eyes)
+{
+ boost::mutex::scoped_lock lock (_mutex);
+
+ ifstream ifi (_film->info_path (frame, eyes).c_str());
+ libdcp::FrameInfo info (ifi);
+
+ QueueItem qi;
+ qi.type = QueueItem::FAKE;
+ qi.size = info.size;
+ qi.frame = frame;
+ if (_film->three_d() && eyes == EYES_BOTH) {
+ qi.eyes = EYES_LEFT;
+ _queue.push_back (qi);
+ qi.eyes = EYES_RIGHT;
+ _queue.push_back (qi);
+ } else {
+ qi.eyes = eyes;
+ _queue.push_back (qi);
+ }
+
+ _condition.notify_all ();
+}
+
+/** This method is not thread safe */
+void
+Writer::write (shared_ptr<const AudioBuffers> audio)
+{
+ _sound_asset_writer->write (audio->data(), audio->frames());
+}
+
+/** This must be called from Writer::thread() with an appropriate lock held,
+ * and with _queue sorted.
+ */
+bool
+Writer::have_sequenced_image_at_queue_head () const
+{
+ if (_queue.empty ()) {
+ return false;
+ }
+
+ /* The queue should contain only EYES_LEFT/EYES_RIGHT pairs or EYES_BOTH */
+
+ if (_queue.front().eyes == EYES_BOTH) {
+ /* 2D */
+ return _queue.front().frame == (_last_written_frame + 1);
+ }
+
+ /* 3D */
+
+ if (_last_written_eyes == EYES_LEFT && _queue.front().frame == _last_written_frame && _queue.front().eyes == EYES_RIGHT) {
+ return true;
+ }
+
+ if (_last_written_eyes == EYES_RIGHT && _queue.front().frame == (_last_written_frame + 1) && _queue.front().eyes == EYES_LEFT) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+Writer::thread ()
+try
+{
+ while (1)
+ {
+ boost::mutex::scoped_lock lock (_mutex);
+
+ while (1) {
+
+ _queue.sort ();
+
+ if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
+ break;
+ }
+
+ TIMING (N_("writer sleeps with a queue of %1"), _queue.size());
+ _condition.wait (lock);
+ TIMING (N_("writer wakes with a queue of %1"), _queue.size());
+ }
+
+ if (_finish && _queue.empty()) {
+ return;
+ }
+
+ /* Write any frames that we can write; i.e. those that are in sequence */
+ while (have_sequenced_image_at_queue_head ()) {
+ QueueItem qi = _queue.front ();
+ _queue.pop_front ();
+ if (qi.type == QueueItem::FULL && qi.encoded) {
+ --_queued_full_in_memory;
+ }
+
+ lock.unlock ();
+ switch (qi.type) {
+ case QueueItem::FULL:
+ {
+ _film->log()->log (String::compose (N_("Writer FULL-writes %1 to MXF"), qi.frame));
+ if (!qi.encoded) {
+ qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
+ }
+
+ libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
+ qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
+ _last_written[qi.eyes] = qi.encoded;
+ ++_full_written;
+ break;
+ }
+ case QueueItem::FAKE:
+ _film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame));
+ _picture_asset_writer->fake_write (qi.size);
+ _last_written[qi.eyes].reset ();
+ ++_fake_written;
+ break;
+ case QueueItem::REPEAT:
+ {
+ _film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame));
+ libdcp::FrameInfo fin = _picture_asset_writer->write (
+ _last_written[qi.eyes]->data(),
+ _last_written[qi.eyes]->size()
+ );
+
+ _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
+ ++_repeat_written;
+ break;
+ }
+ }
+ lock.lock ();
+
+ _last_written_frame = qi.frame;
+ _last_written_eyes = qi.eyes;
+
+ if (_film->length()) {
+ _job->set_progress (
+ float (_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length())
+ );
+ }
+ }
+
+ while (_queued_full_in_memory > _maximum_frames_in_memory) {
+ /* Too many frames in memory which can't yet be written to the stream.
+ Write some FULL frames to disk.
+ */
+
+ /* Find one */
+ list<QueueItem>::reverse_iterator i = _queue.rbegin ();
+ while (i != _queue.rend() && (i->type != QueueItem::FULL || !i->encoded)) {
+ ++i;
+ }
+
+ assert (i != _queue.rend());
+ QueueItem qi = *i;
+
+ ++_pushed_to_disk;
+
+ lock.unlock ();
+
+ _film->log()->log (
+ String::compose (
+ "Writer full (awaiting %1 [last eye was %2]); pushes %3 to disk",
+ _last_written_frame + 1,
+ _last_written_eyes, qi.frame)
+ );
+
+ qi.encoded->write (_film, qi.frame, qi.eyes);
+ lock.lock ();
+ qi.encoded.reset ();
+ --_queued_full_in_memory;
+ }
+ }
+}
+catch (...)
+{
+ store_current ();
+}
+
+void
+Writer::finish ()
+{
+ if (!_thread) {
+ return;
+ }
+
+ boost::mutex::scoped_lock lock (_mutex);
+ _finish = true;
+ _condition.notify_all ();
+ lock.unlock ();
+
+ _thread->join ();
+ if (thrown ()) {
+ rethrow ();
+ }
+
+ delete _thread;
+ _thread = 0;
+
+ _picture_asset_writer->finalize ();
+ _sound_asset_writer->finalize ();
+
+ int const frames = _last_written_frame + 1;
+
+ _picture_asset->set_duration (frames);
+
+ /* Hard-link the video MXF into the DCP */
+
+ boost::filesystem::path from;
+ from /= _film->internal_video_mxf_dir();
+ from /= _film->internal_video_mxf_filename();
+
+ boost::filesystem::path to;
+ to /= _film->dir (_film->dcp_name());
+ to /= _film->video_mxf_filename ();
+
+ boost::system::error_code ec;
+ boost::filesystem::create_hard_link (from, to, ec);
+ if (ec) {
+ /* hard link failed; copy instead */
+ boost::filesystem::copy_file (from, to);
+ _film->log()->log ("Hard-link failed; fell back to copying");
+ }
+
+ /* And update the asset */
+
+ _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
+ _picture_asset->set_file_name (_film->video_mxf_filename ());
+ _sound_asset->set_duration (frames);
+
+ libdcp::DCP dcp (_film->dir (_film->dcp_name()));
+
+ shared_ptr<libdcp::CPL> cpl (
+ new libdcp::CPL (
+ _film->dir (_film->dcp_name()),
+ _film->dcp_name(),
+ _film->dcp_content_type()->libdcp_kind (),
+ frames,
+ _film->video_frame_rate ()
+ )
+ );
+
+ dcp.add_cpl (cpl);
+
+ cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
+ _picture_asset,
+ _sound_asset,
+ shared_ptr<libdcp::SubtitleAsset> ()
+ )
+ ));
+
+ /* Compute the digests for the assets now so that we can keep track of progress.
+ We did _job->descend (0.9) in our constructor */
+ _job->ascend ();
+
+ _job->descend (0.1);
+ _picture_asset->compute_digest (boost::bind (&Job::set_progress, _job.get(), _1));
+ _job->ascend ();
+
+ _job->descend (0.1);
+ _sound_asset->compute_digest (boost::bind (&Job::set_progress, _job.get(), _1));
+ _job->ascend ();
+
+ libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+ meta.set_issue_date_now ();
+ dcp.write_xml (_film->interop (), meta);
+
+ _film->log()->log (String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk));
+}
+
+/** Tell the writer that frame `f' should be a repeat of the frame before it */
+void
+Writer::repeat (int f, Eyes e)
+{
+ boost::mutex::scoped_lock lock (_mutex);
+
+ QueueItem qi;
+ qi.type = QueueItem::REPEAT;
+ qi.frame = f;
+ if (_film->three_d() && e == EYES_BOTH) {
+ qi.eyes = EYES_LEFT;
+ _queue.push_back (qi);
+ qi.eyes = EYES_RIGHT;
+ _queue.push_back (qi);
+ } else {
+ qi.eyes = e;
+ _queue.push_back (qi);
+ }
+
+ _condition.notify_all ();
+}
+
+bool
+Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
+{
+ /* Read the frame info as written */
+ ifstream ifi (_film->info_path (f, eyes).c_str());
+ libdcp::FrameInfo info (ifi);
+ if (info.size == 0) {
+ _film->log()->log (String::compose ("Existing frame %1 has no info file", f));
+ return false;
+ }
+
+ /* Read the data from the MXF and hash it */
+ fseek (mxf, info.offset, SEEK_SET);
+ EncodedData data (info.size);
+ size_t const read = fread (data.data(), 1, data.size(), mxf);
+ if (read != static_cast<size_t> (data.size ())) {
+ _film->log()->log (String::compose ("Existing frame %1 is incomplete", f));
+ return false;
+ }
+
+ string const existing_hash = md5_digest (data.data(), data.size());
+ if (existing_hash != info.hash) {
+ _film->log()->log (String::compose ("Existing frame %1 failed hash check", f));
+ return false;
+ }
+
+ return true;
+}
+
+void
+Writer::check_existing_picture_mxf ()
+{
+ /* Try to open the existing MXF */
+ boost::filesystem::path p;
+ p /= _film->internal_video_mxf_dir ();
+ p /= _film->internal_video_mxf_filename ();
+ FILE* mxf = fopen (p.string().c_str(), "rb");
+ if (!mxf) {
+ _film->log()->log (String::compose ("Could not open existing MXF at %1 (errno=%2)", p.string(), errno));
+ return;
+ }
+
+ while (1) {
+
+ if (_film->three_d ()) {
+ if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_LEFT)) {
+ break;
+ }
+ if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_RIGHT)) {
+ break;
+ }
+ } else {
+ if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_BOTH)) {
+ break;
+ }
+ }
+
+ _film->log()->log (String::compose ("Have existing frame %1", _first_nonexistant_frame));
+ ++_first_nonexistant_frame;
+ }
+
+ fclose (mxf);
+}
+
+/** @param frame Frame index.
+ * @return true if we can fake-write this frame.
+ */
+bool
+Writer::can_fake_write (int frame) const
+{
+ /* We have to do a proper write of the first frame so that we can set up the JPEG2000
+ parameters in the MXF writer.
+ */
+ return (frame != 0 && frame < _first_nonexistant_frame);
+}
+
+bool
+operator< (QueueItem const & a, QueueItem const & b)
+{
+ if (a.frame != b.frame) {
+ return a.frame < b.frame;
+ }
+
+ return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
+}
+
+bool
+operator== (QueueItem const & a, QueueItem const & b)
+{
+ return a.frame == b.frame && a.eyes == b.eyes;
+}
diff --git a/src/lib/writer.h b/src/lib/writer.h
new file mode 100644
index 000000000..d922cfce0
--- /dev/null
+++ b/src/lib/writer.h
@@ -0,0 +1,133 @@
+/*
+ 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 <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/condition.hpp>
+#include "exceptions.h"
+#include "types.h"
+
+class Film;
+class EncodedData;
+class AudioBuffers;
+class Job;
+
+namespace libdcp {
+ class MonoPictureAsset;
+ class MonoPictureAssetWriter;
+ class StereoPictureAsset;
+ class StereoPictureAssetWriter;
+ class PictureAsset;
+ class PictureAssetWriter;
+ class SoundAsset;
+ class SoundAssetWriter;
+}
+
+struct QueueItem
+{
+public:
+ enum Type {
+ /** a normal frame with some JPEG200 data */
+ FULL,
+ /** a frame whose data already exists in the MXF,
+ and we fake-write it; i.e. we update the writer's
+ state but we use the data that is already on disk.
+ */
+ FAKE,
+ /** this is a repeat of the last frame to be written */
+ REPEAT
+ } type;
+
+ /** encoded data for FULL */
+ boost::shared_ptr<const EncodedData> encoded;
+ /** size of data for FAKE */
+ int size;
+ /** frame index */
+ int frame;
+ Eyes eyes;
+};
+
+bool operator< (QueueItem const & a, QueueItem const & b);
+bool operator== (QueueItem const & a, QueueItem const & b);
+
+class Writer : public ExceptionStore, public boost::noncopyable
+{
+public:
+ Writer (boost::shared_ptr<const Film>, boost::shared_ptr<Job>);
+
+ bool can_fake_write (int) const;
+
+ void write (boost::shared_ptr<const EncodedData>, int, Eyes);
+ void fake_write (int, Eyes);
+ void write (boost::shared_ptr<const AudioBuffers>);
+ void repeat (int f, Eyes);
+ void finish ();
+
+private:
+
+ void thread ();
+ void check_existing_picture_mxf ();
+ bool check_existing_picture_mxf_frame (FILE *, int, Eyes);
+ bool have_sequenced_image_at_queue_head () const;
+
+ /** our Film */
+ boost::shared_ptr<const Film> _film;
+ boost::shared_ptr<Job> _job;
+ /** the first frame index that does not already exist in our MXF */
+ int _first_nonexistant_frame;
+
+ /** our thread, or 0 */
+ boost::thread* _thread;
+ /** true if our thread should finish */
+ bool _finish;
+ /** queue of things to write to disk */
+ std::list<QueueItem> _queue;
+ /** number of FULL frames whose JPEG200 data is currently held in RAM */
+ int _queued_full_in_memory;
+ /** mutex for thread state */
+ mutable boost::mutex _mutex;
+ /** condition to manage thread wakeups */
+ boost::condition _condition;
+ /** the data of the last written frame, or 0 if there isn't one */
+ boost::shared_ptr<const EncodedData> _last_written[EYES_COUNT];
+ /** the index of the last written frame */
+ int _last_written_frame;
+ Eyes _last_written_eyes;
+ /** maximum number of frames to hold in memory, for when we are managing
+ ordering
+ */
+ static const int _maximum_frames_in_memory;
+
+ /** number of FULL written frames */
+ int _full_written;
+ /** number of FAKE written frames */
+ int _fake_written;
+ /** number of REPEAT written frames */
+ int _repeat_written;
+ /** number of frames pushed to disk and then recovered
+ due to the limit of frames to be held in memory.
+ */
+ int _pushed_to_disk;
+
+ boost::shared_ptr<libdcp::PictureAsset> _picture_asset;
+ boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer;
+ boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
+ boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+};
diff --git a/src/lib/wscript b/src/lib/wscript
index b2b639f06..6c45d8b1e 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -1,61 +1,94 @@
+import os
+import i18n
+
+sources = """
+ analyse_audio_job.cc
+ audio_analysis.cc
+ audio_buffers.cc
+ audio_content.cc
+ audio_decoder.cc
+ audio_mapping.cc
+ colour_conversion.cc
+ config.cc
+ content.cc
+ content_factory.cc
+ cross.cc
+ dci_metadata.cc
+ dcp_content_type.cc
+ dcp_video_frame.cc
+ decoder.cc
+ dolby_cp750.cc
+ encoder.cc
+ examine_content_job.cc
+ exceptions.cc
+ filter_graph.cc
+ ffmpeg.cc
+ ffmpeg_content.cc
+ ffmpeg_decoder.cc
+ ffmpeg_examiner.cc
+ film.cc
+ filter.cc
+ image.cc
+ job.cc
+ job_manager.cc
+ log.cc
+ moving_image_content.cc
+ moving_image_decoder.cc
+ moving_image_examiner.cc
+ player.cc
+ playlist.cc
+ ratio.cc
+ resampler.cc
+ scp_dcp_job.cc
+ scaler.cc
+ server.cc
+ sndfile_content.cc
+ sndfile_decoder.cc
+ sound_processor.cc
+ still_image_content.cc
+ still_image_decoder.cc
+ still_image_examiner.cc
+ subtitle_content.cc
+ subtitle_decoder.cc
+ timer.cc
+ transcode_job.cc
+ transcoder.cc
+ types.cc
+ ui_signaller.cc
+ util.cc
+ video_content.cc
+ video_decoder.cc
+ writer.cc
+ """
+
def build(bld):
if bld.env.STATIC:
obj = bld(features = 'cxx cxxstlib')
else:
obj = bld(features = 'cxx cxxshlib')
- obj.name = 'libdvdomatic'
- obj.export_includes = ['.']
- obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB'
+ obj.name = 'libdcpomatic'
+ obj.export_includes = ['..']
+ obj.uselib = """
+ AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
+ BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
+ SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
+ """
+
+ obj.source = sources + ' version.cc'
+
if bld.env.TARGET_WINDOWS:
- obj.uselib += ' WINSOCK2'
- 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
- decoder.cc
- decoder_factory.cc
- delay_line.cc
- dolby_cp750.cc
- encoder.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
- job.cc
- job_manager.cc
- 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
- timer.cc
- transcode_job.cc
- transcoder.cc
- ui_signaller.cc
- util.cc
- version.cc
- video_decoder.cc
- video_source.cc
- """
-
- obj.target = 'dvdomatic'
+ obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI'
+ obj.source += ' stack.cpp'
+ if bld.env.STATIC:
+ obj.uselib += ' XML++'
+
+ obj.target = 'dcpomatic'
+
+ i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld)
+
+def pot(bld):
+ i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic')
+
+def pot_merge(bld):
+ i18n.pot_merge(os.path.join('src', 'lib'), 'libdcpomatic')