summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/ab_transcode_job.cc72
-rw-r--r--src/lib/ab_transcode_job.h53
-rw-r--r--src/lib/ab_transcoder.cc142
-rw-r--r--src/lib/ab_transcoder.h74
-rw-r--r--src/lib/analyse_audio_job.cc51
-rw-r--r--src/lib/analyse_audio_job.h9
-rw-r--r--src/lib/audio_analysis.cc10
-rw-r--r--src/lib/audio_analysis.h9
-rw-r--r--src/lib/audio_buffers.cc243
-rw-r--r--src/lib/audio_buffers.h68
-rw-r--r--src/lib/audio_content.cc122
-rw-r--r--src/lib/audio_content.h83
-rw-r--r--src/lib/audio_decoder.cc49
-rw-r--r--src/lib/audio_decoder.h33
-rw-r--r--src/lib/audio_mapping.cc116
-rw-r--r--src/lib/audio_mapping.h58
-rw-r--r--src/lib/audio_source.cc42
-rw-r--r--src/lib/audio_source.h55
-rw-r--r--src/lib/combiner.h42
-rw-r--r--src/lib/config.cc137
-rw-r--r--src/lib/config.h37
-rw-r--r--src/lib/content.cc98
-rw-r--r--src/lib/content.h94
-rw-r--r--src/lib/cross.cc20
-rw-r--r--src/lib/cross.h9
-rw-r--r--src/lib/dci_metadata.cc33
-rw-r--r--src/lib/dci_metadata.h16
-rw-r--r--src/lib/dcp_content_type.h4
-rw-r--r--src/lib/dcp_video_frame.cc117
-rw-r--r--src/lib/dcp_video_frame.h19
-rw-r--r--src/lib/decoder.cc39
-rw-r--r--src/lib/decoder.h39
-rw-r--r--src/lib/decoder_factory.cc60
-rw-r--r--src/lib/decoder_factory.h49
-rw-r--r--src/lib/delay_line.cc57
-rw-r--r--src/lib/encoder.cc174
-rw-r--r--src/lib/encoder.h38
-rw-r--r--src/lib/examine_content_job.cc66
-rw-r--r--src/lib/examine_content_job.h16
-rw-r--r--src/lib/exceptions.h5
-rw-r--r--src/lib/ffmpeg.cc147
-rw-r--r--src/lib/ffmpeg.h75
-rw-r--r--src/lib/ffmpeg_content.cc376
-rw-r--r--src/lib/ffmpeg_content.h153
-rw-r--r--src/lib/ffmpeg_decoder.cc635
-rw-r--r--src/lib/ffmpeg_decoder.h107
-rw-r--r--src/lib/ffmpeg_examiner.cc168
-rw-r--r--src/lib/ffmpeg_examiner.h55
-rw-r--r--src/lib/film.cc1142
-rw-r--r--src/lib/film.h342
-rw-r--r--src/lib/filter.h4
-rw-r--r--src/lib/filter_graph.cc46
-rw-r--r--src/lib/filter_graph.h33
-rw-r--r--src/lib/format.cc240
-rw-r--r--src/lib/format.h126
-rw-r--r--src/lib/gain.cc45
-rw-r--r--src/lib/i18n.h2
-rw-r--r--src/lib/image.cc86
-rw-r--r--src/lib/image.h10
-rw-r--r--src/lib/imagemagick.h (renamed from src/lib/options.h)28
-rw-r--r--src/lib/imagemagick_content.cc109
-rw-r--r--src/lib/imagemagick_content.h51
-rw-r--r--src/lib/imagemagick_decoder.cc123
-rw-r--r--src/lib/imagemagick_decoder.h64
-rw-r--r--src/lib/imagemagick_examiner.cc63
-rw-r--r--src/lib/imagemagick_examiner.h41
-rw-r--r--src/lib/job.cc10
-rw-r--r--src/lib/job.h11
-rw-r--r--src/lib/job_manager.cc2
-rw-r--r--src/lib/log.h4
-rw-r--r--src/lib/matcher.cc224
-rw-r--r--src/lib/matcher.h77
-rw-r--r--src/lib/player.cc499
-rw-r--r--src/lib/player.h105
-rw-r--r--src/lib/playlist.cc308
-rw-r--r--src/lib/playlist.h104
-rw-r--r--src/lib/po/es_ES.po6
-rw-r--r--src/lib/po/fr_FR.po13
-rw-r--r--src/lib/po/it_IT.po4
-rw-r--r--src/lib/po/sv_SE.po6
-rw-r--r--src/lib/processor.h115
-rw-r--r--src/lib/ratio.cc71
-rw-r--r--src/lib/ratio.h71
-rw-r--r--src/lib/resampler.cc61
-rw-r--r--src/lib/resampler.h21
-rw-r--r--src/lib/scaler.h4
-rw-r--r--src/lib/scp_dcp_job.cc2
-rw-r--r--src/lib/scp_dcp_job.h4
-rw-r--r--src/lib/server.cc59
-rw-r--r--src/lib/server.h9
-rw-r--r--src/lib/sndfile_content.cc160
-rw-r--r--src/lib/sndfile_content.h78
-rw-r--r--src/lib/sndfile_decoder.cc174
-rw-r--r--src/lib/sndfile_decoder.h38
-rw-r--r--src/lib/sound_processor.h4
-rw-r--r--src/lib/stack.cpp2
-rw-r--r--src/lib/stack.hpp2
-rw-r--r--src/lib/stream.cc92
-rw-r--r--src/lib/stream.h121
-rw-r--r--src/lib/subtitle.cc21
-rw-r--r--src/lib/subtitle.h18
-rw-r--r--src/lib/timer.h4
-rw-r--r--src/lib/transcode_job.cc33
-rw-r--r--src/lib/transcode_job.h11
-rw-r--r--src/lib/transcoder.cc120
-rw-r--r--src/lib/transcoder.h36
-rw-r--r--src/lib/types.cc56
-rw-r--r--src/lib/types.h118
-rw-r--r--src/lib/ui_signaller.h4
-rw-r--r--src/lib/util.cc373
-rw-r--r--src/lib/util.h174
-rw-r--r--src/lib/version.h6
-rw-r--r--src/lib/video_content.cc224
-rw-r--r--src/lib/video_content.h95
-rw-r--r--src/lib/video_decoder.cc60
-rw-r--r--src/lib/video_decoder.h68
-rw-r--r--src/lib/video_examiner.h30
-rw-r--r--src/lib/video_sink.h52
-rw-r--r--src/lib/video_source.h72
-rw-r--r--src/lib/writer.cc68
-rw-r--r--src/lib/writer.h6
-rw-r--r--src/lib/wscript44
-rw-r--r--src/tools/dcpomatic.cc (renamed from src/tools/dvdomatic.cc)96
-rw-r--r--src/tools/dcpomatic_batch.cc (renamed from src/tools/dvdomatic_batch.cc)21
-rw-r--r--src/tools/dcpomatic_cli.cc (renamed from src/tools/makedcp.cc)31
-rw-r--r--src/tools/dcpomatic_server.cc (renamed from src/tools/servomatic_gui.cc)6
-rw-r--r--src/tools/dcpomatic_server_cli.cc (renamed from src/tools/servomatic_cli.cc)4
-rw-r--r--src/tools/po/es_ES.po14
-rw-r--r--src/tools/po/fr_FR.po2
-rw-r--r--src/tools/po/sv_SE.po12
-rw-r--r--src/tools/servomatictest.cc19
-rw-r--r--src/tools/wscript18
-rw-r--r--src/wx/about_dialog.cc18
-rw-r--r--src/wx/audio_dialog.cc73
-rw-r--r--src/wx/audio_dialog.h10
-rw-r--r--src/wx/audio_mapping_view.cc174
-rw-r--r--src/wx/audio_mapping_view.h (renamed from src/lib/trimmer.h)26
-rw-r--r--src/wx/audio_plot.cc11
-rw-r--r--src/wx/config_dialog.cc121
-rw-r--r--src/wx/config_dialog.h16
-rw-r--r--src/wx/dci_metadata_dialog.cc2
-rw-r--r--src/wx/film_editor.cc1440
-rw-r--r--src/wx/film_editor.h139
-rw-r--r--src/wx/film_viewer.cc190
-rw-r--r--src/wx/film_viewer.h35
-rw-r--r--src/wx/gain_calculator_dialog.cc2
-rw-r--r--src/wx/imagemagick_content_dialog.cc70
-rw-r--r--src/wx/imagemagick_content_dialog.h (renamed from src/lib/audio_sink.h)27
-rw-r--r--src/wx/job_manager_view.cc23
-rw-r--r--src/wx/job_manager_view.h1
-rw-r--r--src/wx/new_film_dialog.cc6
-rw-r--r--src/wx/new_film_dialog.h2
-rw-r--r--src/wx/po/es_ES.po20
-rw-r--r--src/wx/po/fr_FR.po16
-rw-r--r--src/wx/po/it_IT.po21
-rw-r--r--src/wx/po/sv_SE.po23
-rw-r--r--src/wx/properties_dialog.cc21
-rw-r--r--src/wx/server_dialog.cc2
-rw-r--r--src/wx/timecode.cc115
-rw-r--r--src/wx/timecode.h (renamed from src/lib/delay_line.h)29
-rw-r--r--src/wx/timeline.cc561
-rw-r--r--src/wx/timeline.h86
-rw-r--r--src/wx/timeline_dialog.cc (renamed from src/lib/video_source.cc)38
-rw-r--r--src/wx/timeline_dialog.h (renamed from src/lib/gain.h)17
-rw-r--r--src/wx/wscript17
-rw-r--r--src/wx/wx_util.cc14
-rw-r--r--src/wx/wx_util.h12
167 files changed, 7800 insertions, 6684 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
deleted file mode 100644
index a204677db..000000000
--- a/src/lib/ab_transcode_job.cc
+++ /dev/null
@@ -1,72 +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"
-#include "log.h"
-
-#include "i18n.h"
-
-using std::string;
-using boost::shared_ptr;
-
-/** @param f Film to compare.
- * @param o Decode options.
- */
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o)
- : Job (f)
- , _decode_opt (o)
-{
- _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)));
- w.go ();
- set_progress (1);
- set_state (FINISHED_OK);
-
- _film->log()->log ("A/B transcode job completed successfully");
-
- } catch (std::exception& e) {
-
- set_progress (1);
- set_state (FINISHED_ERROR);
- _film->log()->log (String::compose ("A/B transcode job failed (%1)", e.what()));
- throw;
- }
-}
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
deleted file mode 100644
index 8e3cbe2d8..000000000
--- a/src/lib/ab_transcode_job.h
+++ /dev/null
@@ -1,53 +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"
-#include "options.h"
-
-class Film;
-
-/** @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,
- DecodeOptions o
- );
-
- std::string name () const;
- void run ();
-
-private:
- DecodeOptions _decode_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 c42f0d241..000000000
--- a/src/lib/ab_transcoder.cc
+++ /dev/null
@@ -1,142 +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"
-#include "trimmer.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;
-using boost::dynamic_pointer_cast;
-
-/** @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, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
- : _film_a (a)
- , _film_b (b)
- , _job (j)
- , _encoder (e)
- , _combiner (new Combiner (a->log()))
-{
- _da = decoder_factory (_film_a, o);
- _db = decoder_factory (_film_b, o);
-
- shared_ptr<AudioStream> st = _film_a->audio_stream();
- if (st && st->sample_rate()) {
- _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate()));
- }
- _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() / 1000.0f));
- _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
-
- int const sr = st ? st->sample_rate() : 0;
- int const trim_start = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_start() : 0;
- int const trim_end = _film_a->trim_type() == Film::ENCODE ? _film_a->trim_end() : 0;
- _trimmer.reset (new Trimmer (
- _film_a->log(), trim_start, trim_end, _film_a->length().get(),
- sr, _film_a->source_frame_rate(), _film_a->dcp_frame_rate()
- ));
-
- /* 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 ());
- if (_film_a->audio_stream ()) {
- _da.audio->set_audio_stream (_film_a->audio_stream ());
- }
-
- _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4));
- _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4));
-
- _combiner->connect_video (_delay_line);
- if (_matcher) {
- _delay_line->connect_video (_matcher);
- _matcher->connect_video (_trimmer);
- } else {
- _delay_line->connect_video (_trimmer);
- }
- _trimmer->connect_video (_encoder);
-
- _da.audio->connect_audio (_delay_line);
- if (_matcher) {
- _delay_line->connect_audio (_matcher);
- _matcher->connect_audio (_gain);
- } else {
- _delay_line->connect_audio (_gain);
- }
- _gain->connect_audio (_trimmer);
- _trimmer->connect_audio (_encoder);
-}
-
-void
-ABTranscoder::go ()
-{
- _encoder->process_begin ();
-
- bool done[3] = { false, false, false };
-
- while (1) {
- done[0] = _da.video->pass ();
- done[1] = _db.video->pass ();
-
- if (!done[2] && _da.audio && dynamic_pointer_cast<Decoder> (_da.audio) != dynamic_pointer_cast<Decoder> (_da.video)) {
- done[2] = _da.audio->pass ();
- } else {
- done[2] = true;
- }
-
- if (_job) {
- _da.video->set_progress (_job);
- }
-
- if (done[0] && done[1] && done[2]) {
- break;
- }
- }
-
- _delay_line->process_end ();
- if (_matcher) {
- _matcher->process_end ();
- }
- _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 4f1b14e48..000000000
--- a/src/lib/ab_transcoder.h
+++ /dev/null
@@ -1,74 +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 Image;
-class Log;
-class Subtitle;
-class Film;
-class Matcher;
-class DelayLine;
-class Gain;
-class Combiner;
-class Trimmer;
-
-/** @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,
- 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<Trimmer> _trimmer;
- boost::shared_ptr<Image> _image;
-};
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
index 88cd65fee..2848c1ed7 100644
--- a/src/lib/analyse_audio_job.cc
+++ b/src/lib/analyse_audio_job.cc
@@ -21,9 +21,7 @@
#include "analyse_audio_job.h"
#include "compose.hpp"
#include "film.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "audio_decoder.h"
+#include "player.h"
#include "i18n.h"
@@ -35,8 +33,9 @@ using boost::shared_ptr;
int const AnalyseAudioJob::_num_points = 1024;
-AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioContent> c)
: Job (f)
+ , _content (c)
, _done (0)
, _samples_per_point (1)
{
@@ -46,45 +45,47 @@ AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
string
AnalyseAudioJob::name () const
{
- return String::compose (_("Analyse audio of %1"), _film->name());
+ shared_ptr<AudioContent> content = _content.lock ();
+ if (!content) {
+ return "";
+ }
+
+ return String::compose (_("Analyse audio of %1"), content->file().filename());
}
void
AnalyseAudioJob::run ()
{
- if (!_film->audio_stream () || !_film->length()) {
- set_progress (1);
- set_state (FINISHED_ERROR);
+ shared_ptr<AudioContent> content = _content.lock ();
+ if (!content) {
return;
}
-
- DecodeOptions options;
- options.decode_video = false;
- Decoders decoders = decoder_factory (_film, options);
- assert (decoders.audio);
+ shared_ptr<Playlist> playlist (new Playlist);
+ playlist->add (content);
+ shared_ptr<Player> player (new Player (_film, playlist));
+ player->disable_video ();
- decoders.audio->set_audio_stream (_film->audio_stream ());
- decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+ player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
- int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->source_frame_rate());
- _samples_per_point = max (int64_t (1), total_audio_frames / _num_points);
+ _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
- _current.resize (_film->audio_stream()->channels ());
- _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels()));
-
- while (!decoders.audio->pass()) {
- set_progress (float (_done) / total_audio_frames);
+ _current.resize (_film->dcp_audio_channels ());
+ _analysis.reset (new AudioAnalysis (_film->dcp_audio_channels ()));
+
+ _done = 0;
+ while (!player->pass ()) {
+ set_progress (double (_film->audio_frames_to_time (_done)) / _film->length ());
}
- _analysis->write (_film->audio_analysis_path ());
+ _analysis->write (content->audio_analysis_path ());
set_progress (1);
set_state (FINISHED_OK);
}
void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b)
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
{
for (int i = 0; i < b->frames(); ++i) {
for (int j = 0; j < b->channels(); ++j) {
@@ -100,7 +101,7 @@ AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b)
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 ();
}
}
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
index 5435e0a7c..3d4881983 100644
--- a/src/lib/analyse_audio_job.h
+++ b/src/lib/analyse_audio_job.h
@@ -19,21 +19,24 @@
#include "job.h"
#include "audio_analysis.h"
+#include "types.h"
class AudioBuffers;
+class AudioContent;
class AnalyseAudioJob : public Job
{
public:
- AnalyseAudioJob (boost::shared_ptr<Film> f);
+ AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
std::string name () const;
void run ();
private:
- void audio (boost::shared_ptr<const AudioBuffers>);
+ void audio (boost::shared_ptr<const AudioBuffers>, Time);
- int64_t _done;
+ boost::weak_ptr<AudioContent> _content;
+ OutputAudioFrame _done;
int64_t _samples_per_point;
std::vector<AudioPoint> _current;
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
index 9d708bbfd..e12516620 100644
--- a/src/lib/audio_analysis.cc
+++ b/src/lib/audio_analysis.cc
@@ -62,9 +62,9 @@ AudioAnalysis::AudioAnalysis (int channels)
_data.resize (channels);
}
-AudioAnalysis::AudioAnalysis (string filename)
+AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
{
- ifstream f (filename.c_str ());
+ ifstream f (filename.string().c_str ());
int channels;
f >> channels;
@@ -107,10 +107,10 @@ AudioAnalysis::points (int c) const
}
void
-AudioAnalysis::write (string filename)
+AudioAnalysis::write (boost::filesystem::path filename)
{
- string tmp = filename + ".tmp";
-
+ 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) {
diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h
index 6e0e2b78a..d57eba90a 100644
--- a/src/lib/audio_analysis.h
+++ b/src/lib/audio_analysis.h
@@ -17,12 +17,13 @@
*/
-#ifndef DVDOMATIC_AUDIO_ANALYSIS_H
-#define DVDOMATIC_AUDIO_ANALYSIS_H
+#ifndef DCPOMATIC_AUDIO_ANALYSIS_H
+#define DCPOMATIC_AUDIO_ANALYSIS_H
#include <iostream>
#include <vector>
#include <list>
+#include <boost/filesystem.hpp>
class AudioPoint
{
@@ -50,7 +51,7 @@ class AudioAnalysis
{
public:
AudioAnalysis (int c);
- AudioAnalysis (std::string);
+ AudioAnalysis (boost::filesystem::path);
void add_point (int c, AudioPoint const & p);
@@ -58,7 +59,7 @@ public:
int points (int c) const;
int channels () const;
- void write (std::string);
+ void write (boost::filesystem::path);
private:
std::vector<std::vector<AudioPoint> > _data;
diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc
new file mode 100644
index 000000000..403babaf7
--- /dev/null
+++ b/src/lib/audio_buffers.cc
@@ -0,0 +1,243 @@
+/*
+ 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 <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)
+ : _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 ();
+ }
+ }
+}
+
+/** 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 = 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 ();
+ }
+ memcpy (_data[i], other._data[i], _frames * sizeof (float));
+ }
+}
+
+/* XXX: it's a shame that this is a copy-and-paste of the above;
+ probably fixable with c++0x.
+*/
+AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> other)
+ : _channels (other->_channels)
+ , _frames (other->_frames)
+ , _allocated_frames (other->_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 ();
+ }
+ memcpy (_data[i], other->_data[i], _frames * sizeof (float));
+ }
+}
+
+/** AudioBuffers destructor */
+AudioBuffers::~AudioBuffers ()
+{
+ 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.
+ * @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);
+ _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;
+ }
+}
+
+/** 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) <= _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);
+
+ 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];
+ }
+ }
+}
+
diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h
new file mode 100644
index 000000000..47b8145a1
--- /dev/null
+++ b/src/lib/audio_buffers.h
@@ -0,0 +1,68 @@
+/*
+ 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 <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 ();
+
+ 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 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:
+ /** 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;
+};
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
new file mode 100644
index 000000000..e93f348f4
--- /dev/null
+++ b/src/lib/audio_content.cc
@@ -0,0 +1,122 @@
+/*
+ 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");
+}
+
+AudioContent::AudioContent (AudioContent const & o)
+ : Content (o)
+ , _audio_gain (o._audio_gain)
+ , _audio_delay (o._audio_delay)
+{
+
+}
+
+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 ()));
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
new file mode 100644
index 000000000..9bf53e0ab
--- /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>);
+ AudioContent (AudioContent const &);
+
+ void as_xml (xmlpp::Node *) 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 a54c14843..dc49a1846 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -18,19 +18,58 @@
*/
#include "audio_decoder.h"
-#include "stream.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+#include "log.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, DecodeOptions o)
- : Decoder (f, o)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> f)
+ : Decoder (f)
+ , _audio_position (0)
+{
+}
+
+#if 0
+void
+AudioDecoder::process_end ()
{
+ if (_swr_context) {
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ shared_ptr<AudioBuffers> out (new AudioBuffers (film->audio_mapping().dcp_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);
+ _writer->write (out);
+ }
+ }
}
+#endif
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 cfe94b528..ddfb296c9 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -21,38 +21,29 @@
* @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"
+
+class AudioBuffers;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
*/
-class AudioDecoder : public TimedAudioSource, public virtual Decoder
+class AudioDecoder : public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
- 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..4630f17c1
--- /dev/null
+++ b/src/lib/audio_mapping.cc
@@ -0,0 +1,116 @@
+/*
+ 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 ()
+{
+
+}
+
+/** Create a default AudioMapping for a given channel count.
+ * @param c Number of channels.
+ */
+AudioMapping::AudioMapping (int c)
+{
+ if (c == 1) {
+ /* Mono -> Centre */
+ add (0, libdcp::CENTRE);
+ } else {
+ /* 1:1 mapping */
+ for (int i = 0; i < c; ++i) {
+ add (i, static_cast<libdcp::Channel> (i));
+ }
+ }
+}
+
+AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node)
+{
+ 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<int>
+AudioMapping::content_channels () const
+{
+ list<int> c;
+ for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+ if (find (c.begin(), c.end(), i->first) == c.end ()) {
+ c.push_back (i->first);
+ }
+ }
+
+ return c;
+}
+
+list<libdcp::Channel>
+AudioMapping::content_to_dcp (int c) const
+{
+ 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
+{
+ 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..a2de8306b
--- /dev/null
+++ b/src/lib/audio_mapping.h
@@ -0,0 +1,58 @@
+/*
+ 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;
+}
+
+class AudioMapping
+{
+public:
+ AudioMapping ();
+ AudioMapping (int);
+ AudioMapping (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+
+ void add (int, libdcp::Channel);
+
+ std::list<int> dcp_to_content (libdcp::Channel) const;
+ std::list<std::pair<int, libdcp::Channel> > content_to_dcp () const {
+ return _content_to_dcp;
+ }
+
+ std::list<int> content_channels () const;
+ std::list<libdcp::Channel> content_to_dcp (int) const;
+
+private:
+ std::list<std::pair<int, libdcp::Channel> > _content_to_dcp;
+};
+
+#endif
diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc
deleted file mode 100644
index d77e89367..000000000
--- a/src/lib/audio_source.cc
+++ /dev/null
@@ -1,42 +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 "audio_source.h"
-#include "audio_sink.h"
-
-using boost::shared_ptr;
-using boost::bind;
-
-void
-AudioSource::connect_audio (shared_ptr<AudioSink> s)
-{
- Audio.connect (bind (&AudioSink::process_audio, s, _1));
-}
-
-void
-TimedAudioSource::connect_audio (shared_ptr<TimedAudioSink> s)
-{
- Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2));
-}
-
-void
-TimedAudioSource::connect_audio (shared_ptr<AudioSink> s)
-{
- Audio.connect (bind (&AudioSink::process_audio, s, _1));
-}
diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h
deleted file mode 100644
index c13f1636b..000000000
--- a/src/lib/audio_source.h
+++ /dev/null
@@ -1,55 +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/audio_source.h
- * @brief Parent class for classes which emit audio data.
- */
-
-#ifndef DVDOMATIC_AUDIO_SOURCE_H
-#define DVDOMATIC_AUDIO_SOURCE_H
-
-#include <boost/signals2.hpp>
-
-class AudioBuffers;
-class AudioSink;
-class TimedAudioSink;
-
-/** A class that emits audio data */
-class AudioSource
-{
-public:
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>)> Audio;
-
- void connect_audio (boost::shared_ptr<AudioSink>);
-};
-
-
-/** A class that emits audio data with timestamps */
-class TimedAudioSource
-{
-public:
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, double)> Audio;
-
- void connect_audio (boost::shared_ptr<AudioSink>);
- void connect_audio (boost::shared_ptr<TimedAudioSink>);
-};
-
-#endif
diff --git a/src/lib/combiner.h b/src/lib/combiner.h
deleted file mode 100644
index 7ed316e26..000000000
--- a/src/lib/combiner.h
+++ /dev/null
@@ -1,42 +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/combiner.h
- * @brief Class for combining two video streams.
- */
-
-#include "processor.h"
-
-/** @class Combiner
- * @brief A class which can combine two video streams into one, with
- * one image used for the left half of the screen and the other for
- * the right.
- */
-class Combiner : public TimedVideoProcessor
-{
-public:
- Combiner (boost::shared_ptr<Log> log);
-
- void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
- void process_video_b (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
-
-private:
- /** The image that we are currently working on */
- boost::shared_ptr<Image> _image;
-};
diff --git a/src/lib/config.cc b/src/lib/config.cc
index d2d7fa2fd..e0fbcc703 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -22,11 +22,12 @@
#include <fstream>
#include <glib.h>
#include <boost/filesystem.hpp>
+#include <libcxml/cxml.h>
#include "config.h"
#include "server.h"
#include "scaler.h"
#include "filter.h"
-#include "format.h"
+#include "ratio.h"
#include "dcp_content_type.h"
#include "sound_processor.h"
@@ -36,8 +37,11 @@ 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;
@@ -45,10 +49,10 @@ Config* Config::_instance = 0;
Config::Config ()
: _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency()))
, _server_port (6192)
- , _reference_scaler (Scaler::from_id (N_("bicubic")))
, _tms_path (N_("."))
, _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
- , _default_format (0)
+ , _default_still_length (10)
+ , _default_container (Ratio::from_id ("185"))
, _default_dcp_content_type (0)
{
_allowed_dcp_frame_rates.push_back (24);
@@ -57,8 +61,61 @@ Config::Config ()
_allowed_dcp_frame_rates.push_back (48);
_allowed_dcp_frame_rates.push_back (50);
_allowed_dcp_frame_rates.push_back (60);
+}
+
+void
+Config::read ()
+{
+ if (!boost::filesystem::exists (file (false))) {
+ read_old_metadata ();
+ return;
+ }
+
+ cxml::File f (file (false), "Config");
+ 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");
- ifstream f (file().c_str ());
+ 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 (new 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);
+}
+
+void
+Config::read_old_metadata ()
+{
+ ifstream f (file(true).c_str ());
string line;
while (getline (f, line)) {
if (line.empty ()) {
@@ -83,10 +140,6 @@ Config::Config ()
_default_directory = v;
} else if (k == N_("server_port")) {
_server_port = atoi (v.c_str ());
- } else if (k == N_("reference_scaler")) {
- _reference_scaler = Scaler::from_id (v);
- } else if (k == N_("reference_filter")) {
- _reference_filters.push_back (Filter::from_id (v));
} else if (k == N_("server")) {
_servers.push_back (ServerDescription::create_from_metadata (v));
} else if (k == N_("tms_ip")) {
@@ -101,8 +154,8 @@ Config::Config ()
_sound_processor = SoundProcessor::from_id (v);
} else if (k == "language") {
_language = v;
- } else if (k == "default_format") {
- _default_format = Format::from_metadata (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") {
@@ -113,19 +166,23 @@ Config::Config ()
_dcp_metadata.issue_date = v;
}
- _default_dci_metadata.read (k, v);
+ _default_dci_metadata.read_old_metadata (k, v);
}
}
/** @return Filename to write configuration to */
string
-Config::file () const
+Config::file (bool old) const
{
boost::filesystem::path p;
p /= g_get_user_config_dir ();
boost::system::error_code ec;
boost::filesystem::create_directory (p, ec);
- p /= N_(".dvdomatic");
+ if (old) {
+ p /= ".dvdomatic";
+ } else {
+ p /= ".dcpomatic.xml";
+ }
return p.string ();
}
@@ -135,6 +192,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;
@@ -144,44 +208,41 @@ Config::instance ()
void
Config::write () const
{
- ofstream f (file().c_str ());
- f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
- << "default_directory " << _default_directory << "\n"
- << "server_port " << _server_port << "\n";
-
- if (_reference_scaler) {
- f << "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";
+ (*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";
+ 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) {
- f << "sound_processor " << _sound_processor->id () << "\n";
+ root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
}
if (_language) {
- f << "language " << _language.get() << "\n";
+ root->add_child("Language")->add_child_text (_language.get());
}
- if (_default_format) {
- f << "default_format " << _default_format->as_metadata() << "\n";
+ if (_default_container) {
+ root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
}
if (_default_dcp_content_type) {
- f << "default_dcp_content_type " << _default_dcp_content_type->dci_name() << "\n";
+ root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
}
- f << "dcp_metadata_issuer " << _dcp_metadata.issuer << "\n";
- f << "dcp_metadata_creator " << _dcp_metadata.creator << "\n";
- f << "dcp_metadata_issue_date " << _dcp_metadata.issue_date << "\n";
+ root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
+ root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
+
+ _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+
+ root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
- _default_dci_metadata.write (f);
+ doc.write_to_file_formatted (file (false));
}
string
diff --git a/src/lib/config.h b/src/lib/config.h
index a59cdcae0..f3e8f1441 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -21,8 +21,8 @@
* @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>
@@ -34,8 +34,8 @@ class ServerDescription;
class Scaler;
class Filter;
class SoundProcessor;
-class Format;
class DCPContentType;
+class Ratio;
/** @class Config
* @brief A singleton class holding configuration.
@@ -65,14 +65,6 @@ public:
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;
@@ -110,8 +102,12 @@ public:
return _language;
}
- Format const * default_format () const {
- return _default_format;
+ int default_still_length () const {
+ return _default_still_length;
+ }
+
+ Ratio const * default_container () const {
+ return _default_container;
}
DCPContentType const * default_dcp_content_type () const {
@@ -185,8 +181,12 @@ public:
_language = boost::none;
}
- void set_default_format (Format const * f) {
- _default_format = f;
+ 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) {
@@ -204,7 +204,9 @@ public:
private:
Config ();
- std::string 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;
@@ -233,7 +235,8 @@ private:
/** Default DCI metadata for newly-created Films */
DCIMetadata _default_dci_metadata;
boost::optional<std::string> _language;
- Format const * _default_format;
+ int _default_still_length;
+ Ratio const * _default_container;
DCPContentType const * _default_dcp_content_type;
libdcp::XMLMetadata _dcp_metadata;
diff --git a/src/lib/content.cc b/src/lib/content.cc
new file mode 100644
index 000000000..6a33e9f7e
--- /dev/null
+++ b/src/lib/content.cc
@@ -0,0 +1,98 @@
+/*
+ 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"
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const ContentProperty::START = 400;
+int const ContentProperty::LENGTH = 401;
+
+Content::Content (shared_ptr<const Film> f, Time s)
+ : _film (f)
+ , _start (s)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
+ : _film (f)
+ , _file (p)
+ , _start (0)
+{
+
+}
+
+Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : _film (f)
+{
+ _file = node->string_child ("File");
+ _digest = node->string_child ("Digest");
+ _start = node->number_child<Time> ("Start");
+}
+
+Content::Content (Content const & o)
+ : boost::enable_shared_from_this<Content> (o)
+ , _film (o._film)
+ , _file (o._file)
+ , _digest (o._digest)
+ , _start (o._start)
+{
+
+}
+
+void
+Content::as_xml (xmlpp::Node* node) const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ node->add_child("File")->add_child_text (_file.string());
+ node->add_child("Digest")->add_child_text (_digest);
+ node->add_child("Start")->add_child_text (lexical_cast<string> (_start));
+}
+
+void
+Content::examine (shared_ptr<Job>)
+{
+ string const d = md5_digest (_file);
+ boost::mutex::scoped_lock lm (_mutex);
+ _digest = d;
+}
+
+void
+Content::signal_changed (int p)
+{
+ Changed (shared_from_this (), p);
+}
+
+void
+Content::set_start (Time s)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _start = s;
+ }
+
+ signal_changed (ContentProperty::START);
+}
diff --git a/src/lib/content.h b/src/lib/content.h
new file mode 100644
index 000000000..e33f517ab
--- /dev/null
+++ b/src/lib/content.h
@@ -0,0 +1,94 @@
+/*
+ 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 <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 START;
+ static int const LENGTH;
+};
+
+class Content : public boost::enable_shared_from_this<Content>
+{
+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>);
+ Content (Content const &);
+
+ virtual void examine (boost::shared_ptr<Job>);
+ virtual std::string summary () const = 0;
+ virtual std::string information () const = 0;
+ virtual void as_xml (xmlpp::Node *) const;
+ virtual boost::shared_ptr<Content> clone () const = 0;
+ virtual Time length () const = 0;
+
+ boost::filesystem::path file () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _file;
+ }
+
+ std::string digest () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _digest;
+ }
+
+ void set_start (Time);
+
+ Time start () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _start;
+ }
+
+ Time end () const {
+ return start() + length();
+ }
+
+ boost::signals2::signal<void (boost::weak_ptr<Content>, int)> Changed;
+
+protected:
+ void signal_changed (int);
+
+ boost::weak_ptr<const Film> _film;
+ mutable boost::mutex _mutex;
+
+private:
+ boost::filesystem::path _file;
+ std::string _digest;
+ Time _start;
+};
+
+#endif
diff --git a/src/lib/cross.cc b/src/lib/cross.cc
index 124697fb4..ee0ef89b2 100644
--- a/src/lib/cross.cc
+++ b/src/lib/cross.cc
@@ -22,16 +22,16 @@
#include "cross.h"
#include "compose.hpp"
#include "log.h"
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
#include <unistd.h>
#include <mntent.h>
#endif
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
#include <windows.h>
#undef DATADIR
#include <shlwapi.h>
#endif
-#ifdef DVDOMATIC_OSX
+#ifdef DCPOMATIC_OSX
#include <sys/sysctl.h>
#endif
@@ -43,12 +43,12 @@ 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
}
@@ -60,7 +60,7 @@ cpu_info ()
pair<string, int> info;
info.second = 0;
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
ifstream f ("/proc/cpuinfo");
while (f.good ()) {
string l;
@@ -76,7 +76,7 @@ cpu_info ()
}
#endif
-#ifdef DVDOMATIC_OSX
+#ifdef DCPOMATIC_OSX
size_t N = sizeof (info.second);
sysctlbyname ("hw.ncpu", &info.second, &N, 0, 0);
char buffer[64];
@@ -92,7 +92,7 @@ cpu_info ()
void
run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
{
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
SECURITY_ATTRIBUTES security;
security.nLength = sizeof (security);
security.bInheritHandle = TRUE;
@@ -167,7 +167,7 @@ mount_info ()
{
list<pair<string, string> > m;
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
FILE* f = setmntent ("/etc/mtab", "r");
if (!f) {
return m;
diff --git a/src/lib/cross.h b/src/lib/cross.h
index d9cc2d12f..a00fee679 100644
--- a/src/lib/cross.h
+++ b/src/lib/cross.h
@@ -17,16 +17,15 @@
*/
-#include <string>
#include <boost/filesystem.hpp>
-class Log;
-
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
#define WEXITSTATUS(w) (w)
#endif
-extern void dvdomatic_sleep (int);
+class Log;
+
+void dcpomatic_sleep (int);
extern std::pair<std::string, int> 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
index 758886db4..f25b3ddb0 100644
--- a/src/lib/dci_metadata.cc
+++ b/src/lib/dci_metadata.cc
@@ -18,26 +18,39 @@
*/
#include <iostream>
+#include <libcxml/cxml.h>
#include "dci_metadata.h"
#include "i18n.h"
-using namespace std;
+using std::string;
+using boost::shared_ptr;
+
+DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+{
+ 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::write (ostream& f) const
+DCIMetadata::as_xml (xmlpp::Node* root) const
{
- f << N_("audio_language ") << audio_language << N_("\n");
- f << N_("subtitle_language ") << subtitle_language << N_("\n");
- f << N_("territory ") << territory << N_("\n");
- f << N_("rating ") << rating << N_("\n");
- f << N_("studio ") << studio << N_("\n");
- f << N_("facility ") << facility << N_("\n");
- f << N_("package_type ") << package_type << N_("\n");
+ 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 (string k, string v)
+DCIMetadata::read_old_metadata (string k, string v)
{
if (k == N_("audio_language")) {
audio_language = v;
diff --git a/src/lib/dci_metadata.h b/src/lib/dci_metadata.h
index eecdc7655..b87609ed0 100644
--- a/src/lib/dci_metadata.h
+++ b/src/lib/dci_metadata.h
@@ -17,16 +17,24 @@
*/
-#ifndef DVDOMATIC_DCI_METADATA_H
-#define DVDOMATIC_DCI_METADATA_H
+#ifndef DCPOMATIC_DCI_METADATA_H
+#define DCPOMATIC_DCI_METADATA_H
#include <string>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+ class Node;
+}
class DCIMetadata
{
public:
- void read (std::string, std::string);
- void write (std::ostream &) const;
+ DCIMetadata () {}
+ DCIMetadata (boost::shared_ptr<const cxml::Node>);
+
+ void as_xml (xmlpp::Node *) const;
+ void read_old_metadata (std::string, std::string);
std::string audio_language;
std::string subtitle_language;
diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h
index 960bb0129..14204bd72 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.)
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index 77b81a658..2f597522c 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -47,14 +47,12 @@
#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"
@@ -67,35 +65,21 @@ using libdcp::Size;
/** 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 DCP.
- * @param fps Frames per second of the Film's source.
- * @param pp FFmpeg post-processing string to use.
* @param clut Colour look-up table to use (see Config::colour_lut_index ())
* @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, int f, int dcp_fps, string pp, int clut, int bw, shared_ptr<Log> l
+ shared_ptr<const Image> image, int f, int dcp_fps, int clut, 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_fps)
- , _post_process (pp)
, _colour_lut (clut)
, _j2k_bandwidth (bw)
, _log (l)
- , _image (0)
+ , _opj_image (0)
, _parameters (0)
, _cinfo (0)
, _cio (0)
@@ -110,8 +94,8 @@ 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].w = _image->size().width;
+ _cmptparm[i].h = _image->size().height;
_cmptparm[i].x0 = 0;
_cmptparm[i].y0 = 0;
_cmptparm[i].prec = 12;
@@ -119,21 +103,21 @@ DCPVideoFrame::create_openjpeg_container ()
_cmptparm[i].sgnd = 0;
}
- _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
- if (_image == 0) {
+ _opj_image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
+ if (_opj_image == 0) {
throw EncodeError (N_("could not create libopenjpeg image"));
}
- _image->x0 = 0;
- _image->y0 = 0;
- _image->x1 = _out_size.width;
- _image->y1 = _out_size.height;
+ _opj_image->x0 = 0;
+ _opj_image->y0 = 0;
+ _opj_image->x1 = _image->size().width;
+ _opj_image->y1 = _image->size().height;
}
DCPVideoFrame::~DCPVideoFrame ()
{
- if (_image) {
- opj_image_destroy (_image);
+ if (_opj_image) {
+ opj_image_destroy (_opj_image);
}
if (_cio) {
@@ -157,23 +141,6 @@ 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) {
- dvdomatic::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());
- }
-
create_openjpeg_container ();
struct {
@@ -187,9 +154,9 @@ DCPVideoFrame::encode_locally ()
/* 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) {
+ for (int y = 0; y < _image->size().height; ++y) {
+ uint8_t* p = _image->data()[0] + y * _image->stride()[0];
+ for (int x = 0; x < _image->size().width; ++x) {
/* In gamma LUT (converting 8-bit input to 12-bit) */
s.r = lut_in[_colour_lut][*p++ << 4];
@@ -215,9 +182,9 @@ DCPVideoFrame::encode_locally ()
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];
+ _opj_image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x];
+ _opj_image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y];
+ _opj_image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z];
++jn;
}
@@ -267,7 +234,7 @@ DCPVideoFrame::encode_locally ()
_parameters->tcp_numlayers++;
_parameters->cp_disto_alloc = 1;
_parameters->cp_rsiz = CINEMA2K;
- _parameters->cp_comment = strdup (N_("DVD-o-matic"));
+ _parameters->cp_comment = strdup (N_("DCP-o-matic"));
_parameters->cp_cinema = CINEMA2K_24;
/* 3 components, so use MCT */
@@ -275,7 +242,7 @@ DCPVideoFrame::encode_locally ()
/* 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);
+ _parameters->tcp_rates[0] = ((float) (3 * _opj_image->comps[0].w * _opj_image->comps[0].h * _opj_image->comps[0].prec)) / (max_cs_len * 8);
/* get a J2K compressor handle */
_cinfo = opj_create_compress (CODEC_J2K);
@@ -287,14 +254,14 @@ DCPVideoFrame::encode_locally ()
_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, _opj_image);
_cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0);
if (_cio == 0) {
throw EncodeError (N_("could not open JPEG2000 stream"));
}
- int const r = opj_encode (_cinfo, _cio, _image, 0);
+ int const r = opj_encode (_cinfo, _cio, _opj_image, 0);
if (r == 0) {
throw EncodeError (N_("JPEG2000 encoding failed"));
}
@@ -322,46 +289,24 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
stringstream s;
s << N_("encode please\n")
- << N_("input_width ") << _input->size().width << N_("\n")
- << N_("input_height ") << _input->size().height << N_("\n")
- << N_("input_pixel_format ") << _input->pixel_format() << N_("\n")
- << N_("output_width ") << _out_size.width << N_("\n")
- << N_("output_height ") << _out_size.height << N_("\n")
- << N_("padding ") << _padding << N_("\n")
- << N_("subtitle_offset ") << _subtitle_offset << N_("\n")
- << N_("subtitle_scale ") << _subtitle_scale << N_("\n")
- << N_("scaler ") << _scaler->id () << N_("\n")
+ << N_("width ") << _image->size().width << N_("\n")
+ << N_("height ") << _image->size().height << N_("\n")
<< N_("frame ") << _frame << N_("\n")
- << N_("frames_per_second ") << _frames_per_second << N_("\n");
-
- if (!_post_process.empty()) {
- s << N_("post_process ") << _post_process << N_("\n");
- }
-
- s << N_("colour_lut ") << _colour_lut << N_("\n")
+ << N_("frames_per_second ") << _frames_per_second << N_("\n")
+ << N_("colour_lut ") << _colour_lut << N_("\n")
<< N_("j2k_bandwidth ") << _j2k_bandwidth << N_("\n");
- if (_subtitle) {
- s << N_("subtitle_x ") << _subtitle->position().x << N_("\n")
- << N_("subtitle_y ") << _subtitle->position().y << N_("\n")
- << N_("subtitle_width ") << _subtitle->image()->size().width << N_("\n")
- << N_("subtitle_height ") << _subtitle->image()->size().height << N_("\n");
- }
-
_log->log (String::compose (
N_("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]
+ _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 (s.str().length() + 1);
socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1);
- _input->write_to_socket (socket);
- if (_subtitle) {
- _subtitle->image()->write_to_socket (socket);
- }
+ _image->write_to_socket (socket);
shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
socket->read (e->data(), e->size());
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 4ceb07d26..f234b445a 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -105,12 +105,8 @@ public:
class DCPVideoFrame
{
public:
- DCPVideoFrame (
- boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size,
- int, int, float, Scaler const *, int, int, std::string, int, int, boost::shared_ptr<Log>
- );
-
- virtual ~DCPVideoFrame ();
+ DCPVideoFrame (boost::shared_ptr<const Image>, int, int, int, int, boost::shared_ptr<Log>);
+ ~DCPVideoFrame ();
boost::shared_ptr<EncodedData> encode_locally ();
boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
@@ -122,23 +118,16 @@ public:
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
- libdcp::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
+ boost::shared_ptr<const Image> _image;
int _frame; ///< frame index within the DCP's intrinsic duration
int _frames_per_second; ///< Frames per second that we will use for the DCP
- 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
boost::shared_ptr<Log> _log; ///< log
opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t
- opj_image* _image; ///< libopenjpeg's image container
+ opj_image* _opj_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
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 52b22fa06..637e0ddb2 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -21,55 +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 "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"
#include "i18n.h"
-using std::string;
-using std::stringstream;
-using std::min;
-using std::pair;
-using std::list;
using boost::shared_ptr;
-using boost::optional;
/** @param f Film.
* @param o Decode options.
*/
-Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o)
+Decoder::Decoder (shared_ptr<const Film> f)
: _film (f)
- , _opt (o)
{
_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 (N_("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 (N_("decoder does not support seek"));
-}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index 2bc462c33..cfca6867f 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,20 +21,15 @@
* @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 "options.h"
class Image;
class Log;
@@ -45,34 +40,30 @@ class FilterGraph;
/** @class Decoder.
* @brief Parent class for decoders of content.
- *
- * These classes can be instructed run through their content (by
- * calling ::go), and they emit signals when video or audio data is
- * ready for something else to process.
*/
class Decoder
{
public:
- Decoder (boost::shared_ptr<Film>, DecodeOptions);
+ Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- virtual bool pass () = 0;
- virtual bool seek (double);
- virtual bool seek_to_last ();
- virtual void seek_back () {}
- virtual void seek_forward () {}
+ /** Perform one decode pass of the content, which may or may not
+ * cause the object to emit some data.
+ */
+ virtual void pass () = 0;
- boost::signals2::signal<void()> OutputChanged;
+ virtual bool done () const = 0;
protected:
- /** our Film */
- boost::shared_ptr<Film> _film;
- /** our decode options */
- DecodeOptions _opt;
+
+ /** The Film that we are decoding in */
+ boost::weak_ptr<const Film> _film;
private:
+ /** This will be called when our Film emits Changed */
virtual void film_changed (Film::Property) {}
-
+
+ /** Connection to our Film */
boost::signals2::scoped_connection _film_connection;
};
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
deleted file mode 100644
index f7f9f4074..000000000
--- a/src/lib/decoder_factory.cc
+++ /dev/null
@@ -1,60 +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 "sndfile_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, DecodeOptions o
- )
-{
- if (f->content().empty()) {
- return Decoders ();
- }
-
- 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)),
- shared_ptr<AudioDecoder> (new SndfileDecoder (f, o))
- );
- }
-
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o));
- if (f->use_content_audio()) {
- return Decoders (fd, fd);
- }
-
- return Decoders (fd, shared_ptr<AudioDecoder> (new SndfileDecoder (f, o)));
-}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
deleted file mode 100644
index 8076b01c7..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.
- */
-
-#include "options.h"
-
-class Film;
-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>, DecodeOptions
- );
-
-#endif
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
deleted file mode 100644
index f6af6f9b1..000000000
--- a/src/lib/delay_line.cc
+++ /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.
-
-*/
-
-#include <stdint.h>
-#include <cstring>
-#include <algorithm>
-#include <iostream>
-#include "delay_line.h"
-#include "util.h"
-
-using std::min;
-using boost::shared_ptr;
-
-/* @param seconds Delay in seconds, +ve to move audio later.
- */
-DelayLine::DelayLine (shared_ptr<Log> log, double seconds)
- : TimedAudioVideoProcessor (log)
- , _seconds (seconds)
-{
-
-}
-
-void
-DelayLine::process_audio (shared_ptr<const AudioBuffers> data, double t)
-{
- if (_seconds > 0) {
- t += _seconds;
- }
-
- Audio (data, t);
-}
-
-void
-DelayLine::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
-{
- if (_seconds < 0) {
- t -= _seconds;
- }
-
- Video (image, same, sub, t);
-}
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 0ac32d3bf..d3181acd9 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -22,20 +22,13 @@
*/
#include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
-#include <libdcp/picture_asset.h>
#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 "format.h"
#include "cross.h"
#include "writer.h"
@@ -49,18 +42,16 @@ 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 */
-Encoder::Encoder (shared_ptr<Film> f)
+Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<Job> j)
: _film (f)
- , _video_frames_in (0)
+ , _job (j)
, _video_frames_out (0)
-#ifdef HAVE_SWRESAMPLE
- , _swr_context (0)
-#endif
, _have_a_real_frame (false)
, _terminate (false)
{
@@ -78,35 +69,6 @@ Encoder::~Encoder ()
void
Encoder::process_begin ()
{
- if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
-#ifdef HAVE_SWRESAMPLE
-
- stringstream s;
- s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_stream()->sample_rate(), _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) {
_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0)));
}
@@ -119,51 +81,13 @@ Encoder::process_begin ()
}
}
- _writer.reset (new Writer (_film));
+ _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
-
- if (_film->audio_channels() == 0 && _film->minimum_audio_channels() > 0) {
- /* Put audio in where there is none at all */
- int64_t af = video_frames_to_audio_frames (_video_frames_out, 48000, _film->dcp_frame_rate ());
- while (af) {
- int64_t const this_time = min (af, static_cast<int64_t> (24000));
- shared_ptr<AudioBuffers> out (new AudioBuffers (_film->minimum_audio_channels(), this_time));
- out->make_silent ();
- out->set_frames (this_time);
- write_audio (out);
-
- af -= this_time;
- }
- }
-
boost::mutex::scoped_lock lock (_mutex);
_film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ()));
@@ -208,7 +132,7 @@ Encoder::process_end ()
* or 0 if not known.
*/
float
-Encoder::current_frames_per_second () const
+Encoder::current_encoding_rate () const
{
boost::mutex::scoped_lock lock (_history_mutex);
if (int (_time_history.size()) < _history_size) {
@@ -246,15 +170,8 @@ Encoder::frame_done ()
}
void
-Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub)
+Encoder::process_video (shared_ptr<const Image> image, bool same)
{
- FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
-
- if (frc.skip && (_video_frames_in % 2)) {
- ++_video_frames_in;
- return;
- }
-
boost::mutex::scoped_lock lock (_mutex);
/* Wait until the queue has gone down a bit */
@@ -282,15 +199,12 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_
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> (
+ /* XXX: padding */
+ _queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
- _film->subtitle_offset(), _film->subtitle_scale(),
- _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second,
- _film->colour_lut(), _film->j2k_bandwidth(),
- _film->log()
+ image, _video_frames_out, _film->dcp_video_frame_rate(),
+ _film->colour_lut(), _film->j2k_bandwidth(), _film->log()
)
));
@@ -298,49 +212,13 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, boost::shared_
_have_a_real_frame = true;
}
- ++_video_frames_in;
++_video_frames_out;
-
- if (frc.repeat) {
- _writer->repeat (_video_frames_out);
- ++_video_frames_out;
- frame_done ();
- }
}
void
Encoder::process_audio (shared_ptr<const AudioBuffers> data)
{
- if (!data->frames ()) {
- return;
- }
-
-#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;
- }
-#endif
-
- write_audio (data);
+ _writer->write (data);
}
void
@@ -383,7 +261,7 @@ Encoder::encoder_thread (ServerDescription* server)
}
TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
- boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+ shared_ptr<DCPVideoFrame> vf = _queue.front ();
_film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 from queue"), boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
_queue.pop_front ();
@@ -437,34 +315,10 @@ Encoder::encoder_thread (ServerDescription* server)
}
if (remote_backoff > 0) {
- dvdomatic_sleep (remote_backoff);
+ dcpomatic_sleep (remote_backoff);
}
lock.lock ();
_condition.notify_all ();
}
}
-
-void
-Encoder::write_audio (shared_ptr<const AudioBuffers> data)
-{
- AudioMapping m (_film);
- if (m.dcp_channels() != _film->audio_channels()) {
-
- /* Remap and pad with silence */
-
- shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ()));
- for (int i = 0; i < m.dcp_channels(); ++i) {
- optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i));
- if (!s) {
- b->make_silent (i);
- } else {
- memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float));
- }
- }
-
- data = b;
- }
-
- _writer->write (data);
-}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index 70e81a7e0..b5a641f50 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -17,8 +17,8 @@
*/
-#ifndef DVDOMATIC_ENCODER_H
-#define DVDOMATIC_ENCODER_H
+#ifndef DCPOMATIC_ENCODER_H
+#define DCPOMATIC_ENCODER_H
/** @file src/encoder.h
* @brief Encoder to J2K and WAV for DCP.
@@ -33,68 +33,60 @@
#include <stdint.h>
extern "C" {
#include <libavutil/samplefmt.h>
-}
-#ifdef HAVE_SWRESAMPLE
-extern "C" {
#include <libswresample/swresample.h>
}
-#endif
#include "util.h"
-#include "video_sink.h"
-#include "audio_sink.h"
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:
- Encoder (boost::shared_ptr<Film> f);
+ 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<const Image> i, bool same, boost::shared_ptr<Subtitle> s);
+ void process_video (boost::shared_ptr<const Image> i, bool same);
/** Call with some audio data */
void process_audio (boost::shared_ptr<const AudioBuffers>);
/** Called when a processing run has finished */
- virtual void process_end ();
+ void process_end ();
- float current_frames_per_second () const;
+ float current_encoding_rate () const;
int video_frames_out () const;
private:
void frame_done ();
- void write_audio (boost::shared_ptr<const AudioBuffers> data);
-
void encoder_thread (ServerDescription *);
void terminate_threads ();
/** Film that we are encoding */
- boost::shared_ptr<Film> _film;
+ boost::shared_ptr<const Film> _film;
+ boost::shared_ptr<Job> _job;
/** Mutex for _time_history and _last_frame */
mutable boost::mutex _history_mutex;
@@ -105,15 +97,9 @@ private:
/** Number of frames that we should keep history for */
static int const _history_size;
- /** Number of video frames received so far */
- SourceFrame _video_frames_in;
/** Number of video frames written for the DCP so far */
int _video_frames_out;
-#if HAVE_SWRESAMPLE
- SwrContext* _swr_context;
-#endif
-
bool _have_a_real_frame;
bool _terminate;
std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
index 4b30c9431..3cab9716d 100644
--- a/src/lib/examine_content_job.cc
+++ b/src/lib/examine_content_job.cc
@@ -17,29 +17,20 @@
*/
-/** @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 boost::shared_ptr;
-ExamineContentJob::ExamineContentJob (shared_ptr<Film> f)
+ExamineContentJob::ExamineContentJob (shared_ptr<const Film> f, shared_ptr<Content> c)
: Job (f)
+ , _content (c)
{
}
@@ -51,60 +42,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 ());
-
- DecodeOptions o;
- o.decode_audio = false;
-
- Decoders decoders = decoder_factory (_film, o);
-
- set_progress_unknown ();
- while (!decoders.video->pass()) {
- /* keep going */
- }
-
- _film->set_length (decoders.video->video_frame());
-
- _film->log()->log (String::compose (N_("Video length examined as %1 frames"), _film->length().get()));
-
- } else {
-
- /* Get a quick decoder to get the content's length from its header */
-
- Decoders d = decoder_factory (_film, DecodeOptions());
- _film->set_length (d.video->length());
-
- _film->log()->log (String::compose (N_("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 8ee4f0d60..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>);
+ 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.h b/src/lib/exceptions.h
index e45a62353..6bad7c924 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -17,8 +17,8 @@
*/
-#ifndef DVDOMATIC_EXCEPTIONS_H
-#define DVDOMATIC_EXCEPTIONS_H
+#ifndef DCPOMATIC_EXCEPTIONS_H
+#define DCPOMATIC_EXCEPTIONS_H
/** @file src/exceptions.h
* @brief Our exceptions.
@@ -112,6 +112,7 @@ class OpenFileError : public FileError
{
public:
/** @param f File that we were trying to open */
+ /* XXX: should be boost::filesystem::path */
OpenFileError (std::string f);
};
diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc
new file mode 100644
index 000000000..a39de391a
--- /dev/null
+++ b/src/lib/ffmpeg.cc
@@ -0,0 +1,147 @@
+/*
+ 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::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->file().string().c_str(), 0, 0) < 0) {
+ throw OpenFileError (_ffmpeg_content->file().string ());
+ }
+
+ if (avformat_find_stream_info (_format_context, 0) < 0) {
+ throw DecodeError (_("could not find stream information"));
+ }
+
+ /* Find video 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_content.cc b/src/lib/ffmpeg_content.cc
new file mode 100644
index 000000000..1135cc9a3
--- /dev/null
+++ b/src/lib/ffmpeg_content.cc
@@ -0,0 +1,376 @@
+/*
+ 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 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)
+{
+
+}
+
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , VideoContent (f, node)
+ , AudioContent (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<Time> ("FirstVideo");
+}
+
+FFmpegContent::FFmpegContent (FFmpegContent const & o)
+ : Content (o)
+ , VideoContent (o)
+ , AudioContent (o)
+ , _subtitle_streams (o._subtitle_streams)
+ , _subtitle_stream (o._subtitle_stream)
+ , _audio_streams (o._audio_streams)
+ , _audio_stream (o._audio_stream)
+{
+
+}
+
+void
+FFmpegContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("FFmpeg");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+ AudioContent::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
+{
+ return String::compose (_("Movie: %1"), file().filename().string());
+}
+
+string
+FFmpegContent::information () const
+{
+ if (video_length() == 0 || video_frame_rate() == 0) {
+ return "";
+ }
+
+ stringstream s;
+
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
+ s << VideoContent::information ();
+
+ return s.str ();
+}
+
+void
+FFmpegContent::set_subtitle_stream (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->dcp_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->dcp_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)
+{
+ 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");
+ mapping = AudioMapping (node->node_child ("Mapping"));
+ first_audio = node->optional_number_child<Time> ("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));
+ }
+ 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));
+}
+
+shared_ptr<Content>
+FFmpegContent::clone () const
+{
+ return shared_ptr<Content> (new FFmpegContent (*this));
+}
+
+Time
+FFmpegContent::length () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ());
+ return video_length() * frc.factor() * TIME_HZ / film->dcp_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);
+}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
new file mode 100644
index 000000000..c5ccee77a
--- /dev/null
+++ b/src/lib/ffmpeg_content.h
@@ -0,0 +1,153 @@
+/*
+ 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"
+
+class Filter;
+
+class FFmpegAudioStream
+{
+public:
+ FFmpegAudioStream (std::string n, int i, int f, int c)
+ : name (n)
+ , id (i)
+ , frame_rate (f)
+ , channels (c)
+ , mapping (c)
+ {}
+
+ 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;
+};
+
+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:
+ FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+ FFmpegContent (FFmpegContent const &);
+
+ 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 information () const;
+ void as_xml (xmlpp::Node *) const;
+ boost::shared_ptr<Content> clone () const;
+ Time length () 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<Time> first_video () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _first_video;
+ }
+
+private:
+ 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<Time> _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 c2143b949..bf0949130 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -28,19 +28,13 @@
#include <iostream>
#include <stdint.h>
#include <boost/lexical_cast.hpp>
+#include <sndfile.h>
extern "C" {
#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"
@@ -48,6 +42,7 @@ extern "C" {
#include "ffmpeg_decoder.h"
#include "filter_graph.h"
#include "subtitle.h"
+#include "audio_buffers.h"
#include "i18n.h"
@@ -56,182 +51,66 @@ using std::string;
using std::vector;
using std::stringstream;
using std::list;
+using std::min;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
using libdcp::Size;
-boost::mutex FFmpegDecoder::_mutex;
-
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , VideoDecoder (f, o)
- , AudioDecoder (f, o)
- , _format_context (0)
- , _video_stream (-1)
- , _frame (0)
- , _video_codec_context (0)
- , _video_codec (0)
- , _audio_codec_context (0)
- , _audio_codec (0)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
+ : Decoder (f)
+ , VideoDecoder (f)
+ , AudioDecoder (f)
+ , FFmpeg (c)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
+ , _decode_video (video)
+ , _decode_audio (audio)
+ , _pts_offset (0)
{
- setup_general ();
- setup_video ();
- setup_audio ();
setup_subtitle ();
-}
-
-FFmpegDecoder::~FFmpegDecoder ()
-{
- boost::mutex::scoped_lock lm (_mutex);
-
- if (_audio_codec_context) {
- avcodec_close (_audio_codec_context);
- }
-
- if (_video_codec_context) {
- avcodec_close (_video_codec_context);
- }
-
- if (_subtitle_codec_context) {
- avcodec_close (_subtitle_codec_context);
- }
-
- av_free (_frame);
-
- avformat_close_input (&_format_context);
-}
-
-void
-FFmpegDecoder::setup_general ()
-{
- av_register_all ();
-
- if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
- throw OpenFileError (_film->content_path ());
- }
-
- if (avformat_find_stream_info (_format_context, 0) < 0) {
- throw DecodeError (_("could not find stream information"));
- }
-
- /* Find video, audio and subtitle streams and choose the first of each */
-
- 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)
- )
- );
- }
- }
-
- 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"));
+ if (video && audio && c->audio_stream() && c->first_video() && c->audio_stream()->first_audio) {
+ _pts_offset = compute_pts_offset (c->first_video().get(), c->audio_stream()->first_audio.get(), c->video_frame_rate());
}
}
-void
-FFmpegDecoder::setup_video ()
+double
+FFmpegDecoder::compute_pts_offset (double first_video, double first_audio, float video_frame_rate)
{
- boost::mutex::scoped_lock lm (_mutex);
+ double const old_first_video = first_video;
- _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"));
+ /* Round the first video to a frame boundary */
+ if (fabs (rint (first_video * video_frame_rate) - first_video * video_frame_rate) > 1e-6) {
+ first_video = ceil (first_video * video_frame_rate) / video_frame_rate;
}
- if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
- throw DecodeError (N_("could not open video decoder"));
- }
+ /* Compute the required offset (also removing any common start delay) */
+ return first_video - old_first_video - min (first_video, first_audio);
}
-void
-FFmpegDecoder::setup_audio ()
+FFmpegDecoder::~FFmpegDecoder ()
{
boost::mutex::scoped_lock lm (_mutex);
-
- if (!_audio_stream) {
- return;
- }
-
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
- _audio_codec_context = _format_context->streams[ffa->id()]->codec;
- _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
- if (_audio_codec == 0) {
- throw DecodeError (_("could not find audio decoder"));
- }
-
- if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
- throw DecodeError (N_("could not open audio decoder"));
+ if (_subtitle_codec_context) {
+ avcodec_close (_subtitle_codec_context);
}
-}
+}
void
-FFmpegDecoder::setup_subtitle ()
-{
- boost::mutex::scoped_lock lm (_mutex);
-
- if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
- 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 (N_("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 (N_("error on av_read_frame (%1) (%2)"), buf, r));
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
}
/* Get any remaining frames */
@@ -241,41 +120,28 @@ FFmpegDecoder::pass ()
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
- int frame_finished;
-
- if (_opt.decode_video) {
- while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- filter_and_emit_video ();
- }
+ if (_decode_video) {
+ while (decode_video_packet ());
}
-
- if (_audio_stream && _opt.decode_audio) {
+
+ if (_ffmpeg_content->audio_stream() && _decode_audio) {
decode_audio_packet ();
}
-
- return true;
+
+ /* Stop us being asked for any more data */
+ _video_position = _ffmpeg_content->video_length ();
+ _audio_position = _ffmpeg_content->audio_length ();
+ return;
}
avcodec_get_frame_defaults (_frame);
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
- if (_packet.stream_index == _video_stream && _opt.decode_video) {
-
- 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 (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
- }
-
- filter_and_emit_video ();
- }
-
- } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
+ 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 (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) {
+ } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id) {
+#if 0
int got_subtitle;
AVSubtitle sub;
@@ -286,19 +152,19 @@ FFmpegDecoder::pass ()
if (sub.num_rects > 0) {
shared_ptr<TimedSubtitle> ts;
try {
- emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
+ 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> ());
+ subtitle (shared_ptr<TimedSubtitle> ());
}
avsubtitle_free (&sub);
}
+#endif
}
-
+
av_free_packet (&_packet);
- return false;
}
/** @param data pointer to array of pointers to buffers.
@@ -307,19 +173,16 @@ FFmpegDecoder::pass ()
shared_ptr<AudioBuffers>
FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
{
- assert (_film->audio_channels());
+ assert (_ffmpeg_content->audio_channels());
assert (bytes_per_audio_sample());
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
/* Deinterleave and convert to float */
- assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
+ assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
int const total_samples = size / bytes_per_audio_sample();
- int const frames = total_samples / _film->audio_channels();
- shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
+ int const frames = total_samples / _ffmpeg_content->audio_channels();
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
switch (audio_sample_format()) {
case AV_SAMPLE_FMT_S16:
@@ -331,7 +194,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
audio->data(channel)[sample] = float(*p++) / (1 << 15);
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -342,7 +205,7 @@ 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) {
+ for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
for (int j = 0; j < frames; ++j) {
audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
}
@@ -359,7 +222,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
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;
}
@@ -376,7 +239,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
audio->data(channel)[sample] = *p++;
++channel;
- if (channel == _film->audio_channels()) {
+ if (channel == _ffmpeg_content->audio_channels()) {
channel = 0;
++sample;
}
@@ -387,7 +250,7 @@ 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) {
+ for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
memcpy (audio->data(i), p[i], frames * sizeof(float));
}
}
@@ -400,89 +263,14 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
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;
-}
-
-libdcp::Size
-FFmpegDecoder::native_size () const
-{
- return libdcp::Size (_video_codec_context->width, _video_codec_context->height);
-}
-
-PixelFormat
-FFmpegDecoder::pixel_format () const
-{
- return _video_codec_context->pix_fmt;
-}
-
-int
-FFmpegDecoder::time_base_numerator () const
-{
- return _video_codec_context->time_base.num;
-}
-
-int
-FFmpegDecoder::time_base_denominator () const
-{
- return _video_codec_context->time_base.den;
-}
-
-int
-FFmpegDecoder::sample_aspect_ratio_numerator () const
-{
- return _video_codec_context->sample_aspect_ratio.num;
-}
-
-int
-FFmpegDecoder::sample_aspect_ratio_denominator () const
-{
- return _video_codec_context->sample_aspect_ratio.den;
-}
-
-string
-FFmpegDecoder::stream_name (AVStream* s) const
-{
- stringstream n;
-
- if (s->metadata) {
- AVDictionaryEntry const * lang = av_dict_get (s->metadata, N_("language"), 0, 0);
- if (lang) {
- n << lang->value;
- }
-
- AVDictionaryEntry const * title = av_dict_get (s->metadata, N_("title"), 0, 0);
- if (title) {
- if (!n.str().empty()) {
- n << N_(" ");
- }
- n << title->value;
- }
- }
-
- if (n.str().empty()) {
- n << N_("unknown");
- }
-
- return n.str ();
+ return audio_codec_context()->sample_fmt;
}
int
@@ -492,91 +280,29 @@ FFmpegDecoder::bytes_per_audio_sample () const
}
void
-FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
+FFmpegDecoder::seek (VideoContent::Frame frame)
{
- AudioDecoder::set_audio_stream (s);
- setup_audio ();
+ do_seek (frame, false, false);
}
void
-FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- VideoDecoder::set_subtitle_stream (s);
- setup_subtitle ();
- OutputChanged ();
-}
-
-void
-FFmpegDecoder::filter_and_emit_video ()
+FFmpegDecoder::seek_back ()
{
- int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
- if (bet == AV_NOPTS_VALUE) {
- _film->log()->log ("Dropping frame without PTS");
+ if (_video_position == 0) {
return;
}
- shared_ptr<FilterGraph> graph;
-
- {
- boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-
- list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
- ++i;
- }
-
- if (i == _filter_graphs.end ()) {
- graph = filter_graph_factory (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format);
- _filter_graphs.push_back (graph);
- _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
- } 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, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base));
- }
-}
-
-bool
-FFmpegDecoder::seek (double p)
-{
- return do_seek (p, false, false);
-}
-
-bool
-FFmpegDecoder::seek_to_last ()
-{
- /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
- (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
- staying in the same place.
- */
- return do_seek (last_source_time(), true, false);
-}
-
-void
-FFmpegDecoder::seek_back ()
-{
- do_seek (last_source_time() - 2.5 / frames_per_second (), true, true);
+ do_seek (_video_position - 1, true, true);
}
void
-FFmpegDecoder::seek_forward ()
-{
- do_seek (last_source_time() - 0.5 / frames_per_second(), true, true);
-}
-
-bool
-FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
+FFmpegDecoder::do_seek (VideoContent::Frame frame, bool backwards, bool accurate)
{
- int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
+ int64_t const vt = frame * _ffmpeg_content->video_frame_rate() / av_q2d (_format_context->streams[_video_stream]->time_base);
+ av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
+ _video_position = frame;
- int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
-
- avcodec_flush_buffers (_video_codec_context);
+ avcodec_flush_buffers (video_codec_context());
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
@@ -585,17 +311,19 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
while (1) {
int r = av_read_frame (_format_context, &_packet);
if (r < 0) {
- return true;
+ return;
}
avcodec_get_frame_defaults (_frame);
if (_packet.stream_index == _video_stream) {
int finished = 0;
- int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
+ int const r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
if (r >= 0 && finished) {
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet > vt) {
+ _video_position = (bet * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset)
+ * _ffmpeg_content->video_frame_rate();
break;
}
}
@@ -604,123 +332,156 @@ FFmpegDecoder::do_seek (double p, bool backwards, bool accurate)
av_free_packet (&_packet);
}
}
-
- return r < 0;
}
-shared_ptr<FFmpegAudioStream>
-FFmpegAudioStream::create (string t, optional<int> v)
+void
+FFmpegDecoder::decode_audio_packet ()
{
- if (!v) {
- /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
- return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
- }
+ /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
+ several times.
+ */
+
+ AVPacket copy_packet = _packet;
- stringstream s (t);
- string type;
- s >> type;
- if (type != N_("ffmpeg")) {
- return shared_ptr<FFmpegAudioStream> ();
- }
+ while (copy_packet.size > 0) {
- return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
+ int frame_finished;
+ int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+ if (decode_result >= 0) {
+ 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) - _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);
+ }
+ }
+
+ copy_packet.data += decode_result;
+ copy_packet.size -= decode_result;
+ }
+ }
+ }
}
-FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
+bool
+FFmpegDecoder::decode_video_packet ()
{
- stringstream n (t);
-
- int name_index = 4;
- if (!version) {
- name_index = 2;
- int channels;
- n >> _id >> channels;
- _channel_layout = av_get_default_channel_layout (channels);
- _sample_rate = 0;
- } else {
- string type;
- /* Current (marked version 1) */
- n >> type >> _id >> _sample_rate >> _channel_layout;
- assert (type == N_("ffmpeg"));
+ 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);
- for (int i = 0; i < name_index; ++i) {
- size_t const s = t.find (' ');
- if (s != string::npos) {
- t = t.substr (s + 1);
- }
+ shared_ptr<FilterGraph> graph;
+
+ list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
+ while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+ ++i;
}
- _name = t;
-}
+ if (i == _filter_graphs.end ()) {
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
-string
-FFmpegAudioStream::to_string () const
-{
- return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
-}
+ graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ _filter_graphs.push_back (graph);
-void
-FFmpegDecoder::film_changed (Film::Property p)
-{
- switch (p) {
- case Film::CROP:
- case Film::FILTERS:
- {
- boost::mutex::scoped_lock lm (_filter_graphs_mutex);
- _filter_graphs.clear ();
+ film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+ } else {
+ graph = *i;
}
- OutputChanged ();
- break;
- default:
- break;
+ list<shared_ptr<Image> > images = graph->process (_frame);
+
+ string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
+
+ for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
+
+ shared_ptr<Image> image = *i;
+ if (!post_process.empty ()) {
+ image = image->post_process (post_process, true);
+ }
+
+ int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+ if (bet != AV_NOPTS_VALUE) {
+
+ double const pts = bet * av_q2d (_format_context->streams[_video_stream]->time_base) - _pts_offset;
+ 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.
+ */
+ boost::shared_ptr<Image> black (
+ new SimpleImage (
+ 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;
+ }
+
+ 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");
+ }
}
-}
-/** @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();
+ return true;
}
+
void
-FFmpegDecoder::decode_audio_packet ()
+FFmpegDecoder::setup_subtitle ()
{
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- assert (ffa);
-
- /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
- several times.
- */
+ boost::mutex::scoped_lock lm (_mutex);
- AVPacket copy_packet = _packet;
+ if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
+ return;
+ }
- while (copy_packet.size > 0) {
+ _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
+ _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
- int frame_finished;
- int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &copy_packet);
- if (decode_result < 0) {
- /* error */
- break;
- }
-
- if (frame_finished) {
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
-
- 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, data_size), source_pts_seconds);
- }
-
- copy_packet.data += decode_result;
- copy_packet.size -= decode_result;
+ if (_subtitle_codec == 0) {
+ throw DecodeError (_("could not find subtitle decoder"));
+ }
+
+ 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;
+}
+
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 198f4294e..8f0482aad 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -35,119 +35,54 @@ 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 "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 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 FFmpeg
{
public:
- FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions);
+ FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
~FFmpegDecoder ();
- float frames_per_second () const;
- libdcp::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 seek_forward ();
+ void pass ();
+ void seek (VideoContent::Frame);
void seek_back ();
+ bool done () const;
private:
+ friend class ::ffmpeg_pts_offset_test;
/* No copy construction */
FFmpegDecoder (FFmpegDecoder const &);
+ FFmpegDecoder& operator= (FFmpegDecoder const &);
- bool pass ();
- bool do_seek (double p, bool, bool);
- PixelFormat pixel_format () const;
- AVSampleFormat audio_sample_format () const;
- int bytes_per_audio_sample () const;
-
- void filter_and_emit_video ();
+ static double compute_pts_offset (double, double, float);
- void setup_general ();
- void setup_video ();
- void setup_audio ();
void setup_subtitle ();
+ AVSampleFormat audio_sample_format () const;
+ int bytes_per_audio_sample () const;
+ void do_seek (VideoContent::Frame, bool, bool);
+
+ bool decode_video_packet ();
void decode_audio_packet ();
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
- void film_changed (Film::Property);
-
- std::string stream_name (AVStream* s) const;
-
- AVFormatContext* _format_context;
- int _video_stream;
-
- AVFrame* _frame;
-
- 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;
-
+
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
boost::mutex _filter_graphs_mutex;
- static boost::mutex _mutex;
+ bool _decode_video;
+ bool _decode_audio;
+
+ double _pts_offset;
};
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
new file mode 100644
index 000000000..f45b0fe52
--- /dev/null
+++ b/src/lib/ffmpeg_examiner.cc
@@ -0,0 +1,168 @@
+/*
+ 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);
+
+ cout << "got packet " << _packet.stream_index << "\n";
+
+ 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;
+ }
+
+ if (_first_video && have_all_audio) {
+ break;
+ }
+
+ av_free_packet (&_packet);
+ }
+}
+
+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..ec84865ed
--- /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 ce555ac8b..fa75ab1f1 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -29,29 +29,29 @@
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
#include "film.h"
-#include "format.h"
#include "job.h"
#include "filter.h"
-#include "transcoder.h"
#include "util.h"
#include "job_manager.h"
-#include "ab_transcode_job.h"
#include "transcode_job.h"
#include "scp_dcp_job.h"
#include "log.h"
-#include "options.h"
#include "exceptions.h"
#include "examine_content_job.h"
#include "scaler.h"
-#include "decoder_factory.h"
#include "config.h"
#include "version.h"
#include "ui_signaller.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "sndfile_decoder.h"
-#include "analyse_audio_job.h"
+#include "playlist.h"
+#include "player.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_content.h"
+#include "sndfile_content.h"
+#include "dcp_content_type.h"
+#include "ratio.h"
#include "cross.h"
#include "i18n.h"
@@ -68,9 +68,12 @@ using std::setfill;
using std::min;
using std::make_pair;
using std::endl;
+using std::cout;
using std::list;
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
using boost::to_upper_copy;
using boost::ends_with;
using boost::starts_with;
@@ -79,40 +82,32 @@ using libdcp::Size;
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.
*/
-Film::Film (string d, bool must_exist)
- : _use_dci_name (true)
- , _trust_content_header (true)
+Film::Film (string d)
+ : _playlist (new Playlist)
+ , _use_dci_name (true)
, _dcp_content_type (Config::instance()->default_dcp_content_type ())
- , _format (Config::instance()->default_format ())
+ , _container (Config::instance()->default_container ())
, _scaler (Scaler::from_id ("bicubic"))
- , _trim_start (0)
- , _trim_end (0)
- , _trim_type (CPL)
- , _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)
, _colour_lut (0)
, _j2k_bandwidth (200000000)
, _dci_metadata (Config::instance()->default_dci_metadata ())
- , _dcp_frame_rate (0)
+ , _dcp_video_frame_rate (24)
+ , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
, _minimum_audio_channels (0)
- , _source_frame_rate (0)
, _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)
@@ -133,103 +128,46 @@ 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());
- }
- }
-
- _sndfile_stream = SndfileStream::create ();
-
- _log.reset (new FileLog (file ("log")));
-
- if (must_exist) {
- read_metadata ();
- } else {
- write_metadata ();
- }
}
Film::Film (Film const & o)
: boost::enable_shared_from_this<Film> (o)
/* note: the copied film shares the original's log */
, _log (o._log)
+ , _playlist (new Playlist (o._playlist))
, _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)
+ , _container (o._container)
, _scaler (o._scaler)
- , _trim_start (o._trim_start)
- , _trim_end (o._trim_end)
- , _trim_type (o._trim_type)
- , _dcp_ab (o._dcp_ab)
- , _content_audio_stream (o._content_audio_stream)
- , _external_audio (o._external_audio)
- , _use_content_audio (o._use_content_audio)
- , _audio_gain (o._audio_gain)
- , _audio_delay (o._audio_delay)
- , _still_duration (o._still_duration)
- , _subtitle_stream (o._subtitle_stream)
, _with_subtitles (o._with_subtitles)
, _subtitle_offset (o._subtitle_offset)
, _subtitle_scale (o._subtitle_scale)
, _colour_lut (o._colour_lut)
, _j2k_bandwidth (o._j2k_bandwidth)
, _dci_metadata (o._dci_metadata)
+ , _dcp_video_frame_rate (o._dcp_video_frame_rate)
, _dci_date (o._dci_date)
- , _dcp_frame_rate (o._dcp_frame_rate)
, _minimum_audio_channels (o._minimum_audio_channels)
- , _size (o._size)
- , _length (o._length)
- , _content_digest (o._content_digest)
- , _content_audio_streams (o._content_audio_streams)
- , _sndfile_stream (o._sndfile_stream)
- , _subtitle_streams (o._subtitle_streams)
- , _source_frame_rate (o._source_frame_rate)
, _dirty (o._dirty)
{
-
-}
-
-Film::~Film ()
-{
-
+ _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
}
string
Film::video_state_identifier () const
{
- assert (format ());
+ assert (container ());
LocaleGuard lg;
- pair<string, string> f = Filter::ffmpeg_strings (filters());
-
stringstream s;
- s << format()->id()
- << "_" << content_digest()
- << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
- << "_" << _dcp_frame_rate
- << "_" << f.first << "_" << f.second
+ s << container()->id()
+ << "_" << _playlist->video_digest()
+ << "_" << _dcp_video_frame_rate
<< "_" << scaler()->id()
<< "_" << j2k_bandwidth()
- << "_" << boost::lexical_cast<int> (colour_lut());
-
- if (trim_type() == ENCODE) {
- s << "_" << trim_start() << "_" << trim_end();
- }
-
- if (dcp_ab()) {
- pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
- s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
- }
+ << "_" << lexical_cast<int> (colour_lut());
return s.str ();
}
@@ -284,13 +222,12 @@ Film::filename_safe_name () const
return o;
}
-string
-Film::audio_analysis_path () const
+boost::filesystem::path
+Film::audio_analysis_path (shared_ptr<const AudioContent> c) const
{
- boost::filesystem::path p;
- p /= "analysis";
- p /= content_digest();
- return file (p.string ());
+ boost::filesystem::path p = dir ("analysis");
+ p /= c->digest();
+ return p;
}
/** Add suitable Jobs to the JobManager to create a DCP for this Film */
@@ -303,7 +240,7 @@ Film::make_dcp ()
throw BadSettingError (_("name"), _("cannot contain slashes"));
}
- log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
+ log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
{
char buffer[128];
@@ -311,23 +248,18 @@ Film::make_dcp ()
log()->log (String::compose ("Starting to make DCP on %1", buffer));
}
- log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
- if (length()) {
- log()->log (String::compose ("Content length %1", length().get()));
- }
- log()->log (String::compose ("Content digest %1", content_digest()));
- log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
+// log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
+// if (length()) {
+// log()->log (String::compose ("Content length %1", length().get()));
+// }
+// log()->log (String::compose ("Content digest %1", content_digest()));
+// log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
- if (use_content_audio()) {
- log()->log ("Using content's audio");
- } else {
- log()->log (String::compose ("Using external audio (%1 files)", external_audio().size()));
- }
-#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.");
@@ -341,12 +273,12 @@ Film::make_dcp ()
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 (_playlist->content().empty ()) {
+ throw StringError (_("You must add some content to the DCP before creating it"));
}
if (dcp_content_type() == 0) {
@@ -357,60 +289,7 @@ Film::make_dcp ()
throw MissingSettingError (_("name"));
}
- DecodeOptions od;
- od.decode_subtitles = with_subtitles ();
-
- shared_ptr<Job> r;
-
- if (dcp_ab()) {
- r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
- } else {
- r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
- }
-}
-
-/** Start a job to analyse the audio of our content file */
-void
-Film::analyse_audio ()
-{
- if (_analyse_audio_job) {
- return;
- }
-
- _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
- _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
- JobManager::instance()->add (_analyse_audio_job);
-}
-
-/** 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()));
- _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
- JobManager::instance()->add (_examine_content_job);
-}
-
-void
-Film::analyse_audio_finished ()
-{
- ensure_ui_thread ();
-
- if (_analyse_audio_job->finished_ok ()) {
- AudioAnalysisSucceeded ();
- }
-
- _analyse_audio_job.reset ();
-}
-
-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 */
@@ -427,7 +306,7 @@ Film::send_dcp_to_tms ()
int
Film::encoded_frames () const
{
- if (format() == 0) {
+ if (container() == 0) {
return 0;
}
@@ -444,87 +323,44 @@ Film::encoded_frames () const
void
Film::write_metadata () const
{
+ if (!boost::filesystem::exists (directory())) {
+ boost::filesystem::create_directory (directory());
+ }
+
boost::mutex::scoped_lock lm (_state_mutex);
LocaleGuard lg;
boost::filesystem::create_directories (directory());
- string const m = file ("metadata");
- ofstream f (m.c_str ());
- if (!f.good ()) {
- throw CreateFileError (m);
- }
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("Metadata");
- f << "version " << state_version << endl;
+ root->add_child("Version")->add_child_text (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 << endl;
- f << "use_dci_name " << _use_dci_name << endl;
- f << "content " << _content << endl;
- f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
if (_dcp_content_type) {
- f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
- }
- if (_format) {
- f << "format " << _format->as_metadata () << endl;
- }
- f << "left_crop " << _crop.left << endl;
- f << "right_crop " << _crop.right << endl;
- f << "top_crop " << _crop.top << endl;
- f << "bottom_crop " << _crop.bottom << endl;
- for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
- f << "filter " << (*i)->id () << endl;
- }
- f << "scaler " << _scaler->id () << endl;
- f << "trim_start " << _trim_start << endl;
- f << "trim_end " << _trim_end << endl;
- switch (_trim_type) {
- case CPL:
- f << "trim_type cpl\n";
- break;
- case ENCODE:
- f << "trim_type encode\n";
- break;
- }
- f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
- if (_content_audio_stream) {
- f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
+ root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
}
- for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
- f << "external_audio " << *i << endl;
- }
- f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
- f << "audio_gain " << _audio_gain << endl;
- f << "audio_delay " << _audio_delay << endl;
- f << "still_duration " << _still_duration << endl;
- if (_subtitle_stream) {
- f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
- }
- f << "with_subtitles " << _with_subtitles << endl;
- f << "subtitle_offset " << _subtitle_offset << endl;
- f << "subtitle_scale " << _subtitle_scale << endl;
- f << "colour_lut " << _colour_lut << endl;
- f << "j2k_bandwidth " << _j2k_bandwidth << endl;
- _dci_metadata.write (f);
- f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
- f << "dcp_frame_rate " << _dcp_frame_rate << endl;
- f << "minimum_audio_channels " << _minimum_audio_channels << endl;
- f << "width " << _size.width << endl;
- f << "height " << _size.height << endl;
- f << "length " << _length.get_value_or(0) << endl;
- f << "content_digest " << _content_digest << endl;
-
- for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
- f << "content_audio_stream " << (*i)->to_string () << endl;
- }
-
- f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
- for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
- f << "subtitle_stream " << (*i)->to_string () << endl;
+ if (_container) {
+ root->add_child("Container")->add_child_text (_container->id ());
}
- f << "source_frame_rate " << _source_frame_rate << endl;
+ root->add_child("Scaler")->add_child_text (_scaler->id ());
+ root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
+ root->add_child("SubtitleOffset")->add_child_text (lexical_cast<string> (_subtitle_offset));
+ root->add_child("SubtitleScale")->add_child_text (lexical_cast<string> (_subtitle_scale));
+ root->add_child("ColourLUT")->add_child_text (lexical_cast<string> (_colour_lut));
+ root->add_child("J2KBandwidth")->add_child_text (lexical_cast<string> (_j2k_bandwidth));
+ _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+ root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast<string> (_dcp_video_frame_rate));
+ root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+ root->add_child("DCPAudioChannels")->add_child_text (lexical_cast<string> (_dcp_audio_channels));
+ root->add_child("MinimumAudioChannels")->add_child_text (lexical_cast<string> (_minimum_audio_channels));
+ _playlist->as_xml (root->add_child ("Playlist"));
+
+ doc.write_to_file_formatted (file ("metadata.xml"));
_dirty = false;
}
@@ -536,179 +372,46 @@ Film::read_metadata ()
boost::mutex::scoped_lock lm (_state_mutex);
LocaleGuard lg;
- _external_audio.clear ();
- _content_audio_streams.clear ();
- _subtitle_streams.clear ();
-
- boost::optional<int> version;
-
- /* Backward compatibility things */
- boost::optional<int> audio_sample_rate;
- boost::optional<int> audio_stream_index;
- boost::optional<int> subtitle_stream_index;
-
- ifstream f (file ("metadata").c_str());
- if (!f.good()) {
- throw OpenFileError (file ("metadata"));
+ if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+ throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version. You will need to create a new Film, re-add your content and set it up again. Sorry!"));
}
-
- multimap<string, string> kv = read_key_value (f);
- /* We need version before anything else */
- multimap<string, string>::iterator v = kv.find ("version");
- if (v != kv.end ()) {
- version = atoi (v->second.c_str());
- }
+ cxml::File f (file ("metadata.xml"), "Metadata");
- for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
- string const k = i->first;
- string const v = i->second;
+ _name = f.string_child ("Name");
+ _use_dci_name = f.bool_child ("UseDCIName");
- if (k == "audio_sample_rate") {
- audio_sample_rate = atoi (v.c_str());
- }
-
- /* User-specified stuff */
- if (k == "name") {
- _name = v;
- } else if (k == "use_dci_name") {
- _use_dci_name = (v == "1");
- } else if (k == "content") {
- _content = v;
- } else if (k == "trust_content_header") {
- _trust_content_header = (v == "1");
- } else if (k == "dcp_content_type") {
- if (version < 3) {
- _dcp_content_type = DCPContentType::from_pretty_name (v);
- } else {
- _dcp_content_type = DCPContentType::from_dci_name (v);
- }
- } else if (k == "format") {
- _format = Format::from_metadata (v);
- } else if (k == "left_crop") {
- _crop.left = atoi (v.c_str ());
- } else if (k == "right_crop") {
- _crop.right = atoi (v.c_str ());
- } else if (k == "top_crop") {
- _crop.top = atoi (v.c_str ());
- } else if (k == "bottom_crop") {
- _crop.bottom = atoi (v.c_str ());
- } else if (k == "filter") {
- _filters.push_back (Filter::from_id (v));
- } else if (k == "scaler") {
- _scaler = Scaler::from_id (v);
- } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
- _trim_start = atoi (v.c_str ());
- } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
- _trim_end = atoi (v.c_str ());
- } else if (k == "trim_type") {
- if (v == "cpl") {
- _trim_type = CPL;
- } else if (v == "encode") {
- _trim_type = ENCODE;
- }
- } else if (k == "dcp_ab") {
- _dcp_ab = (v == "1");
- } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
- if (!version) {
- audio_stream_index = atoi (v.c_str ());
- } else {
- _content_audio_stream = audio_stream_factory (v, version);
- }
- } else if (k == "external_audio") {
- _external_audio.push_back (v);
- } else if (k == "use_content_audio") {
- _use_content_audio = (v == "1");
- } else if (k == "audio_gain") {
- _audio_gain = atof (v.c_str ());
- } else if (k == "audio_delay") {
- _audio_delay = atoi (v.c_str ());
- } else if (k == "still_duration") {
- _still_duration = atoi (v.c_str ());
- } else if (k == "selected_subtitle_stream") {
- if (!version) {
- subtitle_stream_index = atoi (v.c_str ());
- } else {
- _subtitle_stream = subtitle_stream_factory (v, version);
- }
- } else if (k == "with_subtitles") {
- _with_subtitles = (v == "1");
- } else if (k == "subtitle_offset") {
- _subtitle_offset = atoi (v.c_str ());
- } else if (k == "subtitle_scale") {
- _subtitle_scale = atof (v.c_str ());
- } else if (k == "colour_lut") {
- _colour_lut = atoi (v.c_str ());
- } else if (k == "j2k_bandwidth") {
- _j2k_bandwidth = atoi (v.c_str ());
- } else if (k == "dci_date") {
- _dci_date = boost::gregorian::from_undelimited_string (v);
- } else if (k == "dcp_frame_rate") {
- _dcp_frame_rate = atoi (v.c_str ());
- } else if (k == "minimum_audio_channels") {
- _minimum_audio_channels = atoi (v.c_str ());
+ {
+ optional<string> c = f.optional_string_child ("DCPContentType");
+ if (c) {
+ _dcp_content_type = DCPContentType::from_dci_name (c.get ());
}
+ }
- _dci_metadata.read (k, v);
-
- /* Cached stuff */
- if (k == "width") {
- _size.width = atoi (v.c_str ());
- } else if (k == "height") {
- _size.height = atoi (v.c_str ());
- } else if (k == "length") {
- int const vv = atoi (v.c_str ());
- if (vv) {
- _length = vv;
- }
- } else if (k == "content_digest") {
- _content_digest = v;
- } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
- _content_audio_streams.push_back (audio_stream_factory (v, version));
- } else if (k == "external_audio_stream") {
- _sndfile_stream = audio_stream_factory (v, version);
- } else if (k == "subtitle_stream") {
- _subtitle_streams.push_back (subtitle_stream_factory (v, version));
- } else if (k == "source_frame_rate") {
- _source_frame_rate = atof (v.c_str ());
- } else if (version < 4 && k == "frames_per_second") {
- _source_frame_rate = atof (v.c_str ());
- /* Fill in what would have been used for DCP frame rate by the older version */
- _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
+ {
+ optional<string> c = f.optional_string_child ("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());
- }
- }
+ _scaler = Scaler::from_id (f.string_child ("Scaler"));
+ _with_subtitles = f.bool_child ("WithSubtitles");
+ _subtitle_offset = f.number_child<float> ("SubtitleOffset");
+ _subtitle_scale = f.number_child<float> ("SubtitleScale");
+ _colour_lut = f.number_child<int> ("ColourLUT");
+ _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+ _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+ _dcp_video_frame_rate = f.number_child<int> ("DCPVideoFrameRate");
+ _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+ _dcp_audio_channels = f.number_child<int> ("DCPAudioChannels");
+ _minimum_audio_channels = f.number_child<int> ("MinimumAudioChannels");
- /* 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;
}
-libdcp::Size
-Film::cropped_size (libdcp::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.
*/
@@ -744,67 +447,6 @@ Film::file (string f) const
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;
- }
-
- /* Resample to a DCI-approved sample rate */
- double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
-
- FrameRateConversion frc (source_frame_rate(), dcp_frame_rate());
-
- /* Compensate if the DCP is being run at a different frame rate
- to the source; that is, if the video is run such that it will
- 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 *= source_frame_rate() * frc.factor() / dcp_frame_rate();
- }
-
- return rint (t);
-}
-
-int
-Film::still_duration_in_frames () const
-{
- return still_duration() * source_frame_rate();
-}
-
/** @return a DCI-compliant name for a DCP of this film */
string
Film::dci_name (bool if_created_now) const
@@ -829,8 +471,8 @@ Film::dci_name (bool if_created_now) const
d << "_" << dcp_content_type()->dci_name();
}
- if (format()) {
- d << "_" << format()->dci_name();
+ if (container()) {
+ d << "_" << container()->dci_name();
}
DCIMetadata const dm = dci_metadata ();
@@ -851,22 +493,7 @@ Film::dci_name (bool if_created_now) const
}
}
- switch (audio_channels()) {
- case 1:
- d << "_10";
- break;
- case 2:
- d << "_20";
- break;
- case 6:
- d << "_51";
- break;
- case 8:
- d << "_71";
- break;
- }
-
- d << "_2K";
+ d << "_51_2K";
if (!dm.studio.empty ()) {
d << "_" << dm.studio;
@@ -930,110 +557,6 @@ Film::set_use_dci_name (bool u)
}
void
-Film::set_content (string c)
-{
- string check = directory ();
-
- boost::filesystem::path slash ("/");
- string platform_slash = slash.make_preferred().string ();
-
- if (!ends_with (check, platform_slash)) {
- check += platform_slash;
- }
-
- if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
- c = c.substr (_directory.length() + 1);
- }
-
- string old_content;
-
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (c == _content) {
- return;
- }
-
- old_content = _content;
- _content = c;
- }
-
- /* Do this before we start using FFmpeg ourselves */
- run_ffprobe (c, file ("ffprobe.log"), _log);
-
- /* Reset streams here in case the new content doesn't have one or the other */
- _content_audio_stream = shared_ptr<AudioStream> ();
- _subtitle_stream = shared_ptr<SubtitleStream> ();
-
- /* Start off using content audio */
- set_use_content_audio (true);
-
- /* Create a temporary decoder so that we can get information
- about the content.
- */
-
- try {
- Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
-
- set_size (d.video->native_size ());
- set_source_frame_rate (d.video->frames_per_second ());
- set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
- set_subtitle_streams (d.video->subtitle_streams ());
- if (d.audio) {
- set_content_audio_streams (d.audio->audio_streams ());
- }
-
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = c;
- }
-
- signal_changed (CONTENT);
-
- /* Start off with the first audio and subtitle streams */
- if (d.audio && !d.audio->audio_streams().empty()) {
- set_content_audio_stream (d.audio->audio_streams().front());
- }
-
- if (!d.video->subtitle_streams().empty()) {
- set_subtitle_stream (d.video->subtitle_streams().front());
- }
-
- examine_content ();
-
- } catch (...) {
-
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = old_content;
- throw;
-
- }
-
- /* Default format */
- set_format (Config::instance()->default_format ());
-
- /* 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)
{
{
@@ -1044,90 +567,13 @@ Film::set_dcp_content_type (DCPContentType const * t)
}
void
-Film::set_format (Format const * f)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _format = f;
- }
- signal_changed (FORMAT);
-}
-
-void
-Film::set_crop (Crop c)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _crop = c;
- }
- signal_changed (CROP);
-}
-
-void
-Film::set_left_crop (int c)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
-
- if (_crop.left == c) {
- return;
- }
-
- _crop.left = c;
- }
- signal_changed (CROP);
-}
-
-void
-Film::set_right_crop (int c)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (_crop.right == c) {
- return;
- }
-
- _crop.right = c;
- }
- signal_changed (CROP);
-}
-
-void
-Film::set_top_crop (int c)
+Film::set_container (Ratio const * c)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- if (_crop.top == c) {
- return;
- }
-
- _crop.top = c;
- }
- signal_changed (CROP);
-}
-
-void
-Film::set_bottom_crop (int c)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- if (_crop.bottom == c) {
- return;
- }
-
- _crop.bottom = c;
+ _container = c;
}
- signal_changed (CROP);
-}
-
-void
-Film::set_filters (vector<Filter const *> f)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _filters = f;
- }
- signal_changed (FILTERS);
+ signal_changed (CONTAINER);
}
void
@@ -1141,123 +587,6 @@ Film::set_scaler (Scaler const * s)
}
void
-Film::set_trim_start (int t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _trim_start = t;
- }
- signal_changed (TRIM_START);
-}
-
-void
-Film::set_trim_end (int t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _trim_end = t;
- }
- signal_changed (TRIM_END);
-}
-
-void
-Film::set_trim_type (TrimType t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _trim_type = t;
- }
- signal_changed (TRIM_TYPE);
-}
-
-void
-Film::set_dcp_ab (bool a)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_ab = a;
- }
- signal_changed (DCP_AB);
-}
-
-void
-Film::set_content_audio_stream (shared_ptr<AudioStream> s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_audio_stream = s;
- }
- signal_changed (CONTENT_AUDIO_STREAM);
-}
-
-void
-Film::set_external_audio (vector<string> a)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _external_audio = a;
- }
-
- shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
- if (decoder->audio_stream()) {
- _sndfile_stream = decoder->audio_stream ();
- }
-
- signal_changed (EXTERNAL_AUDIO);
-}
-
-void
-Film::set_use_content_audio (bool e)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _use_content_audio = e;
- }
-
- signal_changed (USE_CONTENT_AUDIO);
-}
-
-void
-Film::set_audio_gain (float g)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_gain = g;
- }
- signal_changed (AUDIO_GAIN);
-}
-
-void
-Film::set_audio_delay (int d)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _audio_delay = d;
- }
- signal_changed (AUDIO_DELAY);
-}
-
-void
-Film::set_still_duration (int d)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _still_duration = d;
- }
- signal_changed (STILL_DURATION);
-}
-
-void
-Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_stream = s;
- }
- signal_changed (SUBTITLE_STREAM);
-}
-
-void
Film::set_with_subtitles (bool w)
{
{
@@ -1319,16 +648,6 @@ Film::set_dci_metadata (DCIMetadata m)
void
-Film::set_dcp_frame_rate (int f)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_frame_rate = f;
- }
- signal_changed (DCP_FRAME_RATE);
-}
-
-void
Film::set_minimum_audio_channels (int c)
{
{
@@ -1339,76 +658,16 @@ Film::set_minimum_audio_channels (int c)
}
void
-Film::set_size (libdcp::Size s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _size = s;
- }
- signal_changed (SIZE);
-}
-
-void
-Film::set_length (SourceFrame l)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _length = l;
- }
- signal_changed (LENGTH);
-}
-
-void
-Film::unset_length ()
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _length = boost::none;
- }
- signal_changed (LENGTH);
-}
-
-void
-Film::set_content_digest (string d)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_digest = d;
- }
- _dirty = true;
-}
-
-void
-Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content_audio_streams = s;
- }
- signal_changed (CONTENT_AUDIO_STREAMS);
-}
-
-void
-Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
+Film::set_dcp_video_frame_rate (int f)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _subtitle_streams = s;
+ _dcp_video_frame_rate = f;
}
- signal_changed (SUBTITLE_STREAMS);
+ signal_changed (DCP_VIDEO_FRAME_RATE);
}
void
-Film::set_source_frame_rate (float f)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _source_frame_rate = f;
- }
- signal_changed (SOURCE_FRAME_RATE);
-}
-
-void
Film::signal_changed (Property p)
{
{
@@ -1416,20 +675,17 @@ Film::signal_changed (Property p)
_dirty = true;
}
- if (ui_signaller) {
- ui_signaller->emit (boost::bind (boost::ref (Changed), p));
+ switch (p) {
+ case Film::CONTENT:
+ set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
+ break;
+ default:
+ break;
}
-}
-int
-Film::audio_channels () const
-{
- shared_ptr<AudioStream> s = audio_stream ();
- if (!s) {
- return 0;
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (Changed), p));
}
-
- return s->channels ();
}
void
@@ -1438,16 +694,6 @@ Film::set_dci_date_today ()
_dci_date = boost::gregorian::day_clock::local_day ();
}
-boost::shared_ptr<AudioStream>
-Film::audio_stream () const
-{
- if (use_content_audio()) {
- return _content_audio_stream;
- }
-
- return _sndfile_stream;
-}
-
string
Film::info_path (int f) const
{
@@ -1503,20 +749,146 @@ Film::have_dcp () const
return true;
}
-bool
-Film::has_audio () const
+shared_ptr<Player>
+Film::player () const
+{
+ return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
+}
+
+shared_ptr<Playlist>
+Film::playlist () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _playlist;
+}
+
+Playlist::ContentList
+Film::content () const
+{
+ return _playlist->content ();
+}
+
+void
+Film::examine_and_add_content (shared_ptr<Content> c)
{
- if (use_content_audio()) {
- return audio_stream();
+ shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
+ j->Finished.connect (bind (&Film::add_content_weak, this, boost::weak_ptr<Content> (c)));
+ JobManager::instance()->add (j);
+}
+
+void
+Film::add_content_weak (weak_ptr<Content> c)
+{
+ shared_ptr<Content> content = c.lock ();
+ if (content) {
+ add_content (content);
}
+}
- vector<string> const e = external_audio ();
- for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
- if (!i->empty ()) {
- return true;
- }
+void
+Film::add_content (shared_ptr<Content> c)
+{
+ /* Add video content after any existing content */
+ if (dynamic_pointer_cast<VideoContent> (c)) {
+ c->set_start (_playlist->video_end ());
+ }
+
+ _playlist->add (c);
+}
+
+void
+Film::remove_content (shared_ptr<Content> c)
+{
+ _playlist->remove (c);
+}
+
+Time
+Film::length () const
+{
+ return _playlist->length ();
+}
+
+bool
+Film::has_subtitles () const
+{
+ return _playlist->has_subtitles ();
+}
+
+OutputVideoFrame
+Film::best_dcp_video_frame_rate () const
+{
+ return _playlist->best_dcp_frame_rate ();
+}
+
+void
+Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
+{
+ if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
+ set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ());
+ }
+
+ if (ui_signaller) {
+ ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
}
+}
+
+void
+Film::playlist_changed ()
+{
+ signal_changed (CONTENT);
+}
- return false;
+int
+Film::loop () const
+{
+ return _playlist->loop ();
+}
+
+void
+Film::set_loop (int c)
+{
+ _playlist->set_loop (c);
+}
+
+OutputAudioFrame
+Film::time_to_audio_frames (Time t) const
+{
+ return t * dcp_audio_frame_rate () / TIME_HZ;
+}
+
+OutputVideoFrame
+Film::time_to_video_frames (Time t) const
+{
+ return t * dcp_video_frame_rate () / TIME_HZ;
+}
+
+Time
+Film::audio_frames_to_time (OutputAudioFrame f) const
+{
+ return f * TIME_HZ / dcp_audio_frame_rate ();
+}
+
+Time
+Film::video_frames_to_time (OutputVideoFrame f) const
+{
+ return f * TIME_HZ / dcp_video_frame_rate ();
+}
+
+OutputAudioFrame
+Film::dcp_audio_frame_rate () const
+{
+ /* XXX */
+ return 48000;
}
+void
+Film::set_sequence_video (bool s)
+{
+ _playlist->set_sequence_video (s);
+}
+
+libdcp::Size
+Film::full_frame () const
+{
+ return libdcp::Size (2048, 1080);
+}
diff --git a/src/lib/film.h b/src/lib/film.h
index ca9bd57f4..f5a7c1246 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -18,12 +18,12 @@
*/
/** @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>
@@ -32,49 +32,43 @@
#include <boost/thread.hpp>
#include <boost/signals2.hpp>
#include <boost/enable_shared_from_this.hpp>
-extern "C" {
-#include <libavcodec/avcodec.h>
-}
-#include "dcp_content_type.h"
#include "util.h"
-#include "stream.h"
#include "dci_metadata.h"
+#include "types.h"
+#include "ffmpeg_content.h"
+#include "playlist.h"
-class Format;
+class DCPContentType;
class Job;
class Filter;
class Log;
class ExamineContentJob;
class AnalyseAudioJob;
class ExternalAudioStream;
+class Content;
+class Player;
/** @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.
*/
class Film : public boost::enable_shared_from_this<Film>
{
public:
- Film (std::string d, bool must_exist = true);
+ Film (std::string d);
Film (Film const &);
- ~Film ();
std::string info_dir () const;
std::string j2c_path (int f, bool t) const;
std::string info_path (int f) const;
std::string internal_video_mxf_dir () const;
std::string internal_video_mxf_filename () const;
- std::string audio_analysis_path () const;
+ boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
std::string dcp_video_mxf_filename () const;
std::string dcp_audio_mxf_filename () const;
- void examine_content ();
- void analyse_audio ();
void send_dcp_to_tms ();
-
void make_dcp ();
/** @return Logger.
@@ -89,15 +83,9 @@ 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;
- libdcp::Size cropped_size (libdcp::Size) const;
std::string dci_name (bool if_created_now) const;
std::string dcp_name (bool if_created_now = false) const;
@@ -106,16 +94,32 @@ public:
return _dirty;
}
- int audio_channels () const;
-
- void set_dci_date_today ();
+ libdcp::Size full_frame () const;
bool have_dcp () const;
- enum TrimType {
- CPL,
- ENCODE
- };
+ boost::shared_ptr<Player> player () const;
+ boost::shared_ptr<Playlist> playlist () const;
+
+ OutputAudioFrame dcp_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 */
+
+ Playlist::ContentList content () const;
+
+ Time length () const;
+ bool has_subtitles () const;
+ OutputVideoFrame best_dcp_video_frame_rate () const;
+
+ void set_loop (int);
+ int loop () const;
+
+ void set_sequence_video (bool);
/** Identifiers for the parts of our state;
used for signalling changes.
@@ -124,36 +128,19 @@ 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,
+ LOOP,
DCP_CONTENT_TYPE,
- FORMAT,
- CROP,
- FILTERS,
+ CONTAINER,
SCALER,
- TRIM_START,
- TRIM_END,
- TRIM_TYPE,
- DCP_AB,
- CONTENT_AUDIO_STREAM,
- EXTERNAL_AUDIO,
- USE_CONTENT_AUDIO,
- AUDIO_GAIN,
- AUDIO_DELAY,
- STILL_DURATION,
- SUBTITLE_STREAM,
WITH_SUBTITLES,
SUBTITLE_OFFSET,
SUBTITLE_SCALE,
COLOUR_LUT,
J2K_BANDWIDTH,
DCI_METADATA,
- SIZE,
- LENGTH,
- CONTENT_AUDIO_STREAMS,
- SUBTITLE_STREAMS,
- SOURCE_FRAME_RATE,
- DCP_FRAME_RATE,
+ DCP_VIDEO_FRAME_RATE,
MINIMUM_AUDIO_CHANNELS
};
@@ -175,34 +162,14 @@ public:
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;
- }
-
- Crop crop () const {
+ Ratio const * container () 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;
+ return _container;
}
Scaler const * scaler () const {
@@ -210,63 +177,6 @@ public:
return _scaler;
}
- int trim_start () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _trim_start;
- }
-
- int trim_end () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _trim_end;
- }
-
- TrimType trim_type () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _trim_type;
- }
-
- 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;
- }
-
- int still_duration_in_frames () const;
-
- 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;
@@ -297,43 +207,15 @@ public:
return _dci_metadata;
}
- int dcp_frame_rate () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_frame_rate;
- }
-
- libdcp::Size size () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _size;
- }
-
- boost::optional<SourceFrame> length () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _length;
- }
-
- std::string content_digest () const {
+ /* XXX: -> "video_frame_rate" */
+ int dcp_video_frame_rate () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _content_digest;
- }
-
- std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _content_audio_streams;
+ return _dcp_video_frame_rate;
}
- std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _subtitle_streams;
- }
-
- float source_frame_rate () const {
+ int dcp_audio_channels () const {
boost::mutex::scoped_lock lm (_state_mutex);
- if (content_type() == STILL) {
- return 24;
- }
-
- return _source_frame_rate;
+ return _dcp_audio_channels;
}
int minimum_audio_channels () const {
@@ -341,75 +223,48 @@ public:
return _minimum_audio_channels;
}
- boost::shared_ptr<AudioStream> audio_stream () const;
- bool has_audio () 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_scaler (Scaler const *);
- void set_trim_start (int);
- void set_trim_end (int);
- void set_trim_type (TrimType);
- 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_colour_lut (int);
void set_j2k_bandwidth (int);
void set_dci_metadata (DCIMetadata);
- void set_dcp_frame_rate (int);
- void set_size (libdcp::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_source_frame_rate (float);
+ void set_dcp_video_frame_rate (int);
+ void set_dci_date_today ();
void set_minimum_audio_channels (int);
- /** Emitted when some property has changed */
+ /** Emitted when some property has of the Film has changed */
mutable boost::signals2::signal<void (Property)> Changed;
- boost::signals2::signal<void ()> AudioAnalysisSucceeded;
+ /** 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 */
- boost::shared_ptr<Log> _log;
-
- /** Any running ExamineContentJob, or 0 */
- boost::shared_ptr<ExamineContentJob> _examine_content_job;
- /** Any running AnalyseAudioJob, or 0 */
- boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
-
void signal_changed (Property);
- void examine_content_finished ();
- void analyse_audio_finished ();
std::string video_state_identifier () const;
+ void playlist_changed ();
+ void playlist_content_changed (boost::weak_ptr<Content>, int);
std::string filename_safe_name () const;
+ void add_content_weak (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.
@@ -418,54 +273,16 @@ private:
/** 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;
/** Scaler algorithm to use */
Scaler const * _scaler;
- /** Frames to trim off the start of the DCP */
- int _trim_start;
- /** Frames to trim off the end of the DCP */
- int _trim_end;
- TrimType _trim_type;
- /** 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
@@ -481,32 +298,15 @@ private:
int _colour_lut;
/** bandwidth for J2K files in bits per second */
int _j2k_bandwidth;
-
/** DCI naming stuff */
DCIMetadata _dci_metadata;
+ /** Frames per second to run our DCP at */
+ int _dcp_video_frame_rate;
/** The date that we should use in a DCI name */
boost::gregorian::date _dci_date;
- /** Frames per second to run our DCP at */
- int _dcp_frame_rate;
+ int _dcp_audio_channels;
int _minimum_audio_channels;
- /* Data which are cached to speed things up */
-
- /** Size, in pixels, of the source (ignoring cropping) */
- libdcp::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> _sndfile_stream;
- /** the subtitle streams that we can use */
- std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
- /** Frames per second of the source */
- float _source_frame_rate;
-
/** true if our state has changed since we last saved it */
mutable bool _dirty;
diff --git a/src/lib/filter.h b/src/lib/filter.h
index 205d92482..7587312c2 100644
--- a/src/lib/filter.h
+++ b/src/lib/filter.h
@@ -21,8 +21,8 @@
* @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>
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index 8ff5e75df..275c46909 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -33,24 +33,23 @@ extern "C" {
#include "filter.h"
#include "exceptions.h"
#include "image.h"
-#include "film.h"
-#include "ffmpeg_decoder.h"
#include "i18n.h"
using std::stringstream;
using std::string;
using std::list;
+using std::cout;
using boost::shared_ptr;
+using boost::weak_ptr;
using libdcp::Size;
-/** Construct a FFmpegFilterGraph 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.
*/
-FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::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)
@@ -58,12 +57,13 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco
{
_frame = av_frame_alloc ();
- string filters = Filter::ffmpeg_strings (film->filters()).first;
+ string filters = Filter::ffmpeg_strings (content->filters()).first;
if (!filters.empty ()) {
- filters += N_(",");
+ filters += ",";
}
- filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
+ /* XXX; remove */
+ filters += crop_string (Position (), _size);
AVFilterGraph* graph = avfilter_graph_alloc();
if (graph == 0) {
@@ -83,8 +83,8 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco
stringstream a;
a << "video_size=" << _size.width << "x" << _size.height << ":"
<< "pix_fmt=" << _pixel_format << ":"
- << "time_base=" << decoder->time_base_numerator() << "/" << decoder->time_base_denominator() << ":"
- << "pixel_aspect=" << decoder->sample_aspect_ratio_numerator() << "/" << decoder->sample_aspect_ratio_denominator();
+ << "time_base=1/1:"
+ << "pixel_aspect=1/1";
int r;
@@ -127,7 +127,7 @@ FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr<Film> film, FFmpegDecoder* deco
/* XXX: leaking `inputs' / `outputs' ? */
}
-FFmpegFilterGraph::~FFmpegFilterGraph ()
+FilterGraph::~FilterGraph ()
{
av_frame_free (&_frame);
}
@@ -136,7 +136,7 @@ FFmpegFilterGraph::~FFmpegFilterGraph ()
* set of Images. Caller handles memory management of the input frame.
*/
list<shared_ptr<Image> >
-FFmpegFilterGraph::process (AVFrame* frame)
+FilterGraph::process (AVFrame* frame)
{
list<shared_ptr<Image> > images;
@@ -161,25 +161,7 @@ FFmpegFilterGraph::process (AVFrame* frame)
* @return true if this chain can process images with `s' and `p', otherwise false.
*/
bool
-FFmpegFilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
{
return (_size == s && _pixel_format == p);
}
-
-list<shared_ptr<Image> >
-EmptyFilterGraph::process (AVFrame* frame)
-{
- list<shared_ptr<Image> > im;
- im.push_back (shared_ptr<Image> (new SimpleImage (frame)));
- return im;
-}
-
-shared_ptr<FilterGraph>
-filter_graph_factory (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size size, AVPixelFormat pixel_format)
-{
- if (film->filters().empty() && film->crop() == Crop()) {
- return shared_ptr<FilterGraph> (new EmptyFilterGraph);
- }
-
- return shared_ptr<FilterGraph> (new FFmpegFilterGraph (film, decoder, size, pixel_format));
-}
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index ee378af4c..e294812c2 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -21,41 +21,22 @@
* @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"
class Image;
class VideoFilter;
-class FFmpegDecoder;
-class FilterGraph
-{
-public:
- virtual ~FilterGraph () {}
- virtual bool can_process (libdcp::Size, AVPixelFormat) const = 0;
- virtual std::list<boost::shared_ptr<Image> > process (AVFrame *) = 0;
-};
-
-class EmptyFilterGraph : public FilterGraph
-{
-public:
- bool can_process (libdcp::Size, AVPixelFormat) const {
- return true;
- }
-
- std::list<boost::shared_ptr<Image> > process (AVFrame *);
-};
-
-/** @class FFmpegFilterGraph
+/** @class FilterGraph
* @brief A graph of FFmpeg filters.
*/
-class FFmpegFilterGraph : public FilterGraph
+class FilterGraph
{
public:
- FFmpegFilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
- ~FFmpegFilterGraph ();
+ FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+ ~FilterGraph ();
bool can_process (libdcp::Size s, AVPixelFormat p) const;
std::list<boost::shared_ptr<Image> > process (AVFrame * frame);
@@ -68,6 +49,4 @@ private:
AVFrame* _frame;
};
-boost::shared_ptr<FilterGraph> filter_graph_factory (boost::shared_ptr<Film>, FFmpegDecoder *, libdcp::Size, AVPixelFormat);
-
#endif
diff --git a/src/lib/format.cc b/src/lib/format.cc
deleted file mode 100644
index 78e200847..000000000
--- a/src/lib/format.cc
+++ /dev/null
@@ -1,240 +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"
-
-#include "i18n.h"
-
-using std::string;
-using std::setprecision;
-using std::stringstream;
-using std::vector;
-using boost::shared_ptr;
-using libdcp::Size;
-
-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 << N_(" (");
- }
-
- s << setprecision(3) << _ratio << N_(":1");
-
- if (!_nickname.empty ()) {
- s << N_(")");
- }
-
- 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 ()
-{
- /// TRANSLATORS: these are film picture aspect ratios; "Academy" means 1.37, "Flat" 1.85 and "Scope" 2.39.
- _formats.push_back (
- new FixedFormat (1.19, libdcp::Size (1285, 1080), "119", _("1.19"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (4.0 / 3.0, libdcp::Size (1436, 1080), "133", _("4:3"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.38, libdcp::Size (1485, 1080), "138", _("1.375"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (4.0 / 3.0, libdcp::Size (1998, 1080), "133-in-flat", _("4:3 within Flat"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.37, libdcp::Size (1480, 1080), "137", _("Academy"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.66, libdcp::Size (1793, 1080), "166", _("1.66"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.66, libdcp::Size (1998, 1080), "166-in-flat", _("1.66 within Flat"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.78, libdcp::Size (1998, 1080), "178-in-flat", _("16:9 within Flat"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.78, libdcp::Size (1920, 1080), "178", _("16:9"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.85, libdcp::Size (1998, 1080), "185", _("Flat"), "F"
- ));
-
- _formats.push_back (
- new FixedFormat (1.78, libdcp::Size (2048, 858), "178-in-scope", _("16:9 within Scope"), "S"
- ));
-
- _formats.push_back (
- new FixedFormat (2.39, libdcp::Size (2048, 858), "239", _("Scope"), "S"
- ));
-
- _formats.push_back (
- new FixedFormat (1.896, libdcp::Size (2048, 1080), "full-frame", _("Full frame"), "C"
- ));
-
- _formats.push_back (
- new VariableFormat (libdcp::Size (1998, 1080), "var-185", _("Flat without stretch"), "F"
- ));
-
- _formats.push_back (
- new VariableFormat (libdcp::Size (2048, 858), "var-239", _("Scope without stretch"), "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
- * @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 (float r, libdcp::Size dcp, string id, string n, string d)
- : Format (dcp, id, n, d)
- , _ratio (r)
-{
-
-}
-
-/** @return Number of pixels (int the DCP image) to pad either side of the film
- * (so there are dcp_padding() pixels on the left and dcp_padding() on the right)
- */
-int
-Format::dcp_padding (shared_ptr<const Film> f) const
-{
- int p = rint ((_dcp_size.width - (_dcp_size.height * ratio(f))) / 2.0);
-
- /* This comes out -ve for Scope; bodge it */
- if (p < 0) {
- p = 0;
- }
-
- return p;
-}
-
-float
-Format::container_ratio () const
-{
- return static_cast<float> (_dcp_size.width) / _dcp_size.height;
-}
-
-VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d)
- : Format (dcp, id, n, d)
-{
-
-}
-
-float
-VariableFormat::ratio (shared_ptr<const Film> f) const
-{
- libdcp::Size const c = f->cropped_size (f->size ());
- return float (c.width) / c.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 e95306232..000000000
--- a/src/lib/format.h
+++ /dev/null
@@ -1,126 +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 (libdcp::Size dcp, std::string id, std::string n, std::string d)
- : _dcp_size (dcp)
- , _id (id)
- , _nickname (n)
- , _dci_name (d)
- {}
-
- /** @return the ratio of the container (including any padding) */
- float container_ratio () const;
-
- 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.
- */
- libdcp::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:
- /** @return the ratio */
- virtual float ratio (boost::shared_ptr<const Film> f) const = 0;
-
- /** libdcp::Size in pixels of the images that we should
- * put in a DCP for this ratio. This size will not correspond
- * to the ratio when we are doing things like 16:9 in a Flat frame.
- */
- libdcp::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 (float, libdcp::Size, std::string, std::string, std::string);
-
- float ratio (boost::shared_ptr<const Film>) const {
- return _ratio;
- }
-
- std::string name () const;
-
-private:
-
- float _ratio;
-};
-
-class VariableFormat : public Format
-{
-public:
- VariableFormat (libdcp::Size, std::string, std::string, std::string);
-
- float ratio (boost::shared_ptr<const Film> f) const;
-
- std::string name () const;
-};
diff --git a/src/lib/gain.cc b/src/lib/gain.cc
deleted file mode 100644
index ccd779d71..000000000
--- a/src/lib/gain.cc
+++ /dev/null
@@ -1,45 +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 "gain.h"
-
-using boost::shared_ptr;
-
-/** @param gain gain in dB */
-Gain::Gain (shared_ptr<Log> log, float gain)
- : AudioProcessor (log)
- , _gain (gain)
-{
-
-}
-
-void
-Gain::process_audio (shared_ptr<const AudioBuffers> b)
-{
- if (_gain != 0) {
- float const linear_gain = pow (10, _gain / 20);
- for (int i = 0; i < b->channels(); ++i) {
- for (int j = 0; j < b->frames(); ++j) {
- b->data(i)[j] *= linear_gain;
- }
- }
- }
-
- Audio (b);
-}
diff --git a/src/lib/i18n.h b/src/lib/i18n.h
index 46bb1d565..890313bc6 100644
--- a/src/lib/i18n.h
+++ b/src/lib/i18n.h
@@ -19,5 +19,5 @@
#include <libintl.h>
-#define _(x) dgettext ("libdvdomatic", x)
+#define _(x) dgettext ("libdcpomatic", x)
#define N_(x) x
diff --git a/src/lib/image.cc b/src/lib/image.cc
index f28652d4e..722ff5d3c 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -43,8 +43,9 @@ extern "C" {
#include "i18n.h"
-using namespace std;
-using namespace boost;
+using std::string;
+using std::min;
+using boost::shared_ptr;
using libdcp::Size;
void
@@ -53,22 +54,28 @@ Image::swap (Image& other)
std::swap (_pixel_format, other._pixel_format);
}
-/** @param n Component index.
- * @return Number of lines in the image for the given component.
- */
int
-Image::lines (int n) const
+Image::line_factor (int n) const
{
if (n == 0) {
- return size().height;
+ return 1;
}
-
+
AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
if (!d) {
throw PixelFormatError (N_("lines()"), _pixel_format);
}
- return size().height / pow(2.0f, d->log2_chroma_h);
+ return pow (2.0f, d->log2_chroma_h);
+}
+
+/** @param n Component index.
+ * @return Number of lines in the image for the given component.
+ */
+int
+Image::lines (int n) const
+{
+ return rint (ceil (static_cast<double>(size().height) / line_factor (n)));
}
/** @return Number of components */
@@ -121,7 +128,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, bool result_aligned)
* @param scaler Scaler to use.
*/
shared_ptr<Image>
-Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool result_aligned) const
+Image::scale_and_convert_to_rgb (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) const
{
assert (scaler);
/* Empirical testing suggests that sws_scale() will crash if
@@ -129,14 +136,11 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons
*/
assert (aligned ());
- libdcp::Size content_size = out_size;
- content_size.width -= (padding * 2);
-
- shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, result_aligned));
+ shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned));
struct SwsContext* scale_context = sws_getContext (
size().width, size().height, pixel_format(),
- content_size.width, content_size.height, PIX_FMT_RGB24,
+ out_size.width, out_size.height, PIX_FMT_RGB24,
scaler->ffmpeg_id (), 0, 0, 0
);
@@ -148,28 +152,6 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons
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, result_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;
@@ -232,12 +214,12 @@ Image::crop (Crop crop, bool aligned) const
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;
-
+
/* 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 += stride()[c];
out_p += out->stride()[c];
@@ -385,6 +367,21 @@ Image::alpha_blend (shared_ptr<const Image> other, Position position)
}
void
+Image::copy (shared_ptr<const Image> other, Position 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) {
@@ -512,12 +509,11 @@ SimpleImage::SimpleImage (AVFrame* frame)
}
}
-SimpleImage::SimpleImage (shared_ptr<const Image> other)
+SimpleImage::SimpleImage (shared_ptr<const Image> other, bool aligned)
: Image (*other.get())
+ , _size (other->size())
+ , _aligned (aligned)
{
- _size = other->size ();
- _aligned = true;
-
allocate ();
for (int i = 0; i < components(); ++i) {
diff --git a/src/lib/image.h b/src/lib/image.h
index 70dacfaee..5407ce66e 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>
@@ -69,12 +69,14 @@ public:
virtual bool aligned () const = 0;
int components () const;
+ int line_factor (int) const;
int lines (int) const;
- boost::shared_ptr<Image> scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool aligned) const;
+ boost::shared_ptr<Image> scale_and_convert_to_rgb (libdcp::Size, Scaler const *, bool) const;
boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, 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 copy (boost::shared_ptr<const Image> image, Position pos);
boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
void make_black ();
@@ -108,7 +110,7 @@ public:
SimpleImage (AVPixelFormat, libdcp::Size, bool);
SimpleImage (AVFrame *);
SimpleImage (SimpleImage const &);
- SimpleImage (boost::shared_ptr<const Image>);
+ SimpleImage (boost::shared_ptr<const Image>, bool);
SimpleImage& operator= (SimpleImage const &);
~SimpleImage ();
diff --git a/src/lib/options.h b/src/lib/imagemagick.h
index 0d2c07fd5..5a1712a3a 100644
--- a/src/lib/options.h
+++ b/src/lib/imagemagick.h
@@ -17,27 +17,19 @@
*/
-#ifndef DVDOMATIC_OPTIONS_H
-#define DVDOMATIC_OPTIONS_H
+class ImageMagickContent;
-/** @file src/options.h
- * @brief Options for a decoding operation.
- */
-
-class DecodeOptions
+class ImageMagick
{
public:
- DecodeOptions ()
- : decode_video (true)
- , decode_audio (true)
- , decode_subtitles (false)
- , video_sync (true)
+ ImageMagick (boost::shared_ptr<const ImageMagickContent> c)
+ : _imagemagick_content (c)
{}
- bool decode_video;
- bool decode_audio;
- bool decode_subtitles;
- bool video_sync;
-};
+ boost::shared_ptr<const ImageMagickContent> content () const {
+ return _imagemagick_content;
+ }
-#endif
+protected:
+ boost::shared_ptr<const ImageMagickContent> _imagemagick_content;
+};
diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc
new file mode 100644
index 000000000..2fd65ffa0
--- /dev/null
+++ b/src/lib/imagemagick_content.cc
@@ -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 <libcxml/cxml.h>
+#include "imagemagick_content.h"
+#include "imagemagick_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+ImageMagickContent::ImageMagickContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , VideoContent (f, p)
+{
+
+}
+
+ImageMagickContent::ImageMagickContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+ : Content (f, node)
+ , VideoContent (f, node)
+{
+
+}
+
+string
+ImageMagickContent::summary () const
+{
+ return String::compose (_("Image: %1"), file().filename().string());
+}
+
+bool
+ImageMagickContent::valid_file (boost::filesystem::path f)
+{
+ string ext = f.extension().string();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+ return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp");
+}
+
+void
+ImageMagickContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("ImageMagick");
+ Content::as_xml (node);
+ VideoContent::as_xml (node);
+}
+
+void
+ImageMagickContent::examine (shared_ptr<Job> job)
+{
+ Content::examine (job);
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ shared_ptr<ImageMagickExaminer> examiner (new ImageMagickExaminer (film, shared_from_this()));
+
+ set_video_length (Config::instance()->default_still_length() * 24);
+ take_from_video_examiner (examiner);
+}
+
+shared_ptr<Content>
+ImageMagickContent::clone () const
+{
+ return shared_ptr<Content> (new ImageMagickContent (*this));
+}
+
+void
+ImageMagickContent::set_video_length (VideoContent::Frame len)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _video_length = len;
+ }
+
+ signal_changed (ContentProperty::LENGTH);
+}
+
+Time
+ImageMagickContent::length () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ FrameRateConversion frc (24, film->dcp_video_frame_rate ());
+ return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate ();
+}
+
diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h
new file mode 100644
index 000000000..04425af08
--- /dev/null
+++ b/src/lib/imagemagick_content.h
@@ -0,0 +1,51 @@
+/*
+ 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_IMAGEMAGICK_CONTENT_H
+#define DCPOMATIC_IMAGEMAGICK_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+ class Node;
+}
+
+class ImageMagickContent : public VideoContent
+{
+public:
+ ImageMagickContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ ImageMagickContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+
+ boost::shared_ptr<ImageMagickContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<ImageMagickContent> (Content::shared_from_this ());
+ };
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ void as_xml (xmlpp::Node *) const;
+ boost::shared_ptr<Content> clone () const;
+ Time length () const;
+
+ void set_video_length (VideoContent::Frame);
+
+ static bool valid_file (boost::filesystem::path);
+};
+
+#endif
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
index 5ce22c296..04d3d9df7 100644
--- a/src/lib/imagemagick_decoder.cc
+++ b/src/lib/imagemagick_decoder.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
@@ -20,6 +20,7 @@
#include <iostream>
#include <boost/filesystem.hpp>
#include <Magick++.h>
+#include "imagemagick_content.h"
#include "imagemagick_decoder.h"
#include "image.h"
#include "film.h"
@@ -31,66 +32,36 @@ using std::cout;
using boost::shared_ptr;
using libdcp::Size;
-ImageMagickDecoder::ImageMagickDecoder (
- boost::shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , VideoDecoder (f, o)
+ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
+ : Decoder (f)
+ , VideoDecoder (f)
+ , ImageMagick (c)
{
- if (boost::filesystem::is_directory (_film->content_path())) {
- for (
- boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (_film->content_path());
- i != boost::filesystem::directory_iterator();
- ++i) {
-
- if (still_image_file (i->path().string())) {
- _files.push_back (i->path().string());
- }
- }
- } else {
- _files.push_back (_film->content_path ());
- }
-
- _iter = _files.begin ();
-}
-
-libdcp::Size
-ImageMagickDecoder::native_size () const
-{
- if (_files.empty ()) {
- throw DecodeError (_("no still image files found"));
- }
-
- /* Look at the first file and assume its size holds for all */
- using namespace MagickCore;
- Magick::Image* image = new Magick::Image (_film->content_path ());
- libdcp::Size const s = libdcp::Size (image->columns(), image->rows());
- delete image;
- return s;
}
-bool
+void
ImageMagickDecoder::pass ()
{
- if (_iter == _files.end()) {
- if (video_frame() >= _film->still_duration_in_frames()) {
- return true;
- }
+ if (_video_position >= _imagemagick_content->video_length ()) {
+ return;
+ }
- emit_video (_image, true, double (video_frame()) / frames_per_second());
- return false;
+ if (_image) {
+ video (_image, true, _video_position);
+ return;
}
+
+ Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ());
+ _video_size = libdcp::Size (magick_image->columns(), magick_image->rows());
- Magick::Image* magick_image = new Magick::Image (_film->content_path ());
-
- libdcp::Size size = native_size ();
- shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false));
+ _image.reset (new SimpleImage (PIX_FMT_RGB24, _video_size.get(), 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) {
+ uint8_t* p = _image->data()[0];
+ for (int y = 0; y < _video_size->height; ++y) {
+ for (int x = 0; x < _video_size->width; ++x) {
Magick::Color c = magick_image->pixelColor (x, y);
*p++ = c.redQuantum() * 255 / QuantumRange;
*p++ = c.greenQuantum() * 255 / QuantumRange;
@@ -100,59 +71,25 @@ ImageMagickDecoder::pass ()
delete magick_image;
- _image = image->crop (_film->crop(), true);
-
- emit_video (_image, false, double (video_frame()) / frames_per_second());
-
- ++_iter;
- return false;
+ video (_image, false, _video_position);
}
-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)
+void
+ImageMagickDecoder::seek (VideoContent::Frame frame)
{
- 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;
+ _video_position = frame;
}
void
-ImageMagickDecoder::film_changed (Film::Property p)
+ImageMagickDecoder::seek_back ()
{
- if (p == Film::CROP) {
- OutputChanged ();
+ if (_video_position > 0) {
+ _video_position--;
}
}
-float
-ImageMagickDecoder::frames_per_second () const
+bool
+ImageMagickDecoder::done () const
{
- return _film->source_frame_rate ();
+ return _video_position >= _imagemagick_content->video_length ();
}
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
index 80a08f81f..286f47337 100644
--- a/src/lib/imagemagick_decoder.h
+++ b/src/lib/imagemagick_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
@@ -18,67 +18,27 @@
*/
#include "video_decoder.h"
+#include "imagemagick.h"
namespace Magick {
class Image;
}
-class ImageMagickDecoder : public VideoDecoder
+class ImageMagickContent;
+
+class ImageMagickDecoder : public VideoDecoder, public ImageMagick
{
public:
- ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
- float frames_per_second () const;
-
- libdcp::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;
- }
+ ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
- int64_t audio_channel_layout () const {
- return 0;
- }
+ /* Decoder */
- 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;
- }
+ void pass ();
+ void seek (VideoContent::Frame);
+ void seek_back ();
+ bool done () const;
private:
- void film_changed (Film::Property);
-
- std::list<std::string> _files;
- std::list<std::string>::iterator _iter;
-
boost::shared_ptr<Image> _image;
+ mutable boost::optional<libdcp::Size> _video_size;
};
diff --git a/src/lib/imagemagick_examiner.cc b/src/lib/imagemagick_examiner.cc
new file mode 100644
index 000000000..0eee0d425
--- /dev/null
+++ b/src/lib/imagemagick_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 "imagemagick_content.h"
+#include "imagemagick_examiner.h"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+ImageMagickExaminer::ImageMagickExaminer (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
+ : ImageMagick (c)
+ , _film (f)
+{
+ using namespace MagickCore;
+ Magick::Image* image = new Magick::Image (_imagemagick_content->file().string());
+ _video_size = libdcp::Size (image->columns(), image->rows());
+ delete image;
+}
+
+libdcp::Size
+ImageMagickExaminer::video_size () const
+{
+ return _video_size;
+}
+
+int
+ImageMagickExaminer::video_length () const
+{
+ return _imagemagick_content->video_length ();
+}
+
+float
+ImageMagickExaminer::video_frame_rate () const
+{
+ boost::shared_ptr<const Film> f = _film.lock ();
+ if (!f) {
+ return 24;
+ }
+
+ return f->dcp_video_frame_rate ();
+}
+
diff --git a/src/lib/imagemagick_examiner.h b/src/lib/imagemagick_examiner.h
new file mode 100644
index 000000000..801ede442
--- /dev/null
+++ b/src/lib/imagemagick_examiner.h
@@ -0,0 +1,41 @@
+/*
+ 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 "imagemagick.h"
+#include "video_examiner.h"
+
+namespace Magick {
+ class Image;
+}
+
+class ImageMagickContent;
+
+class ImageMagickExaminer : public ImageMagick, public VideoExaminer
+{
+public:
+ ImageMagickExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
+
+ float video_frame_rate () const;
+ libdcp::Size video_size () const;
+ VideoContent::Frame video_length () const;
+
+private:
+ boost::weak_ptr<const Film> _film;
+ libdcp::Size _video_size;
+};
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 8bb43a91f..080d1eaf6 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -36,9 +36,7 @@ using std::list;
using std::stringstream;
using boost::shared_ptr;
-/** @param s Film that we are operating on.
- */
-Job::Job (shared_ptr<Film> f)
+Job::Job (shared_ptr<const Film> f)
: _film (f)
, _thread (0)
, _state (NEW)
@@ -95,7 +93,7 @@ Job::run_wrapper ()
set_state (FINISHED_ERROR);
set_error (
e.what (),
- _("It is not known what caused this error. The best idea is to report the problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)")
+ _("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)")
);
} catch (...) {
@@ -104,7 +102,7 @@ Job::run_wrapper ()
set_state (FINISHED_ERROR);
set_error (
_("Unknown error"),
- _("It is not known what caused this error. The best idea is to report the problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)")
+ _("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)")
);
}
@@ -204,7 +202,7 @@ Job::set_progress (float p)
boost::this_thread::interruption_point ();
if (paused ()) {
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
}
}
diff --git a/src/lib/job.h b/src/lib/job.h
index 37fa56d20..791a9101b 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -21,8 +21,8 @@
* @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>
@@ -38,7 +38,7 @@ class Film;
class Job : public boost::enable_shared_from_this<Job>
{
public:
- Job (boost::shared_ptr<Film> s);
+ Job (boost::shared_ptr<const Film>);
virtual ~Job() {}
/** @return user-readable name of this job */
@@ -71,7 +71,7 @@ public:
void descend (float);
float overall_progress () const;
- /** Emitted by the JobManagerView from the UI thread */
+ /** Emitted from the UI thread when the job is finished */
boost::signals2::signal<void()> Finished;
protected:
@@ -91,8 +91,7 @@ protected:
void set_state (State);
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:
diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc
index 910597628..f96275467 100644
--- a/src/lib/job_manager.cc
+++ b/src/lib/job_manager.cc
@@ -126,7 +126,7 @@ JobManager::scheduler ()
}
}
- dvdomatic_sleep (1);
+ dcpomatic_sleep (1);
}
}
diff --git a/src/lib/log.h b/src/lib/log.h
index 3a2cfcbfd..3ad6516c1 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.
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
deleted file mode 100644
index 4acb82afa..000000000
--- a/src/lib/matcher.cc
+++ /dev/null
@@ -1,224 +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"
-
-#include "i18n.h"
-
-using std::min;
-using std::cout;
-using std::list;
-using boost::shared_ptr;
-
-Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
- : Processor (log)
- , _sample_rate (sample_rate)
- , _frames_per_second (frames_per_second)
- , _video_frames (0)
- , _audio_frames (0)
- , _had_first_video (false)
- , _had_first_audio (false)
-{
-
-}
-
-void
-Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
-{
- _pixel_format = image->pixel_format ();
- _size = image->size ();
-
- _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size()));
-
- if (!_first_input || t < _first_input.get()) {
- _first_input = t;
- }
-
- bool const this_is_first_video = !_had_first_video;
- _had_first_video = true;
-
- if (!_had_first_audio) {
- /* No audio yet; we must postpone these data until we have some */
- _pending_video.push_back (VideoRecord (image, same, sub, t));
- } else if (this_is_first_video && _had_first_audio) {
- /* First video since we got audio */
- _pending_video.push_back (VideoRecord (image, same, sub, t));
- fix_start ();
- } else {
- /* Normal running */
-
- /* Difference between where this video is and where it should be */
- double const delta = t - _first_input.get() - _video_frames / _frames_per_second;
- double const one_frame = 1 / _frames_per_second;
-
- if (delta > one_frame) {
- /* Insert frames to make up the difference */
- int const extra = rint (delta / one_frame);
- for (int i = 0; i < extra; ++i) {
- repeat_last_video ();
- _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second));
- }
- }
-
- if (delta > -one_frame) {
- Video (image, same, sub);
- ++_video_frames;
- } else {
- /* We are omitting a frame to keep things right */
- _log->log (String::compose ("Frame removed at %1s; delta %2; first input was at %3", t, delta, _first_input.get()));
- }
-
- _last_image = image;
- _last_subtitle = sub;
- }
-}
-
-void
-Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t)
-{
- _channels = b->channels ();
-
- _log->log (String::compose (
- "Matcher audio (%1 frames) @ %2 [video=%3, audio=%4, pending_video=%5, pending_audio=%6]",
- b->frames(), t, _video_frames, _audio_frames, _pending_video.size(), _pending_audio.size()
- )
- );
-
- if (!_first_input || t < _first_input.get()) {
- _first_input = t;
- }
-
- bool const this_is_first_audio = !_had_first_audio;
- _had_first_audio = true;
-
- if (!_had_first_video) {
- /* No video yet; we must postpone these data until we have some */
- _pending_audio.push_back (AudioRecord (b, t));
- } else if (this_is_first_audio && _had_first_video) {
- /* First audio since we got video */
- _pending_audio.push_back (AudioRecord (b, t));
- fix_start ();
- } else {
- /* Normal running. We assume audio time stamps are consecutive, so there's no equivalent of
- the checking / insertion of repeat frames that there is for video.
- */
- Audio (b);
- _audio_frames += b->frames ();
- }
-}
-
-void
-Matcher::process_end ()
-{
- if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) {
- /* We won't do anything */
- return;
- }
-
- _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames",
- _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames));
-
- match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second));
-}
-
-void
-Matcher::fix_start ()
-{
- assert (!_pending_video.empty ());
- assert (!_pending_audio.empty ());
-
- _log->log (String::compose ("Fixing start; video at %1, audio at %2", _pending_video.front().time, _pending_audio.front().time));
-
- match (_pending_video.front().time - _pending_audio.front().time);
-
- for (list<VideoRecord>::iterator i = _pending_video.begin(); i != _pending_video.end(); ++i) {
- process_video (i->image, i->same, i->subtitle, i->time);
- }
-
- _pending_video.clear ();
-
- for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
- process_audio (i->audio, i->time);
- }
-
- _pending_audio.clear ();
-}
-
-void
-Matcher::match (double extra_video_needed)
-{
- _log->log (String::compose ("Match %1", extra_video_needed));
-
- if (extra_video_needed > 0) {
-
- /* Emit black video frames */
-
- int const black_video_frames = ceil (extra_video_needed * _frames_per_second);
-
- _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames));
-
- shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
- black->make_black ();
- for (int i = 0; i < black_video_frames; ++i) {
- Video (black, i != 0, shared_ptr<Subtitle>());
- ++_video_frames;
- }
-
- extra_video_needed -= black_video_frames / _frames_per_second;
- }
-
- if (extra_video_needed < 0) {
-
- /* Emit silence */
-
- int64_t to_do = -extra_video_needed * _sample_rate;
- _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do));
-
- /* Do things in half second blocks as I think there may be limits
- to what FFmpeg (and in particular the resampler) can cope with.
- */
- int64_t const block = _sample_rate / 2;
- shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
- b->make_silent ();
-
- 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;
- }
- }
-}
-
-void
-Matcher::repeat_last_video ()
-{
- if (!_last_image) {
- shared_ptr<Image> im (new SimpleImage (_pixel_format.get(), _size.get(), true));
- im->make_black ();
- _last_image = im;
- }
-
- Video (_last_image, true, _last_subtitle);
- ++_video_frames;
-}
-
diff --git a/src/lib/matcher.h b/src/lib/matcher.h
deleted file mode 100644
index 61fd81436..000000000
--- a/src/lib/matcher.h
+++ /dev/null
@@ -1,77 +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"
-
-class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource
-{
-public:
- Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second);
- void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
- void process_audio (boost::shared_ptr<const AudioBuffers>, double);
- void process_end ();
-
-private:
- void fix_start ();
- void match (double);
- void repeat_last_video ();
-
- int _sample_rate;
- float _frames_per_second;
- int _video_frames;
- int64_t _audio_frames;
- boost::optional<AVPixelFormat> _pixel_format;
- boost::optional<libdcp::Size> _size;
- boost::optional<int> _channels;
-
- struct VideoRecord {
- VideoRecord (boost::shared_ptr<const Image> i, bool s, boost::shared_ptr<Subtitle> u, double t)
- : image (i)
- , same (s)
- , subtitle (u)
- , time (t)
- {}
-
- boost::shared_ptr<const Image> image;
- bool same;
- boost::shared_ptr<Subtitle> subtitle;
- double time;
- };
-
- struct AudioRecord {
- AudioRecord (boost::shared_ptr<const AudioBuffers> a, double t)
- : audio (a)
- , time (t)
- {}
-
- boost::shared_ptr<const AudioBuffers> audio;
- double time;
- };
-
- std::list<VideoRecord> _pending_video;
- std::list<AudioRecord> _pending_audio;
-
- boost::optional<double> _first_input;
- boost::shared_ptr<const Image> _last_image;
- boost::shared_ptr<Subtitle> _last_subtitle;
-
- bool _had_first_video;
- bool _had_first_audio;
-};
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644
index 000000000..9969fbf9e
--- /dev/null
+++ b/src/lib/player.cc
@@ -0,0 +1,499 @@
+/*
+ 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 "imagemagick_decoder.h"
+#include "imagemagick_content.h"
+#include "sndfile_decoder.h"
+#include "sndfile_content.h"
+#include "playlist.h"
+#include "job.h"
+#include "image.h"
+#include "ratio.h"
+#include "resampler.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->start ())
+ , audio_position (c->start ())
+ {}
+
+ Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+ : content (c)
+ , decoder (d)
+ , video_position (c->start ())
+ , audio_position (c->start ())
+ {}
+
+ 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<ImageMagickContent> (p.content)) {
+ s << "\timagemagick";
+ } else if (dynamic_pointer_cast<SndfileContent> (p.content)) {
+ s << "\tsndfile ";
+ }
+
+ s << " at " << p.content->start() << " 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_buffers (f->dcp_audio_channels(), 0)
+{
+ _playlist->Changed.connect (bind (&Player::playlist_changed, this));
+ _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
+ 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 (dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+ if ((*i)->video_position < earliest_t) {
+ earliest_t = (*i)->video_position;
+ earliest = *i;
+ type = VIDEO;
+ }
+ }
+
+ if (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.\n";
+#endif
+ emit_black ();
+ } else {
+#ifdef DEBUG_PLAYER
+ cout << "Pass " << *earliest << "\n";
+#endif
+ earliest->decoder->pass ();
+ }
+ break;
+
+ case AUDIO:
+ if (earliest_t > _audio_position) {
+#ifdef DEBUG_PLAYER
+ cout << "no audio here; emitting silence.\n";
+#endif
+ emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+ } else {
+#ifdef DEBUG_PLAYER
+ cout << "Pass " << *earliest << "\n";
+#endif
+ earliest->decoder->pass ();
+ }
+ break;
+ }
+
+#ifdef DEBUG_PLAYER
+ cout << "\tpost pass " << _video_position << " " << _audio_position << "\n";
+#endif
+
+ return false;
+}
+
+void
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, 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->dcp_video_frame_rate());
+ if (frc.skip && (frame % 2) == 1) {
+ return;
+ }
+
+ image = image->crop (content->crop(), true);
+
+ libdcp::Size const image_size = content->ratio()->size (_video_container_size);
+
+ image = image->scale_and_convert_to_rgb (image_size, _film->scaler(), true);
+
+#if 0
+ if (film->with_subtitles ()) {
+ shared_ptr<Subtitle> sub;
+ if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
+ sub = _timed_subtitle->subtitle ();
+ }
+
+ if (sub) {
+ dcpomatic::Rect const tx = subtitle_transformed_area (
+ float (image_size.width) / content->video_size().width,
+ float (image_size.height) / content->video_size().height,
+ sub->area(), film->subtitle_offset(), film->subtitle_scale()
+ );
+
+ shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true);
+ image->alpha_blend (im, tx.position());
+ }
+ }
+#endif
+
+ 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 SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+ im->make_black ();
+ im->copy (image, Position ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
+ image = im;
+ }
+
+ Time time = content->start() + (frame * frc.factor() * TIME_HZ / _film->dcp_video_frame_rate());
+
+ Video (image, same, time);
+ time += TIME_HZ / _film->dcp_video_frame_rate();
+
+ if (frc.repeat) {
+ Video (image, true, time);
+ time += TIME_HZ / _film->dcp_video_frame_rate();
+ }
+
+ _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);
+
+ if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
+ audio = resampler(content)->run (audio);
+ }
+
+ /* Remap channels */
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->dcp_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) {
+ dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+ }
+
+ /* The time of this audio may indicate that some of our buffered audio is not going to
+ be added to any more, so it can be emitted.
+ */
+
+ Time const time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate());
+
+ if (time > _audio_position) {
+ /* We can emit some audio from our buffers */
+ OutputAudioFrame const N = _film->time_to_audio_frames (time - _audio_position);
+ assert (N <= _audio_buffers.frames());
+ shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
+ emit->copy_from (&_audio_buffers, N, 0, 0);
+ Audio (emit, _audio_position);
+ _audio_position = piece->audio_position = time + _film->audio_frames_to_time (N);
+
+ /* And remove it from our buffers */
+ if (_audio_buffers.frames() > N) {
+ _audio_buffers.move (N, 0, _audio_buffers.frames() - N);
+ }
+ _audio_buffers.set_frames (_audio_buffers.frames() - N);
+ }
+
+ /* Now accumulate the new audio into our buffers */
+ _audio_buffers.ensure_size (_audio_buffers.frames() + audio->frames());
+ _audio_buffers.accumulate_frames (audio.get(), 0, 0, audio->frames ());
+ _audio_buffers.set_frames (_audio_buffers.frames() + audio->frames());
+}
+
+void
+Player::flush ()
+{
+ if (_audio_buffers.frames() > 0) {
+ shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), _audio_buffers.frames()));
+ emit->copy_from (&_audio_buffers, _audio_buffers.frames(), 0, 0);
+ Audio (emit, _audio_position);
+ _audio_position += _film->audio_frames_to_time (_audio_buffers.frames ());
+ _audio_buffers.set_frames (0);
+ }
+
+ while (_video_position < _audio_position) {
+ emit_black ();
+ }
+
+ while (_audio_position < _video_position) {
+ emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ }
+
+}
+
+/** @return true on error */
+void
+Player::seek (Time t)
+{
+ 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->start ();
+ s = max (static_cast<Time> (0), s);
+ s = min (vc->length(), s);
+
+ FrameRateConversion frc (vc->video_frame_rate(), _film->dcp_video_frame_rate());
+ VideoContent::Frame f = s * _film->dcp_video_frame_rate() / (frc.factor() * TIME_HZ);
+ dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f);
+ }
+
+ /* XXX: don't seek audio because we don't need to... */
+}
+
+
+void
+Player::seek_back ()
+{
+
+}
+
+void
+Player::setup_pieces ()
+{
+ list<shared_ptr<Piece> > old_pieces = _pieces;
+
+ _pieces.clear ();
+
+ Playlist::ContentList content = _playlist->content ();
+ sort (content.begin(), content.end(), ContentSorter ());
+
+ for (Playlist::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));
+ fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
+
+ piece->decoder = fd;
+ }
+
+ shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
+ if (ic) {
+ shared_ptr<ImageMagickDecoder> id;
+
+ /* See if we can re-use an old ImageMagickDecoder */
+ for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
+ shared_ptr<ImageMagickDecoder> imd = dynamic_pointer_cast<ImageMagickDecoder> ((*j)->decoder);
+ if (imd && imd->content() == ic) {
+ id = imd;
+ }
+ }
+
+ if (!id) {
+ id.reset (new ImageMagickDecoder (_film, ic));
+ id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
+ }
+
+ piece->decoder = id;
+ }
+
+ 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 p)
+{
+ shared_ptr<Content> c = w.lock ();
+ if (!c) {
+ return;
+ }
+
+ if (p == ContentProperty::START || p == ContentProperty::LENGTH) {
+ _have_valid_pieces = false;
+ }
+}
+
+void
+Player::playlist_changed ()
+{
+ _have_valid_pieces = false;
+}
+
+void
+Player::set_video_container_size (libdcp::Size s)
+{
+ _video_container_size = s;
+ _black_frame.reset (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+ _black_frame->make_black ();
+}
+
+shared_ptr<Resampler>
+Player::resampler (shared_ptr<AudioContent> c)
+{
+ map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
+ if (i != _resamplers.end ()) {
+ return i->second;
+ }
+
+ 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 ()
+{
+ /* XXX: use same here */
+ Video (_black_frame, false, _video_position);
+ _video_position += _film->video_frames_to_time (1);
+}
+
+void
+Player::emit_silence (OutputAudioFrame most)
+{
+ OutputAudioFrame N = min (most, _film->dcp_audio_frame_rate() / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->dcp_audio_channels(), N));
+ silence->make_silent ();
+ Audio (silence, _audio_position);
+ _audio_position += _film->audio_frames_to_time (N);
+}
+
+
+
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644
index 000000000..bbdb14ec2
--- /dev/null
+++ b/src/lib/player.h
@@ -0,0 +1,105 @@
+/*
+ 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 "audio_buffers.h"
+#include "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:
+ Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
+
+ void disable_video ();
+ void disable_audio ();
+
+ bool pass ();
+ void seek (Time);
+ void seek_back ();
+
+ 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 true if the image is the same as the last one that was emitted.
+ * Third parameter is the time.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
+
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+
+private:
+
+ void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ void setup_pieces ();
+ void playlist_changed ();
+ void content_changed (boost::weak_ptr<Content>, int);
+ void do_seek (Time, bool);
+ void flush ();
+ void emit_black ();
+ void emit_silence (OutputAudioFrame);
+ boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>);
+
+ 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;
+
+ AudioBuffers _audio_buffers;
+
+ libdcp::Size _video_container_size;
+ boost::shared_ptr<Image> _black_frame;
+ std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
+};
+
+#endif
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
new file mode 100644
index 000000000..995067b66
--- /dev/null
+++ b/src/lib/playlist.cc
@@ -0,0 +1,308 @@
+/*
+ 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 "imagemagick_decoder.h"
+#include "imagemagick_content.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 boost::optional;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
+
+Playlist::Playlist ()
+ : _loop (1)
+ , _sequence_video (true)
+ , _sequencing_video (false)
+{
+
+}
+
+Playlist::Playlist (shared_ptr<const Playlist> other)
+ : _loop (other->_loop)
+{
+ for (ContentList::const_iterator i = other->_content.begin(); i != other->_content.end(); ++i) {
+ _content.push_back ((*i)->clone ());
+ }
+}
+
+Playlist::~Playlist ()
+{
+ _content.clear ();
+ reconnect ();
+}
+
+void
+Playlist::content_changed (weak_ptr<Content> c, int p)
+{
+ if (p == ContentProperty::LENGTH && _sequence_video && !_sequencing_video) {
+ _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_start (last);
+ last = (*i)->end ();
+ }
+
+ _sequencing_video = false;
+ }
+
+ ContentChanged (c, p);
+}
+
+string
+Playlist::video_digest () const
+{
+ string t;
+
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ if (!dynamic_pointer_cast<const VideoContent> (*i)) {
+ continue;
+ }
+
+ t += (*i)->digest ();
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+ if (fc && fc->subtitle_stream()) {
+ t += fc->subtitle_stream()->id;
+ }
+ }
+
+ t += lexical_cast<string> (_loop);
+
+ 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) {
+ string const type = (*i)->string_child ("Type");
+
+ boost::shared_ptr<Content> content;
+
+ if (type == "FFmpeg") {
+ content.reset (new FFmpegContent (film, *i));
+ } else if (type == "ImageMagick") {
+ content.reset (new ImageMagickContent (film, *i));
+ } else if (type == "Sndfile") {
+ content.reset (new SndfileContent (film, *i));
+ }
+
+ _content.push_back (content);
+ }
+
+ reconnect ();
+ _loop = node->number_child<int> ("Loop");
+ _sequence_video = node->bool_child ("SequenceVideo");
+}
+
+/** @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"));
+ }
+
+ node->add_child("Loop")->add_child_text(lexical_cast<string> (_loop));
+ node->add_child("SequenceVideo")->add_child_text(_sequence_video ? "1" : "0");
+}
+
+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::set_loop (int l)
+{
+ _loop = l;
+ 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, bailing early if we hit an exact match */
+ 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;
+ }
+
+ this_error += 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)));
+ }
+}
+
+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->start() < b->start();
+}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
new file mode 100644
index 000000000..2d243fe8f
--- /dev/null
+++ b/src/lib/playlist.h
@@ -0,0 +1,104 @@
+/*
+ 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 ImageMagickContent;
+class ImageMagickDecoder;
+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. At the moment
+ * the ordering is implicit; video content is placed sequentially, and audio content is taken
+ * from the video unless any sound-only files are present. If sound-only files exist, they
+ * are played simultaneously (i.e. they can be split up into multiple files for different channels)
+ */
+
+struct ContentSorter
+{
+ bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b);
+};
+
+class Playlist
+{
+public:
+ Playlist ();
+ Playlist (boost::shared_ptr<const 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>);
+
+ bool has_subtitles () const;
+
+ typedef std::vector<boost::shared_ptr<Content> > ContentList;
+
+ ContentList content () const {
+ return _content;
+ }
+
+ std::string video_digest () const;
+
+ int loop () const {
+ return _loop;
+ }
+
+ void set_loop (int l);
+
+ Time length () const;
+ int best_dcp_frame_rate () const;
+ Time video_end () const;
+
+ void set_sequence_video (bool);
+
+ mutable boost::signals2::signal<void ()> Changed;
+ mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+
+private:
+ void content_changed (boost::weak_ptr<Content>, int);
+ void reconnect ();
+
+ ContentList _content;
+ int _loop;
+ 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
index 39b6dfceb..7a07645b7 100644
--- a/src/lib/po/es_ES.po
+++ b/src/lib/po/es_ES.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: LIBDVDOMATIC\n"
+"Project-Id-Version: LIBDCPOMATIC\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-04-02 19:10-0500\n"
@@ -254,10 +254,10 @@ msgstr "Horizontal deblocking filter A"
#: src/lib/job.cc:97 src/lib/job.cc:106
msgid ""
"It is not known what caused this error. The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
+"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
msgstr ""
"Error desconocido. La mejor idea es informar del problema a la lista de "
-"correo de DVD-O-matic (dvdomatic@carlh.net)"
+"correo de DCP-o-matic (dcpomatic@carlh.net)"
#: src/lib/filter.cc:82
msgid "Kernel deinterlacer"
diff --git a/src/lib/po/fr_FR.po b/src/lib/po/fr_FR.po
index 9dcbd42c2..f68631bbd 100644
--- a/src/lib/po/fr_FR.po
+++ b/src/lib/po/fr_FR.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVD-o-matic FRENCH\n"
+"Project-Id-Version: DCP-o-matic FRENCH\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-05-21 10:30+0100\n"
@@ -249,13 +249,10 @@ msgstr "Filtre dé-bloc horizontal"
msgid "Horizontal deblocking filter A"
msgstr "Filtre dé-bloc horizontal"
-#: src/lib/job.cc:97 src/lib/job.cc:106
-msgid ""
-"It is not known what caused this error. The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
-msgstr ""
-"Erreur indéterminée. Merci de rapporter le problème à la liste DVD-o-matic "
-"(dvdomatic@carlh.net)"
+#: src/lib/job.cc:97
+#: src/lib/job.cc:106
+msgid "It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
+msgstr "Erreur indéterminée. Merci de rapporter le problème à la liste DCP-o-matic (dcpomatic@carlh.net)"
#: src/lib/filter.cc:82
msgid "Kernel deinterlacer"
diff --git a/src/lib/po/it_IT.po b/src/lib/po/it_IT.po
index 9671c66a7..0ddfa7721 100644
--- a/src/lib/po/it_IT.po
+++ b/src/lib/po/it_IT.po
@@ -251,10 +251,10 @@ msgstr "Filtro A sblocco orizzontale"
#: src/lib/job.cc:97 src/lib/job.cc:106
msgid ""
"It is not known what caused this error. The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
+"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
msgstr ""
"Non sappiamo cosa ha causato questo errore. La cosa migliore è inviare un "
-"report del problema alla mailing list di DVD-o-matic (dvdomatic@carlh.net)"
+"report del problema alla mailing list di DCP-o-matic (dcpomatic@carlh.net)"
#: src/lib/filter.cc:82
msgid "Kernel deinterlacer"
diff --git a/src/lib/po/sv_SE.po b/src/lib/po/sv_SE.po
index 9391801dc..c1c62ca41 100644
--- a/src/lib/po/sv_SE.po
+++ b/src/lib/po/sv_SE.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVD-o-matic\n"
+"Project-Id-Version: DCP-o-matic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-04-10 15:35+0100\n"
@@ -252,10 +252,10 @@ msgstr "Filter för horisontal kantighetsutjämning A"
#: src/lib/job.cc:97 src/lib/job.cc:106
msgid ""
"It is not known what caused this error. The best idea is to report the "
-"problem to the DVD-o-matic mailing list (dvdomatic@carlh.net)"
+"problem to the DCP-o-matic mailing list (dcpomatic@carlh.net)"
msgstr ""
"Det är inte känt vad som orsakade detta fel. Bästa sättet att rapportera "
-"problemet är till DVD-o-matics mejl-lista (dvdomatic@carlh.net)"
+"problemet är till DCP-o-matics mejl-lista (dcpomatic@carlh.net)"
#: src/lib/filter.cc:82
msgid "Kernel deinterlacer"
diff --git a/src/lib/processor.h b/src/lib/processor.h
deleted file mode 100644
index 603239f8f..000000000
--- a/src/lib/processor.h
+++ /dev/null
@@ -1,115 +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 (boost::shared_ptr<Log> log)
- : _log (log)
- {}
-
- virtual ~Processor() {}
-
- /** Will be called at the end of a processing run */
- virtual void process_end () {}
-
-protected:
- boost::shared_ptr<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 (boost::shared_ptr<Log> log)
- : Processor (log)
- {}
-};
-
-class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink
-{
-public:
- TimedAudioVideoProcessor (boost::shared_ptr<Log> log)
- : Processor (log)
- {}
-};
-
-
-/** @class AudioProcessor
- * @brief A processor which handles just audio data.
- */
-class AudioProcessor : public Processor, public AudioSource, public AudioSink
-{
-public:
- /** Construct an AudioProcessor.
- * @param log Log to write to.
- */
- AudioProcessor (boost::shared_ptr<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 (boost::shared_ptr<Log> log)
- : Processor (log)
- {}
-};
-
-class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink
-{
-public:
- TimedVideoProcessor (boost::shared_ptr<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..5480eee12
--- /dev/null
+++ b/src/lib/ratio.h
@@ -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.
+
+*/
+
+#ifndef DCPOMATIC_RATIO_H
+#define DCPOMATIC_RATIO_H
+
+#include <vector>
+#include <libdcp/util.h>
+
+class Ratio
+{
+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/resampler.cc b/src/lib/resampler.cc
new file mode 100644
index 000000000..1235b9038
--- /dev/null
+++ b/src/lib/resampler.cc
@@ -0,0 +1,61 @@
+extern "C" {
+#include "libavutil/channel_layout.h"
+}
+#include "resampler.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+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);
+}
+
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
+{
+ /* 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 resampled;
+}
diff --git a/src/lib/resampler.h b/src/lib/resampler.h
new file mode 100644
index 000000000..cda718934
--- /dev/null
+++ b/src/lib/resampler.h
@@ -0,0 +1,21 @@
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libswresample/swresample.h>
+}
+
+class AudioBuffers;
+
+class Resampler
+{
+public:
+ Resampler (int, int, int);
+ ~Resampler ();
+
+ boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
+
+private:
+ SwrContext* _swr_context;
+ int _in_rate;
+ int _out_rate;
+ int _channels;
+};
diff --git a/src/lib/scaler.h b/src/lib/scaler.h
index c80f4b7db..a736e92de 100644
--- a/src/lib/scaler.h
+++ b/src/lib/scaler.h
@@ -21,8 +21,8 @@
* @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>
diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc
index a9fdfefda..8cde44f02 100644
--- a/src/lib/scp_dcp_job.cc
+++ b/src/lib/scp_dcp_job.cc
@@ -96,7 +96,7 @@ public:
};
-SCPDCPJob::SCPDCPJob (shared_ptr<Film> f)
+SCPDCPJob::SCPDCPJob (shared_ptr<const Film> f)
: Job (f)
, _status (_("Waiting"))
{
diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h
index 08d8e2c78..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>);
+ 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 9c5a77f68..5ca04c692 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -29,6 +29,7 @@
#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"
@@ -51,6 +52,19 @@ using boost::bind;
using boost::scoped_array;
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.
@@ -68,15 +82,6 @@ ServerDescription::create_from_metadata (string v)
return new 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 << N_(" ") << _threads;
- return s.str ();
-}
-
Server::Server (shared_ptr<Log> log)
: _log (log)
{
@@ -93,45 +98,25 @@ Server::process (shared_ptr<Socket> socket)
stringstream s (buffer.get());
multimap<string, string> kv = read_key_value (s);
- if (get_required_string (kv, N_("encode")) != N_("please")) {
+ if (get_required_string (kv, "encode") != "please") {
return -1;
}
- libdcp::Size in_size (get_required_int (kv, N_("input_width")), get_required_int (kv, N_("input_height")));
- int pixel_format_int = get_required_int (kv, N_("input_pixel_format"));
- libdcp::Size out_size (get_required_int (kv, N_("output_width")), get_required_int (kv, N_("output_height")));
- int padding = get_required_int (kv, N_("padding"));
- int subtitle_offset = get_required_int (kv, N_("subtitle_offset"));
- float subtitle_scale = get_required_float (kv, N_("subtitle_scale"));
- string scaler_id = get_required_string (kv, N_("scaler"));
- int frame = get_required_int (kv, N_("frame"));
- int frames_per_second = get_required_int (kv, N_("frames_per_second"));
- string post_process = get_optional_string (kv, N_("post_process"));
- int colour_lut_index = get_required_int (kv, N_("colour_lut"));
- int j2k_bandwidth = get_required_int (kv, N_("j2k_bandwidth"));
- Position subtitle_position (get_optional_int (kv, N_("subtitle_x")), get_optional_int (kv, N_("subtitle_y")));
- libdcp::Size subtitle_size (get_optional_int (kv, N_("subtitle_width")), get_optional_int (kv, N_("subtitle_height")));
+ libdcp::Size size (get_required_int (kv, "width"), get_required_int (kv, "height"));
+ int frame = get_required_int (kv, "frame");
+ int frames_per_second = get_required_int (kv, "frames_per_second");
+ int colour_lut_index = get_required_int (kv, "colour_lut");
+ int j2k_bandwidth = get_required_int (kv, "j2k_bandwidth");
/* 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));
+ shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, true));
image->read_from_socket (socket);
- 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));
- }
-
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, frame, frames_per_second, colour_lut_index, j2k_bandwidth, _log
);
shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
diff --git a/src/lib/server.h b/src/lib/server.h
index 89aeca626..398401a55 100644
--- a/src/lib/server.h
+++ b/src/lib/server.h
@@ -26,10 +26,15 @@
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/thread/condition.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.
*/
@@ -44,6 +49,8 @@ public:
, _threads (t)
{}
+ ServerDescription (boost::shared_ptr<const cxml::Node>);
+
/** @return server's host name or IP address in string form */
std::string host_name () const {
return _host_name;
@@ -62,7 +69,7 @@ public:
_threads = t;
}
- std::string as_metadata () const;
+ void as_xml (xmlpp::Node *) const;
static ServerDescription * create_from_metadata (std::string v);
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
new file mode 100644
index 000000000..beee7cd9d
--- /dev/null
+++ b/src/lib/sndfile_content.cc
@@ -0,0 +1,160 @@
+/*
+ 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 "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_channels = node->number_child<int> ("AudioChannels");
+ _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+ _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
+ _audio_mapping = AudioMapping (node->node_child ("AudioMapping"));
+}
+
+string
+SndfileContent::summary () const
+{
+ return String::compose (_("Sound file: %1"), file().filename().string());
+}
+
+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");
+}
+
+shared_ptr<Content>
+SndfileContent::clone () const
+{
+ return shared_ptr<Content> (new SndfileContent (*this));
+}
+
+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);
+
+ /* XXX: do this in signal_changed...? */
+ _audio_mapping = AudioMapping (_audio_channels);
+ 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> (_audio_frame_rate));
+ _audio_mapping.as_xml (node->add_child("AudioMapping"));
+}
+
+Time
+SndfileContent::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->dcp_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..876d66088
--- /dev/null
+++ b/src/lib/sndfile_content.h
@@ -0,0 +1,78 @@
+/*
+ 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/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 information () const;
+ void as_xml (xmlpp::Node *) const;
+ boost::shared_ptr<Content> clone () const;
+ Time 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;
+};
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
index 7e9e67d0f..80a6afd2b 100644
--- a/src/lib/sndfile_decoder.cc
+++ b/src/lib/sndfile_decoder.cc
@@ -19,157 +19,101 @@
#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::stringstream;
using std::min;
using std::cout;
using boost::shared_ptr;
-using boost::optional;
-SndfileDecoder::SndfileDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , AudioDecoder (f, o)
- , _done (0)
- , _frames (0)
+SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
+ : Decoder (f)
+ , AudioDecoder (f)
+ , _sndfile_content (c)
+ , _deinterleave_buffer (0)
{
- _done = 0;
- _frames = 0;
-
- 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;
+ _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info);
+ if (!_sndfile) {
+ throw DecodeError (_("could not open audio file for reading"));
}
- bool first = true;
-
- 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"));
- }
+ _done = 0;
+ _remaining = _info.frames;
+}
- if (info.channels != 1) {
- throw DecodeError (_("external audio files must be mono"));
- }
-
- _sndfiles.push_back (s);
-
- if (first) {
- shared_ptr<SndfileStream> st (
- new SndfileStream (
- info.samplerate, av_get_default_channel_layout (N)
- )
- );
-
- _audio_streams.push_back (st);
- _audio_stream = st;
- _frames = info.frames;
- first = false;
- } else {
- if (info.frames != _frames) {
- throw DecodeError (_("external audio files have differing lengths"));
- }
- }
- }
- }
+SndfileDecoder::~SndfileDecoder ()
+{
+ sf_close (_sndfile);
+ delete[] _deinterleave_buffer;
}
-bool
+void
SndfileDecoder::pass ()
{
- if (_audio_streams.empty ()) {
- return true;
- }
-
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
*/
- sf_count_t const block = _audio_stream->sample_rate() / 2;
- shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block));
- sf_count_t const this_time = min (block, _frames - _done);
- 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), this_time);
- }
- }
+ sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2;
+ sf_count_t const this_time = min (block, _remaining);
- audio->set_frames (this_time);
- Audio (audio, double(_done) / _audio_stream->sample_rate());
- _done += this_time;
-
- return (_done == _frames);
-}
-
-SndfileDecoder::~SndfileDecoder ()
-{
- for (size_t i = 0; i < _sndfiles.size(); ++i) {
- if (_sndfiles[i]) {
- sf_close (_sndfiles[i]);
+ 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, double(_done) / audio_frame_rate());
+ _done += this_time;
+ _remaining -= this_time;
}
-shared_ptr<SndfileStream>
-SndfileStream::create ()
-{
- return shared_ptr<SndfileStream> (new SndfileStream);
-}
-
-shared_ptr<SndfileStream>
-SndfileStream::create (string t, optional<int> v)
+int
+SndfileDecoder::audio_channels () const
{
- if (!v) {
- /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
- return shared_ptr<SndfileStream> ();
- }
-
- stringstream s (t);
- string type;
- s >> type;
- if (type != N_("external")) {
- return shared_ptr<SndfileStream> ();
- }
-
- return shared_ptr<SndfileStream> (new SndfileStream (t, v));
+ return _info.channels;
}
-SndfileStream::SndfileStream (string t, optional<int> v)
+AudioContent::Frame
+SndfileDecoder::audio_length () const
{
- assert (v);
-
- stringstream s (t);
- string type;
- s >> type >> _sample_rate >> _channel_layout;
+ return _info.frames;
}
-SndfileStream::SndfileStream ()
+int
+SndfileDecoder::audio_frame_rate () const
{
-
+ return _info.samplerate;
}
-string
-SndfileStream::to_string () const
+bool
+SndfileDecoder::done () const
{
- return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout);
+ return _audio_position >= _sndfile_content->audio_length ();
}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
index 9489cb5ec..77fa6d177 100644
--- a/src/lib/sndfile_decoder.h
+++ b/src/lib/sndfile_decoder.h
@@ -20,37 +20,27 @@
#include <sndfile.h>
#include "decoder.h"
#include "audio_decoder.h"
-#include "stream.h"
-class SndfileStream : public AudioStream
-{
-public:
- SndfileStream (int sample_rate, int64_t layout)
- : AudioStream (sample_rate, layout)
- {}
-
- std::string to_string () const;
-
- static boost::shared_ptr<SndfileStream> create ();
- static boost::shared_ptr<SndfileStream> create (std::string t, boost::optional<int> v);
-
-private:
- friend class stream_test;
-
- SndfileStream ();
- SndfileStream (std::string t, boost::optional<int> v);
-};
+class SndfileContent;
class SndfileDecoder : public AudioDecoder
{
public:
- SndfileDecoder (boost::shared_ptr<Film>, DecodeOptions);
+ SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
~SndfileDecoder ();
- bool pass ();
+ void pass ();
+ bool done () const;
+
+ int audio_channels () const;
+ AudioContent::Frame audio_length () const;
+ int audio_frame_rate () const;
private:
- std::vector<SNDFILE*> _sndfiles;
- sf_count_t _done;
- sf_count_t _frames;
+ 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..bdbe72ba2 100644
--- a/src/lib/sound_processor.h
+++ b/src/lib/sound_processor.h
@@ -21,8 +21,8 @@
* @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>
diff --git a/src/lib/stack.cpp b/src/lib/stack.cpp
index 20a5c5be7..a8183d344 100644
--- a/src/lib/stack.cpp
+++ b/src/lib/stack.cpp
@@ -1,5 +1,3 @@
-/** -*- c-basic-offset: 4; default-tab-width: 4; indent-tabs-mode: nil; -*- */
-
// Copyright 2007 Edd Dawson.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
diff --git a/src/lib/stack.hpp b/src/lib/stack.hpp
index 2b622d020..73a13bf85 100644
--- a/src/lib/stack.hpp
+++ b/src/lib/stack.hpp
@@ -1,5 +1,3 @@
-/** -*- c-basic-offset: 4; default-tab-width: 4; indent-tabs-mode: nil; -*- */
-
// Copyright 2007 Edd Dawson.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
deleted file mode 100644
index bfe7b5eb4..000000000
--- a/src/lib/stream.cc
+++ /dev/null
@@ -1,92 +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 "sndfile_decoder.h"
-
-#include "i18n.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 (N_("%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 = SndfileStream::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
index 5c2a0d0b5..7013f1d7d 100644
--- a/src/lib/subtitle.cc
+++ b/src/lib/subtitle.cc
@@ -27,8 +27,7 @@
#include "i18n.h"
-using namespace std;
-using namespace boost;
+using boost::shared_ptr;
using libdcp::Size;
/** Construct a TimedSubtitle. This is a subtitle image, position,
@@ -45,8 +44,8 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
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);
+ _from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+ _to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
@@ -80,9 +79,9 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub)
_subtitle.reset (new Subtitle (Position (rect->x, rect->y), image));
}
-/** @param t Time in seconds from the start of the source */
+/** @param t Time from the start of the source */
bool
-TimedSubtitle::displayed_at (double t) const
+TimedSubtitle::displayed_at (Time t) const
{
return t >= _from && t <= _to;
}
@@ -108,13 +107,13 @@ Subtitle::Subtitle (Position p, shared_ptr<Image> i)
* in the coordinate space of the source.
* @param subtitle_scale scaling factor to apply to the subtitle image.
*/
-dvdomatic::Rect
+dcpomatic::Rect
subtitle_transformed_area (
float target_x_scale, float target_y_scale,
- dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
+ dcpomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
)
{
- dvdomatic::Rect tx;
+ dcpomatic::Rect tx;
sub_area.y += subtitle_offset;
@@ -143,8 +142,8 @@ subtitle_transformed_area (
}
/** @return area that this subtitle takes up, in the original uncropped source's coordinate space */
-dvdomatic::Rect
+dcpomatic::Rect
Subtitle::area () const
{
- return dvdomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height);
+ return dcpomatic::Rect (_position.x, _position.y, _image->size().width, _image->size().height);
}
diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h
index e3a853695..1020397cc 100644
--- a/src/lib/subtitle.h
+++ b/src/lib/subtitle.h
@@ -23,7 +23,7 @@
#include <list>
#include <boost/shared_ptr.hpp>
-#include "util.h"
+#include "types.h"
struct AVSubtitle;
class Image;
@@ -46,17 +46,17 @@ public:
return _image;
}
- dvdomatic::Rect area () const;
+ dcpomatic::Rect area () const;
private:
Position _position;
boost::shared_ptr<Image> _image;
};
-dvdomatic::Rect
+dcpomatic::Rect
subtitle_transformed_area (
float target_x_scale, float target_y_scale,
- dvdomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
+ dcpomatic::Rect sub_area, int subtitle_offset, float subtitle_scale
);
/** A Subtitle class with details of the time over which it should be shown */
@@ -65,7 +65,7 @@ class TimedSubtitle
public:
TimedSubtitle (AVSubtitle const &);
- bool displayed_at (double t) const;
+ bool displayed_at (Time) const;
boost::shared_ptr<Subtitle> subtitle () const {
return _subtitle;
@@ -74,8 +74,8 @@ public:
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;
+ /** display from time from the start of the content */
+ Time _from;
+ /** display to time from the start of the content */
+ Time _to;
};
diff --git a/src/lib/timer.h b/src/lib/timer.h
index f509a7492..173d0d961 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>
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 234ebe051..6d5edd7c0 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -25,10 +25,8 @@
#include <iomanip>
#include "transcode_job.h"
#include "film.h"
-#include "format.h"
#include "transcoder.h"
#include "log.h"
-#include "encoder.h"
#include "i18n.h"
@@ -39,11 +37,9 @@ using std::setprecision;
using boost::shared_ptr;
/** @param s Film to use.
- * @param o Decode options.
*/
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o)
+TranscodeJob::TranscodeJob (shared_ptr<const Film> f)
: Job (f)
- , _decode_opt (o)
{
}
@@ -60,11 +56,9 @@ TranscodeJob::run ()
try {
_film->log()->log (N_("Transcode job starting"));
- _film->log()->log (String::compose (N_("Audio delay is %1ms"), _film->audio_delay()));
- _encoder.reset (new Encoder (_film));
- 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);
@@ -83,11 +77,11 @@ TranscodeJob::run ()
string
TranscodeJob::status () const
{
- if (!_encoder) {
+ if (!_transcoder) {
return _("0%");
}
- float const fps = _encoder->current_frames_per_second ();
+ float const fps = _transcoder->current_encoding_rate ();
if (fps == 0) {
return Job::status ();
}
@@ -106,24 +100,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->length()) {
+ if (fps == 0) {
return 0;
}
/* Compute approximate proposed length here, as it's only here that we need it */
- int length = _film->length().get();
- FrameRateConversion const frc (_film->source_frame_rate(), _film->dcp_frame_rate());
- if (frc.skip) {
- length /= 2;
- }
- /* If we are repeating it shouldn't affect transcode time, so don't take it into account */
-
- /* We assume that dcp_length() is valid, if it is set */
- int const left = length - _encoder->video_frames_out();
+ 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 9b69e4e65..9128206d2 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -23,9 +23,8 @@
#include <boost/shared_ptr.hpp>
#include "job.h"
-#include "options.h"
-class Encoder;
+class Transcoder;
/** @class TranscodeJob
* @brief A job which transcodes from one format to another.
@@ -33,16 +32,14 @@ class Encoder;
class TranscodeJob : public Job
{
public:
- TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od);
+ TranscodeJob (boost::shared_ptr<const Film> f);
std::string name () const;
void run ();
std::string status () const;
-protected:
+private:
int remaining_time () const;
-private:
- DecodeOptions _decode_opt;
- boost::shared_ptr<Encoder> _encoder;
+ boost::shared_ptr<Transcoder> _transcoder;
};
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index a202d440c..f4a52639a 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -28,107 +28,65 @@
#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 "trimmer.h"
+#include "player.h"
+#include "job.h"
using std::string;
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, bool same)
+{
+ shared_ptr<Encoder> e = encoder.lock ();
+ if (e) {
+ e->process_video (image, 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, 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))
+ , _player (f->player ())
+ , _encoder (new Encoder (f, j))
{
- assert (_encoder);
-
- shared_ptr<AudioStream> st = f->audio_stream();
- if (st && st->sample_rate ()) {
- _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate()));
- }
- _delay_line.reset (new DelayLine (f->log(), f->audio_delay() / 1000.0f));
- _gain.reset (new Gain (f->log(), f->audio_gain()));
-
- int const sr = st ? st->sample_rate() : 0;
- int const trim_start = f->trim_type() == Film::ENCODE ? f->trim_start() : 0;
- int const trim_end = f->trim_type() == Film::ENCODE ? f->trim_end() : 0;
- _trimmer.reset (new Trimmer (
- f->log(), trim_start, trim_end, f->length().get_value_or(0),
- sr, f->source_frame_rate(), f->dcp_frame_rate()
- ));
-
- /* Set up the decoder to use the film's set streams */
- _decoders.video->set_subtitle_stream (f->subtitle_stream ());
- if (f->audio_stream ()) {
- _decoders.audio->set_audio_stream (f->audio_stream ());
- }
-
- _decoders.video->connect_video (_delay_line);
- if (_matcher) {
- _delay_line->connect_video (_matcher);
- _matcher->connect_video (_trimmer);
- } else {
- _delay_line->connect_video (_trimmer);
- }
- _trimmer->connect_video (_encoder);
-
- _decoders.audio->connect_audio (_delay_line);
- if (_matcher) {
- _delay_line->connect_audio (_matcher);
- _matcher->connect_audio (_gain);
- } else {
- _delay_line->connect_audio (_gain);
- }
- _gain->connect_audio (_trimmer);
- _trimmer->connect_audio (_encoder);
+ _player->Video.connect (bind (video_proxy, _encoder, _1, _2));
+ _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 ();
-
- bool done[2] = { false, false };
-
- while (1) {
- if (!done[0]) {
- done[0] = _decoders.video->pass ();
- if (_job) {
- _decoders.video->set_progress (_job);
- }
- }
-
- if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
- done[1] = _decoders.audio->pass ();
- } else {
- done[1] = true;
- }
-
- if (done[0] && done[1]) {
- break;
- }
- }
-
- _delay_line->process_end ();
- if (_matcher) {
- _matcher->process_end ();
- }
- _gain->process_end ();
+ while (!_player->pass ()) {}
_encoder->process_end ();
}
+
+float
+Transcoder::current_encoding_rate () const
+{
+ return _encoder->current_encoding_rate ();
+}
+
+int
+Transcoder::video_frames_out () const
+{
+ return _encoder->video_frames_out ();
+}
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index f5b8ae6e3..b3c8f888b 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -17,28 +17,21 @@
*/
+#include "types.h"
+
/** @file src/transcoder.h
- * @brief A class which takes a Film and some Options, then uses those to transcode the 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"
-
class Film;
class Job;
class Encoder;
-class Matcher;
class VideoFilter;
-class Gain;
-class VideoDecoder;
-class AudioDecoder;
-class DelayLine;
-class Trimmer;
+class Player;
/** @class Transcoder
- * @brief A class which takes a Film and some Options, then uses those to transcode the film.
*
* A decoder is selected according to the content type, and the encoder can be specified
* as a parameter to the constructor.
@@ -47,27 +40,18 @@ class Transcoder
{
public:
Transcoder (
- boost::shared_ptr<Film> f,
- 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;
+ 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;
- boost::shared_ptr<Trimmer> _trimmer;
};
diff --git a/src/lib/types.cc b/src/lib/types.cc
new file mode 100644
index 000000000..78cb4cd64
--- /dev/null
+++ b/src/lib/types.cc
@@ -0,0 +1,56 @@
+/*
+ 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;
+
+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 other A Rect.
+ * @return The intersection of this with `other'.
+ */
+dcpomatic::Rect
+dcpomatic::Rect::intersection (Rect const & other) const
+{
+ 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
+ );
+}
+
+bool
+dcpomatic::Rect::contains (Position p) const
+{
+ return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
+}
diff --git a/src/lib/types.h b/src/lib/types.h
new file mode 100644
index 000000000..33f8239d8
--- /dev/null
+++ b/src/lib/types.h
@@ -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.
+
+*/
+
+#ifndef DCPOMATIC_TYPES_H
+#define DCPOMATIC_TYPES_H
+
+#include <vector>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <libdcp/util.h>
+
+class Content;
+
+typedef int64_t Time;
+#define TIME_MAX INT64_MAX
+#define TIME_HZ ((Time) 96000)
+typedef int64_t OutputAudioFrame;
+typedef int OutputVideoFrame;
+
+/** @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;
+};
+
+namespace dcpomatic {
+
+/** @struct Rect
+ * @brief A rectangle.
+ */
+struct Rect
+{
+ Rect ()
+ : x (0)
+ , y (0)
+ , width (0)
+ , height (0)
+ {}
+
+ Rect (int x_, int y_, int w_, int h_)
+ : x (x_)
+ , y (y_)
+ , width (w_)
+ , height (h_)
+ {}
+
+ int x;
+ int y;
+ int width;
+ int height;
+
+ Position position () const {
+ return Position (x, y);
+ }
+
+ libdcp::Size size () const {
+ return libdcp::Size (width, height);
+ }
+
+ Rect intersection (Rect const & other) const;
+
+ bool contains (Position) const;
+};
+
+}
+
+#endif
diff --git a/src/lib/ui_signaller.h b/src/lib/ui_signaller.h
index 0d19660bf..73db8bff8 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>
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 83980f828..d425fc8fe 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -27,7 +27,7 @@
#include <iostream>
#include <fstream>
#include <climits>
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
#endif
@@ -56,32 +56,37 @@ 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 "film.h"
-#ifdef DVDOMATIC_WINDOWS
+#include "ratio.h"
+#ifdef DCPOMATIC_WINDOWS
#include "stack.hpp"
#endif
#include "i18n.h"
-using std::cout;
using std::string;
using std::stringstream;
-using std::list;
+using std::setfill;
using std::ostream;
+using std::endl;
using std::vector;
+using std::hex;
+using std::setw;
using std::ifstream;
-using std::istream;
+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;
@@ -114,6 +119,12 @@ seconds_to_hms (int s)
return hms.str ();
}
+string
+time_to_hms (Time t)
+{
+ return seconds_to_hms (t / TIME_HZ);
+}
+
/** @param s Number of seconds.
* @return String containing an approximate description of s (e.g. "about 2 hours")
*/
@@ -150,7 +161,7 @@ seconds_to_approximate_hms (int s)
return ap.str ();
}
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
/** @param l Mangled C++ identifier.
* @return Demangled version.
*/
@@ -249,7 +260,7 @@ seconds (struct timeval t)
return t.tv_sec + (double (t.tv_usec) / 1e6);
}
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
{
dbg::stack s;
@@ -263,9 +274,9 @@ LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
* Must be called from the UI thread, if there is one.
*/
void
-dvdomatic_setup ()
+dcpomatic_setup ()
{
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
backtrace_file /= g_get_user_config_dir ();
backtrace_file /= "backtrace.txt";
SetUnhandledExceptionFilter(exception_handler);
@@ -273,7 +284,7 @@ dvdomatic_setup ()
avfilter_register_all ();
- Format::setup_formats ();
+ Ratio::setup_ratios ();
DCPContentType::setup_dcp_content_types ();
Scaler::setup_scalers ();
Filter::setup_filters ();
@@ -282,7 +293,7 @@ dvdomatic_setup ()
ui_thread = boost::this_thread::get_id ();
}
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
boost::filesystem::path
mo_path ()
{
@@ -297,9 +308,9 @@ mo_path ()
#endif
void
-dvdomatic_setup_gettext_i18n (string lang)
+dcpomatic_setup_gettext_i18n (string lang)
{
-#ifdef DVDOMATIC_POSIX
+#ifdef DCPOMATIC_POSIX
lang += ".UTF8";
#endif
@@ -315,15 +326,15 @@ dvdomatic_setup_gettext_i18n (string lang)
}
setlocale (LC_ALL, "");
- textdomain ("libdvdomatic");
+ textdomain ("libdcpomatic");
-#ifdef DVDOMATIC_WINDOWS
- bindtextdomain ("libdvdomatic", mo_path().string().c_str());
- bind_textdomain_codeset ("libdvdomatic", "UTF8");
+#ifdef DCPOMATIC_WINDOWS
+ bindtextdomain ("libdcpomatic", mo_path().string().c_str());
+ bind_textdomain_codeset ("libdcpomatic", "UTF8");
#endif
-#ifdef DVDOMATIC_POSIX
- bindtextdomain ("libdvdomatic", POSIX_LOCALE_PREFIX);
+#ifdef DCPOMATIC_POSIX
+ bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
#endif
}
@@ -384,11 +395,11 @@ md5_digest (void const * data, int size)
* @return MD5 digest of file's contents.
*/
string
-md5_digest (string file)
+md5_digest (boost::filesystem::path file)
{
- ifstream f (file.c_str(), std::ios::binary);
+ ifstream f (file.string().c_str(), std::ios::binary);
if (!f.good ()) {
- throw OpenFileError (file);
+ throw OpenFileError (file.string());
}
f.seekg (0, std::ios::end);
@@ -445,66 +456,11 @@ about_equal (float a, float b)
return (fabs (a - b) < 1e-4);
}
-class FrameRateCandidate
-{
-public:
- FrameRateCandidate (float source_, int dcp_)
- : source (source_)
- , dcp (dcp_)
- {}
-
- float source;
- int dcp;
-};
-
-int
-best_dcp_frame_rate (float source_fps)
-{
- 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, bailing early if we hit an exact match */
- float error = std::numeric_limits<float>::max ();
- optional<FrameRateCandidate> best;
- list<FrameRateCandidate>::iterator i = candidates.begin();
- while (i != candidates.end()) {
-
- if (about_equal (i->source, source_fps)) {
- best = *i;
- break;
- }
-
- float const e = fabs (i->source - source_fps);
- if (e < error) {
- error = e;
- best = *i;
- }
-
- ++i;
- }
-
- assert (best);
- return best->dcp;
-}
-
-/** @param An arbitrary sampling rate.
- * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz).
+/** @param An arbitrary audio frame rate.
+ * @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
*/
int
-dcp_audio_sample_rate (int fs)
+dcp_audio_frame_rate (int fs)
{
if (fs <= 48000) {
return 48000;
@@ -513,16 +469,6 @@ dcp_audio_sample_rate (int fs)
return 96000;
}
-bool operator== (Crop const & a, Crop const & b)
-{
- return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
-}
-
-bool operator!= (Crop const & a, Crop const & b)
-{
- return !(a == b);
-}
-
/** @param index Colour LUT index.
* @return Human-readable name.
*/
@@ -635,22 +581,6 @@ Socket::read_uint32 ()
return ntohl (v);
}
-/** @param other A Rect.
- * @return The intersection of this with `other'.
- */
-dvdomatic::Rect
-dvdomatic::Rect::intersection (Rect const & other) const
-{
- 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
- );
-}
-
/** Round a number up to the nearest multiple of another number.
* @param c Index.
* @param s Array of numbers to round, indexed by c.
@@ -767,151 +697,6 @@ 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));
- }
-}
-
-/* XXX: it's a shame that this is a copy-and-paste of the above;
- probably fixable with c++0x.
-*/
-AudioBuffers::AudioBuffers (boost::shared_ptr<const AudioBuffers> 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 ()
-{
- for (int i = 0; i < _channels; ++i) {
- delete[] _data[i];
- }
-
- delete[] _data;
-}
-
-/** @param c Channel index.
- * @return Buffer for this channel.
- */
-float*
-AudioBuffers::data (int c) const
-{
- assert (c >= 0 && c < _channels);
- return _data[c];
-}
-
-/** Set the number of frames that these AudioBuffers will report themselves
- * as having.
- * @param f Frames; must be less than or equal to the number of allocated frames.
- */
-void
-AudioBuffers::set_frames (int f)
-{
- assert (f <= _allocated_frames);
- _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;
- }
-}
-
-/** Copy data from another AudioBuffers to this one. All channels are copied.
- * @param from AudioBuffers to copy from; must have the same number of channels as this.
- * @param frames_to_copy Number of frames to copy.
- * @param read_offset Offset to read from in `from'.
- * @param write_offset Offset to write to in `to'.
- */
-void
-AudioBuffers::copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset)
-{
- assert (from->channels() == channels());
-
- assert (from);
- assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames);
- assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames);
-
- for (int i = 0; i < _channels; ++i) {
- memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float));
- }
-}
-
-/** Move audio data around.
- * @param from Offset to move from.
- * @param to Offset to move to.
- * @param frames Number of frames to move.
- */
-
-void
-AudioBuffers::move (int from, int to, int frames)
-{
- 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));
- }
-}
-
/** Trip an assert if the caller is not in the UI thread */
void
ensure_ui_thread ()
@@ -919,30 +704,17 @@ ensure_ui_thread ()
assert (boost::this_thread::get_id() == ui_thread);
}
-/** @param v Source video frame.
+/** @param v Content video frame.
* @param audio_sample_rate Source audio sample rate.
* @param frames_per_second Number of video frames per second.
* @return Equivalent number of audio frames for `v'.
*/
int64_t
-video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
{
return ((int64_t) v * audio_sample_rate / frames_per_second);
}
-/** @param f Filename.
- * @return true if this file is a still image, false if it is something else.
- */
-bool
-still_image_file (string f)
-{
- string ext = boost::filesystem::path(f).extension().string();
-
- transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-
- return (ext == N_(".tif") || ext == N_(".tiff") || ext == N_(".jpg") || ext == N_(".jpeg") || ext == N_(".png") || ext == N_(".bmp"));
-}
-
string
audio_channel_name (int c)
{
@@ -963,71 +735,6 @@ audio_channel_name (int c)
return channels[c];
}
-AudioMapping::AudioMapping (shared_ptr<const Film> f)
- : _source_channels (f->audio_stream() ? f->audio_stream()->channels() : 0)
- , _minimum_channels (f->minimum_audio_channels ())
-{
-
-}
-
-optional<libdcp::Channel>
-AudioMapping::source_to_dcp (int c) const
-{
- if (c >= _source_channels) {
- return optional<libdcp::Channel> ();
- }
-
- if (_source_channels == 1) {
- /* mono sources to centre */
- return libdcp::CENTRE;
- }
-
- return static_cast<libdcp::Channel> (c);
-}
-
-optional<int>
-AudioMapping::dcp_to_source (libdcp::Channel c) const
-{
- if (_source_channels == 1) {
- if (c == libdcp::CENTRE) {
- return 0;
- } else {
- return optional<int> ();
- }
- }
-
- if (static_cast<int> (c) >= _source_channels) {
- return optional<int> ();
- }
-
- return static_cast<int> (c);
-}
-
-/** @return minimum number of DCP channels that we can allow in this
- DCP, given the nature of the source.
-*/
-int
-AudioMapping::minimum_dcp_channels () const
-{
- if (_source_channels == 1) {
- /* The source is mono, so to put the mono channel into
- the centre we need to generate a 5.1 soundtrack.
- */
- return 6;
- }
-
- return _source_channels;
-}
-
-/** @return number of channels that there should be in the DCP, including
- * any silent padded ones.
- */
-int
-AudioMapping::dcp_channels () const
-{
- return max (_source_channels, _minimum_channels);
-}
-
FrameRateConversion::FrameRateConversion (float source, int dcp)
: skip (false)
, repeat (false)
diff --git a/src/lib/util.h b/src/lib/util.h
index c9e5bef16..7af8ffedf 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -22,8 +22,8 @@
* @brief Some utility functions and classes.
*/
-#ifndef DVDOMATIC_UTIL_H
-#define DVDOMATIC_UTIL_H
+#ifndef DCPOMATIC_UTIL_H
+#define DCPOMATIC_UTIL_H
#include <string>
#include <vector>
@@ -37,8 +37,10 @@ extern "C" {
#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(...)
@@ -53,23 +55,22 @@ class Scaler;
class Film;
extern std::string seconds_to_hms (int);
+extern std::string time_to_hms (Time);
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 dvdomatic_setup_gettext_i18n (std::string);
+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 (void const *, int);
extern void ensure_ui_thread ();
extern std::string audio_channel_name (int);
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
extern boost::filesystem::path mo_path ();
#endif
-typedef int SourceFrame;
-
struct FrameRateConversion
{
FrameRateConversion (float, int);
@@ -105,96 +106,8 @@ struct FrameRateConversion
std::string description;
};
-int best_dcp_frame_rate (float);
-
-enum ContentType {
- STILL, ///< content is still images
- VIDEO ///< content is a video
-};
-
-/** @struct Crop
- * @brief A description of the crop of an image or video.
- */
-struct Crop
-{
- Crop () : left (0), right (0), top (0), bottom (0) {}
-
- /** Number of pixels to remove from the left-hand side */
- int left;
- /** Number of pixels to remove from the right-hand side */
- int right;
- /** Number of pixels to remove from the top */
- int top;
- /** Number of pixels to remove from the bottom */
- int bottom;
-};
-
-extern bool operator== (Crop const & a, Crop const & b);
-extern bool operator!= (Crop const & a, Crop const & b);
-
-/** @struct Position
- * @brief A position.
- */
-struct Position
-{
- Position ()
- : x (0)
- , y (0)
- {}
-
- Position (int x_, int y_)
- : x (x_)
- , y (y_)
- {}
-
- /** x coordinate */
- int x;
- /** y coordinate */
- int y;
-};
-
-namespace dvdomatic
-{
-
-/** @struct Rect
- * @brief A rectangle.
- */
-struct Rect
-{
- Rect ()
- : x (0)
- , y (0)
- , width (0)
- , height (0)
- {}
-
- Rect (int x_, int y_, int w_, int h_)
- : x (x_)
- , y (y_)
- , width (w_)
- , height (h_)
- {}
-
- int x;
- int y;
- int width;
- int height;
-
- Position position () const {
- return Position (x, y);
- }
-
- libdcp::Size size () const {
- return libdcp::Size (width, height);
- }
-
- Rect intersection (Rect const & other) const;
-};
-
-}
-
extern std::string crop_string (Position, libdcp::Size);
-extern int dcp_audio_sample_rate (int);
+extern int dcp_audio_frame_rate (int);
extern std::string colour_lut_index_to_name (int index);
extern int stride_round_up (int, int const *, int);
extern int stride_lookup (int c, int const * stride);
@@ -207,7 +120,7 @@ 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.
@@ -241,68 +154,7 @@ private:
int _timeout;
};
-/** @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 ();
-
- 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 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;
-};
-
-class AudioMapping
-{
-public:
- AudioMapping (boost::shared_ptr<const Film>);
-
- boost::optional<libdcp::Channel> source_to_dcp (int c) const;
- boost::optional<int> dcp_to_source (libdcp::Channel c) const;
-
- int minimum_dcp_channels () const;
- int dcp_channels () const;
-
-private:
- int _source_channels;
- int _minimum_channels;
-};
-
-extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
-extern bool still_image_file (std::string);
+extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
class LocaleGuard
{
diff --git a/src/lib/version.h b/src/lib/version.h
index e1ec9067c..b70be8343 100644
--- a/src/lib/version.h
+++ b/src/lib/version.h
@@ -1,4 +1,4 @@
-extern char const * dvdomatic_version;
-extern char const * dvdomatic_git_commit;
-extern char const * dvdomatic_cxx_flags;
+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..3818fa792
--- /dev/null
+++ b/src/lib/video_content.cc
@@ -0,0 +1,224 @@
+/*
+ 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 "i18n.h"
+
+int const VideoContentProperty::VIDEO_SIZE = 0;
+int const VideoContentProperty::VIDEO_FRAME_RATE = 1;
+int const VideoContentProperty::VIDEO_CROP = 2;
+int const VideoContentProperty::VIDEO_RATIO = 3;
+
+using std::string;
+using std::stringstream;
+using std::setprecision;
+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)
+ , _ratio (Ratio::from_id ("185"))
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
+ : Content (f, p)
+ , _video_length (0)
+ , _video_frame_rate (0)
+ , _ratio (Ratio::from_id ("185"))
+{
+
+}
+
+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");
+ _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 ());
+ }
+}
+
+VideoContent::VideoContent (VideoContent const & o)
+ : Content (o)
+ , _video_length (o._video_length)
+ , _video_size (o._video_size)
+ , _video_frame_rate (o._video_frame_rate)
+ , _ratio (o._ratio)
+{
+
+}
+
+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("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 ());
+ }
+}
+
+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_crop (Crop c)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _crop = c;
+ }
+ signal_changed (VideoContentProperty::VIDEO_CROP);
+}
+
+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);
+}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
new file mode 100644
index 000000000..372cab3bd
--- /dev/null
+++ b/src/lib/video_content.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_VIDEO_CONTENT_H
+#define DCPOMATIC_VIDEO_CONTENT_H
+
+#include "content.h"
+
+class VideoExaminer;
+class Ratio;
+
+class VideoContentProperty
+{
+public:
+ static int const VIDEO_SIZE;
+ static int const VIDEO_FRAME_RATE;
+ static int const VIDEO_CROP;
+ static int const VIDEO_RATIO;
+};
+
+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>);
+ VideoContent (VideoContent const &);
+
+ void as_xml (xmlpp::Node *) const;
+ virtual std::string information () 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_crop (Crop);
+ void set_left_crop (int);
+ void set_right_crop (int);
+ void set_top_crop (int);
+ void set_bottom_crop (int);
+
+ 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;
+ }
+
+protected:
+ void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
+
+ VideoContent::Frame _video_length;
+
+private:
+ libdcp::Size _video_size;
+ float _video_frame_rate;
+ Crop _crop;
+ Ratio const * _ratio;
+};
+
+#endif
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index 16a076698..f61e63d4d 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -21,74 +21,42 @@
#include "subtitle.h"
#include "film.h"
#include "image.h"
-#include "log.h"
-#include "options.h"
-#include "job.h"
+#include "ratio.h"
#include "i18n.h"
using std::cout;
using boost::shared_ptr;
-using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o)
- : Decoder (f, o)
- , _video_frame (0)
- , _last_source_time (0)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
+ : Decoder (f)
+ , _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, bool same, 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 ();
- }
-
- Video (image, same, sub, t);
- ++_video_frame;
-
- _last_source_time = t;
+ Video (image, same, frame);
+ _video_position = frame + 1;
}
-/** Set up the current subtitle. This will be put onto frames that
- * fit within its time specification. s may be 0 to say that there
- * is no current subtitle.
+#if 0
+
+/** Called by subclasses when a subtitle is ready.
+ * s may be 0 to say that there is no current subtitle.
* @param s New current subtitle, or 0.
*/
void
-VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
+VideoDecoder::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));
+ _timed_subtitle->subtitle()->set_position (Position (p.x - _video_content->crop().left, p.y - _video_content->crop().top));
}
}
+#endif
-/** Set which stream of subtitles we should use from our source.
- * @param s Stream to use.
- */
-void
-VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
- _subtitle_stream = s;
-}
-
-void
-VideoDecoder::set_progress (Job* j) const
-{
- assert (j);
-
- if (_film->length()) {
- j->set_progress (float (_video_frame) / _film->length().get());
- }
-}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 6e4fd48c0..d24219d95 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -17,67 +17,33 @@
*/
-#ifndef DVDOMATIC_VIDEO_DECODER_H
-#define DVDOMATIC_VIDEO_DECODER_H
+#ifndef DCPOMATIC_VIDEO_DECODER_H
+#define DCPOMATIC_VIDEO_DECODER_H
-#include "video_source.h"
-#include "stream.h"
#include "decoder.h"
+#include "util.h"
-class VideoDecoder : public TimedVideoSource, public virtual Decoder
+class VideoContent;
+
+class VideoDecoder : public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
- /** @return video frames per second, or 0 if unknown */
- virtual float frames_per_second () const = 0;
- /** @return native size in pixels */
- virtual libdcp::Size native_size () const = 0;
- /** @return length (in source video frames), according to our content's header */
- virtual SourceFrame length () const = 0;
-
- 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;
+ VideoDecoder (boost::shared_ptr<const Film>);
- virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+ virtual void seek (VideoContent::Frame) = 0;
+ virtual void seek_back () = 0;
- void set_progress (Job *) const;
+ /** 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 the frame within our source.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<const Image>, 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>, bool, double);
- void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
- /** 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:
- int _video_frame;
- double _last_source_time;
-
- boost::shared_ptr<TimedSubtitle> _timed_subtitle;
+ void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ VideoContent::Frame _video_position;
};
#endif
diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h
new file mode 100644
index 000000000..72f6ccc12
--- /dev/null
+++ b/src/lib/video_examiner.h
@@ -0,0 +1,30 @@
+/*
+ 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 "types.h"
+#include "video_content.h"
+
+class VideoExaminer
+{
+public:
+ 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_sink.h b/src/lib/video_sink.h
deleted file mode 100644
index 0170c7350..000000000
--- a/src/lib/video_sink.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.
-
-*/
-
-#ifndef DVDOMATIC_VIDEO_SINK_H
-#define DVDOMATIC_VIDEO_SINK_H
-
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-
-class Subtitle;
-class Image;
-
-class VideoSink
-{
-public:
- /** Call with a frame of video.
- * @param i Video frame image.
- * @param 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<const Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0;
-};
-
-class TimedVideoSink
-{
-public:
- /** Call with a frame of video.
- * @param i Video frame image.
- * @param same true if i is the same as last time we were called.
- * @param s A subtitle that should be on this frame, or 0.
- * @param t Source timestamp.
- */
- virtual void process_video (boost::shared_ptr<const Image> i, bool same, boost::shared_ptr<Subtitle> s, double t) = 0;
-};
-
-#endif
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
deleted file mode 100644
index 748cb6fe9..000000000
--- a/src/lib/video_source.h
+++ /dev/null
@@ -1,72 +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 TimedVideoSink;
-class Subtitle;
-class Image;
-
-/** @class VideoSource
- * @param A class that emits video data without timestamps.
- */
-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<const Image>, bool, boost::shared_ptr<Subtitle>)> Video;
-
- void connect_video (boost::shared_ptr<VideoSink>);
-};
-
-/** @class TimedVideoSource
- * @param A class that emits video data with timestamps.
- */
-class TimedVideoSource
-{
-public:
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is true if the image is the same as the last one that was emitted.
- * Third parameter is either 0 or a subtitle that should be on this frame.
- * Fourth parameter is the source timestamp of this frame.
- */
- boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double)> Video;
-
- void connect_video (boost::shared_ptr<VideoSink>);
- void connect_video (boost::shared_ptr<TimedVideoSink>);
-};
-
-#endif
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index cff0b5be2..cbb84a940 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -23,14 +23,19 @@
#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 "format.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"
@@ -44,8 +49,9 @@ using boost::shared_ptr;
int const Writer::_maximum_frames_in_memory = 8;
-Writer::Writer (shared_ptr<Film> f)
+Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j)
: _film (f)
+ , _job (j)
, _first_nonexistant_frame (0)
, _thread (0)
, _finish (false)
@@ -70,28 +76,24 @@ Writer::Writer (shared_ptr<Film> f)
new libdcp::MonoPictureAsset (
_film->internal_video_mxf_dir (),
_film->internal_video_mxf_filename (),
- _film->dcp_frame_rate (),
- _film->format()->dcp_size ()
+ _film->dcp_video_frame_rate (),
+ _film->container()->size (_film->full_frame ())
)
);
_picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
- AudioMapping m (_film);
+ _sound_asset.reset (
+ new libdcp::SoundAsset (
+ _film->dir (_film->dcp_name()),
+ _film->dcp_audio_mxf_filename (),
+ _film->dcp_video_frame_rate (),
+ _film->dcp_audio_channels (),
+ _film->dcp_audio_frame_rate()
+ )
+ );
- if (m.dcp_channels() > 0) {
- _sound_asset.reset (
- new libdcp::SoundAsset (
- _film->dir (_film->dcp_name()),
- _film->dcp_audio_mxf_filename (),
- _film->dcp_frame_rate (),
- m.dcp_channels (),
- dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
- )
- );
-
- _sound_asset_writer = _sound_asset->start_write ();
- }
+ _sound_asset_writer = _sound_asset->start_write ();
_thread = new boost::thread (boost::bind (&Writer::thread, this));
}
@@ -201,6 +203,10 @@ try
}
}
lock.lock ();
+
+ if (_film->length ()) {
+ _job->set_progress (float(_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length()));
+ }
++_last_written_frame;
}
@@ -256,21 +262,11 @@ Writer::finish ()
_thread = 0;
_picture_asset_writer->finalize ();
-
- if (_sound_asset_writer) {
- _sound_asset_writer->finalize ();
- }
-
+ _sound_asset_writer->finalize ();
+
int const frames = _last_written_frame + 1;
- int duration = 0;
- if (_film->trim_type() == Film::CPL) {
- duration = frames - _film->trim_start() - _film->trim_end();
- _picture_asset->set_entry_point (_film->trim_start ());
- } else {
- duration = frames;
- }
- _picture_asset->set_duration (duration);
+ _picture_asset->set_duration (frames);
/* Hard-link the video MXF into the DCP */
@@ -294,13 +290,7 @@ Writer::finish ()
_picture_asset->set_directory (_film->dir (_film->dcp_name ()));
_picture_asset->set_file_name (_film->dcp_video_mxf_filename ());
-
- if (_sound_asset) {
- if (_film->trim_type() == Film::CPL) {
- _sound_asset->set_entry_point (_film->trim_start ());
- }
- _sound_asset->set_duration (duration);
- }
+ _sound_asset->set_duration (frames);
libdcp::DCP dcp (_film->dir (_film->dcp_name()));
@@ -310,7 +300,7 @@ Writer::finish ()
_film->dcp_name(),
_film->dcp_content_type()->libdcp_kind (),
frames,
- _film->dcp_frame_rate ()
+ _film->dcp_video_frame_rate ()
)
);
diff --git a/src/lib/writer.h b/src/lib/writer.h
index beb16c7b9..e56e12e75 100644
--- a/src/lib/writer.h
+++ b/src/lib/writer.h
@@ -26,6 +26,7 @@
class Film;
class EncodedData;
class AudioBuffers;
+class Job;
namespace libdcp {
class MonoPictureAsset;
@@ -63,7 +64,7 @@ bool operator== (QueueItem const & a, QueueItem const & b);
class Writer : public ExceptionStore
{
public:
- Writer (boost::shared_ptr<Film>);
+ Writer (boost::shared_ptr<const Film>, boost::shared_ptr<Job>);
bool can_fake_write (int) const;
@@ -79,7 +80,8 @@ private:
void check_existing_picture_mxf ();
/** our Film */
- boost::shared_ptr<Film> _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;
diff --git a/src/lib/wscript b/src/lib/wscript
index 7c7a64d58..2f8653984 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -2,53 +2,57 @@ import os
import i18n
sources = """
- ab_transcode_job.cc
- ab_transcoder.cc
analyse_audio_job.cc
audio_analysis.cc
+ audio_buffers.cc
+ audio_content.cc
audio_decoder.cc
- audio_source.cc
+ audio_mapping.cc
config.cc
- combiner.cc
+ content.cc
cross.cc
dci_metadata.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
exceptions.cc
filter_graph.cc
+ ffmpeg.cc
+ ffmpeg_content.cc
ffmpeg_decoder.cc
+ ffmpeg_examiner.cc
film.cc
filter.cc
- format.cc
- gain.cc
image.cc
+ imagemagick_content.cc
imagemagick_decoder.cc
+ imagemagick_examiner.cc
job.cc
job_manager.cc
log.cc
lut.cc
- matcher.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
- stream.cc
subtitle.cc
timer.cc
transcode_job.cc
transcoder.cc
- trimmer.cc
+ types.cc
ui_signaller.cc
util.cc
+ video_content.cc
video_decoder.cc
- video_source.cc
writer.cc
"""
@@ -58,12 +62,12 @@ def build(bld):
else:
obj = bld(features = 'cxx cxxshlib')
- obj.name = 'libdvdomatic'
+ 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 GLIB LZMA
+ SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA
"""
obj.source = sources + ' version.cc'
@@ -71,13 +75,15 @@ def build(bld):
if bld.env.TARGET_WINDOWS:
obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI'
obj.source += ' stack.cpp'
+ if bld.env.STATIC:
+ obj.uselib += ' XML++'
+ obj.source = sources + " version.cc"
+ obj.target = 'dcpomatic'
- obj.target = 'dvdomatic'
-
- i18n.po_to_mo(os.path.join('src', 'lib'), 'libdvdomatic', bld)
+ i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld)
def pot(bld):
- i18n.pot(os.path.join('src', 'lib'), sources, 'libdvdomatic')
+ i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic')
def pot_merge(bld):
- i18n.pot_merge(os.path.join('src', 'lib'), 'libdvdomatic')
+ i18n.pot_merge(os.path.join('src', 'lib'), 'libdcpomatic')
diff --git a/src/tools/dvdomatic.cc b/src/tools/dcpomatic.cc
index e0629bb98..ac39d4fed 100644
--- a/src/tools/dvdomatic.cc
+++ b/src/tools/dcpomatic.cc
@@ -40,12 +40,9 @@
#include "wx/wx_ui_signaller.h"
#include "wx/about_dialog.h"
#include "lib/film.h"
-#include "lib/format.h"
#include "lib/config.h"
-#include "lib/filter.h"
#include "lib/util.h"
-#include "lib/scaler.h"
-#include "lib/exceptions.h"
+#include "lib/version.h"
#include "lib/ui_signaller.h"
#include "lib/log.h"
@@ -64,6 +61,7 @@ static FilmViewer* film_viewer = 0;
static shared_ptr<Film> film;
static std::string log_level;
static std::string film_to_load;
+static std::string film_to_create;
static wxMenu* jobs_menu = 0;
static void set_menu_sensitivity ();
@@ -151,7 +149,6 @@ enum {
ID_jobs_make_dcp,
ID_jobs_send_dcp_to_tms,
ID_jobs_show_dcp,
- ID_jobs_analyse_audio,
};
void
@@ -180,12 +177,10 @@ setup_menu (wxMenuBar* m)
add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM);
add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM);
add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM);
- jobs_menu->AppendSeparator ();
- add_item (jobs_menu, _("&Analyse audio"), ID_jobs_analyse_audio, NEEDS_FILM);
wxMenu* help = new wxMenu;
#ifdef __WXOSX__
- add_item (help, _("About DVD-o-matic"), wxID_ABOUT, ALWAYS);
+ add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
#else
add_item (help, _("About"), wxID_ABOUT, ALWAYS);
#endif
@@ -224,34 +219,24 @@ public:
Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
- Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
Connect (wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
- wxPanel* panel = new wxPanel (this);
- wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (panel, 1, wxEXPAND);
- SetSizer (s);
+ film_editor = new FilmEditor (film, this);
+ film_viewer = new FilmViewer (film, this);
+ JobManagerView* job_manager_view = new JobManagerView (this, static_cast<JobManagerView::Buttons> (0));
- film_editor = new FilmEditor (film, panel);
- film_viewer = new FilmViewer (film, panel);
- JobManagerView* job_manager_view = new JobManagerView (panel, static_cast<JobManagerView::Buttons> (0));
+ wxBoxSizer* right_sizer = new wxBoxSizer (wxVERTICAL);
+ right_sizer->Add (film_viewer, 2, wxEXPAND | wxALL, 6);
+ right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
- _top_sizer = new wxBoxSizer (wxHORIZONTAL);
- _top_sizer->Add (film_editor, 0, wxALL, 6);
- _top_sizer->Add (film_viewer, 1, wxEXPAND | wxALL, 6);
-
- wxBoxSizer* main_sizer = new wxBoxSizer (wxVERTICAL);
- main_sizer->Add (_top_sizer, 2, wxEXPAND | wxALL, 6);
- main_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
- panel->SetSizer (main_sizer);
+ wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
+ main_sizer->Add (film_editor, 1, wxEXPAND | wxALL, 6);
+ main_sizer->Add (right_sizer, 2, wxEXPAND | wxALL, 6);
set_menu_sensitivity ();
- /* XXX: calling these here is a bit of a hack */
- film_editor->setup_visibility ();
-
film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
if (film) {
file_changed (film->directory ());
@@ -260,22 +245,11 @@ public:
}
set_film ();
-
- film_editor->Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Frame::film_editor_sized), 0, this);
+ SetSizer (main_sizer);
}
private:
- void film_editor_sized (wxSizeEvent &)
- {
- static bool in_layout = false;
- if (!in_layout) {
- in_layout = true;
- _top_sizer->Layout ();
- in_layout = false;
- }
- }
-
void menu_opened (wxMenuEvent& ev)
{
if (ev.GetMenu() != jobs_menu) {
@@ -297,7 +271,7 @@ private:
void file_changed (string f)
{
stringstream s;
- s << wx_to_std (_("DVD-o-matic"));
+ s << wx_to_std (_("DCP-o-matic"));
if (!f.empty ()) {
s << " - " << f;
}
@@ -326,7 +300,8 @@ private:
}
maybe_save_then_delete_film ();
- film.reset (new Film (d->get_path (), false));
+ film.reset (new Film (d->get_path ()));
+ film->write_metadata ();
film->log()->set_level (log_level);
film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
set_film ();
@@ -420,31 +395,26 @@ private:
#endif
}
- void jobs_analyse_audio (wxCommandEvent &)
- {
- film->analyse_audio ();
- }
-
void help_about (wxCommandEvent &)
{
AboutDialog* d = new AboutDialog (this);
d->ShowModal ();
d->Destroy ();
}
-
- wxSizer* _top_sizer;
};
#if wxMINOR_VERSION == 9
static const wxCmdLineEntryDesc command_line_description[] = {
{ wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
- { wxCMD_LINE_PARAM, 0, 0, "film to load", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
};
#else
static const wxCmdLineEntryDesc command_line_description[] = {
{ wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
- { wxCMD_LINE_PARAM, 0, 0, wxT("film to load"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 }
};
#endif
@@ -457,7 +427,7 @@ class App : public wxApp
return false;
}
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
unsetenv ("UBUNTU_MENUPROXY");
#endif
@@ -471,16 +441,16 @@ class App : public wxApp
/* Enable i18n; this will create a Config object
to look for a force-configured language. This Config
- object will be wrong, however, because dvdomatic_setup
+ object will be wrong, however, because dcpomatic_setup
hasn't yet been called and there aren't any scalers, filters etc.
set up yet.
*/
- dvdomatic_setup_i18n ();
+ dcpomatic_setup_i18n ();
/* Set things up, including scalers / filters etc.
which will now be internationalised correctly.
*/
- dvdomatic_setup ();
+ dcpomatic_setup ();
/* Force the configuration to be re-loaded correctly next
time it is needed.
@@ -490,13 +460,21 @@ class App : public wxApp
if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
try {
film.reset (new Film (film_to_load));
+ film->read_metadata ();
film->log()->set_level (log_level);
} catch (exception& e) {
error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what())));
}
}
- Frame* f = new Frame (_("DVD-o-matic"));
+ if (!film_to_create.empty ()) {
+ film.reset (new Film (film_to_create));
+ film->write_metadata ();
+ film->log()->set_level (log_level);
+ film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
+ }
+
+ Frame* f = new Frame (_("DCP-o-matic"));
SetTopWindow (f);
f->Maximize ();
f->Show ();
@@ -516,11 +494,15 @@ class App : public wxApp
bool OnCmdLineParsed (wxCmdLineParser& parser)
{
if (parser.GetParamCount() > 0) {
- film_to_load = wx_to_std (parser.GetParam(0));
+ if (parser.Found (wxT ("new"))) {
+ film_to_create = wx_to_std (parser.GetParam (0));
+ } else {
+ film_to_load = wx_to_std (parser.GetParam(0));
+ }
}
wxString log;
- if (parser.Found(wxT("log"), &log)) {
+ if (parser.Found (wxT ("log"), &log)) {
log_level = wx_to_std (log);
}
diff --git a/src/tools/dvdomatic_batch.cc b/src/tools/dcpomatic_batch.cc
index d9ddb9d46..b4ab054fd 100644
--- a/src/tools/dvdomatic_batch.cc
+++ b/src/tools/dcpomatic_batch.cc
@@ -132,11 +132,11 @@ private:
void help_about (wxCommandEvent &)
{
wxAboutDialogInfo info;
- info.SetName (_("DVD-o-matic Batch Converter"));
- if (strcmp (dvdomatic_git_commit, "release") == 0) {
- info.SetVersion (std_to_wx (String::compose ("version %1", dvdomatic_version)));
+ info.SetName (_("DCP-o-matic Batch Converter"));
+ if (strcmp (dcpomatic_git_commit, "release") == 0) {
+ info.SetVersion (std_to_wx (String::compose ("version %1", dcpomatic_version)));
} else {
- info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dvdomatic_version, dvdomatic_git_commit)));
+ info.SetVersion (std_to_wx (String::compose ("version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
}
info.SetDescription (_("Free, open-source DCP generation from almost anything."));
info.SetCopyright (_("(C) 2012-2013 Carl Hetherington, Terrence Meiczinger, Paul Davis, Ole Laursen"));
@@ -157,7 +157,7 @@ private:
translators.Add (wxT ("Adam Klotblixt"));
info.SetTranslators (translators);
- info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic"));
+ info.SetWebSite (wxT ("http://carlh.net/software/dcpomatic"));
wxAboutBox (info);
}
@@ -177,6 +177,7 @@ private:
if (r == wxID_OK) {
try {
shared_ptr<Film> film (new Film (wx_to_std (c->GetPath ())));
+ film->read_metadata ();
film->make_dcp ();
} catch (std::exception& e) {
wxString p = c->GetPath ();
@@ -197,29 +198,29 @@ class App : public wxApp
return false;
}
-#ifdef DVDOMATIC_LINUX
+#ifdef DCPOMATIC_LINUX
unsetenv ("UBUNTU_MENUPROXY");
#endif
/* Enable i18n; this will create a Config object
to look for a force-configured language. This Config
- object will be wrong, however, because dvdomatic_setup
+ object will be wrong, however, because dcpomatic_setup
hasn't yet been called and there aren't any scalers, filters etc.
set up yet.
*/
- dvdomatic_setup_i18n ();
+ dcpomatic_setup_i18n ();
/* Set things up, including scalers / filters etc.
which will now be internationalised correctly.
*/
- dvdomatic_setup ();
+ dcpomatic_setup ();
/* Force the configuration to be re-loaded correctly next
time it is needed.
*/
Config::drop ();
- Frame* f = new Frame (_("DVD-o-matic Batch Converter"));
+ Frame* f = new Frame (_("DCP-o-matic Batch Converter"));
SetTopWindow (f);
f->Maximize ();
f->Show ();
diff --git a/src/tools/makedcp.cc b/src/tools/dcpomatic_cli.cc
index 1cd5145ed..ee9e2cdc0 100644
--- a/src/tools/makedcp.cc
+++ b/src/tools/dcpomatic_cli.cc
@@ -21,12 +21,10 @@
#include <iomanip>
#include <getopt.h>
#include <libdcp/version.h>
-#include "format.h"
#include "film.h"
#include "filter.h"
#include "transcode_job.h"
#include "job_manager.h"
-#include "ab_transcode_job.h"
#include "util.h"
#include "scaler.h"
#include "version.h"
@@ -46,9 +44,9 @@ static void
help (string n)
{
cerr << "Syntax: " << n << " [OPTION] <FILM>\n"
- << " -v, --version show DVD-o-matic version\n"
+ << " -v, --version show DCP-o-matic version\n"
<< " -h, --help show this help\n"
- << " -d, --deps list DVD-o-matic dependency details\n"
+ << " -d, --deps list DCP-o-matic dependency details and quit\n"
<< " -f, --flags show flags passed to C++ compiler on build\n"
<< " -n, --no-progress do not print progress to stdout\n"
<< " -r, --no-remote do not use any remote servers\n"
@@ -85,7 +83,7 @@ main (int argc, char* argv[])
switch (c) {
case 'v':
- cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n";
+ cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
exit (EXIT_SUCCESS);
case 'h':
help (argv[0]);
@@ -94,7 +92,7 @@ main (int argc, char* argv[])
cout << dependency_version_summary () << "\n";
exit (EXIT_SUCCESS);
case 'f':
- cout << dvdomatic_cxx_flags << "\n";
+ cout << dcpomatic_cxx_flags << "\n";
exit (EXIT_SUCCESS);
case 'n':
progress = false;
@@ -115,13 +113,13 @@ main (int argc, char* argv[])
film_dir = argv[optind];
- dvdomatic_setup ();
+ dcpomatic_setup ();
if (no_remote) {
Config::instance()->set_servers (vector<ServerDescription*> ());
}
- cout << "DVD-o-matic " << dvdomatic_version << " git " << dvdomatic_git_commit;
+ cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
char buf[256];
if (gethostname (buf, 256) == 0) {
cout << " on " << buf;
@@ -130,7 +128,8 @@ main (int argc, char* argv[])
shared_ptr<Film> film;
try {
- film.reset (new Film (film_dir, true));
+ film.reset (new Film (film_dir));
+ film->read_metadata ();
} catch (std::exception& e) {
cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
exit (EXIT_FAILURE);
@@ -138,14 +137,10 @@ main (int argc, char* argv[])
film->log()->set_level ((Log::Level) log_level);
- cout << "\nMaking ";
- if (film->dcp_ab()) {
- cout << "A/B ";
- }
- cout << "DCP for " << film->name() << "\n";
- cout << "Content: " << film->content() << "\n";
- pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
- cout << "Filters: " << f.first << " " << f.second << "\n";
+ cout << "\nMaking DCP for " << film->name() << "\n";
+// cout << "Content: " << film->content() << "\n";
+// pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
+// cout << "Filters: " << f.first << " " << f.second << "\n";
film->make_dcp ();
@@ -154,7 +149,7 @@ main (int argc, char* argv[])
bool error = false;
while (!should_stop) {
- dvdomatic_sleep (5);
+ dcpomatic_sleep (5);
list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
diff --git a/src/tools/servomatic_gui.cc b/src/tools/dcpomatic_server.cc
index 000c2019f..d3a353154 100644
--- a/src/tools/servomatic_gui.cc
+++ b/src/tools/dcpomatic_server.cc
@@ -61,7 +61,7 @@ class StatusDialog : public wxDialog
{
public:
StatusDialog ()
- : wxDialog (0, wxID_ANY, _("DVD-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ : wxDialog (0, wxID_ANY, _("DCP-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, _timer (this, ID_timer)
{
_sizer = new wxFlexGridSizer (1, 6, 6);
@@ -105,7 +105,7 @@ public:
#endif
#ifndef __WXOSX__
/* XXX: fix this for OS X */
- SetIcon (icon, std_to_wx ("DVD-o-matic encode server"));
+ SetIcon (icon, std_to_wx ("DCP-o-matic encode server"));
#endif
Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status));
@@ -150,7 +150,7 @@ private:
return false;
}
- dvdomatic_setup ();
+ dcpomatic_setup ();
_icon = new TaskBarIcon;
_thread = new thread (bind (&App::main_thread, this));
diff --git a/src/tools/servomatic_cli.cc b/src/tools/dcpomatic_server_cli.cc
index 6626d45b9..76d085034 100644
--- a/src/tools/servomatic_cli.cc
+++ b/src/tools/dcpomatic_server_cli.cc
@@ -51,7 +51,7 @@ static void
help (string n)
{
cerr << "Syntax: " << n << " [OPTION]\n"
- << " -v, --version show DVD-o-matic version\n"
+ << " -v, --version show DCP-o-matic version\n"
<< " -h, --help show this help\n"
<< " -t, --threads number of parallel encoding threads to use\n";
}
@@ -78,7 +78,7 @@ main (int argc, char* argv[])
switch (c) {
case 'v':
- cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n";
+ cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
exit (EXIT_SUCCESS);
case 'h':
help (argv[0]);
diff --git a/src/tools/po/es_ES.po b/src/tools/po/es_ES.po
index bbae550d7..43c9b12f1 100644
--- a/src/tools/po/es_ES.po
+++ b/src/tools/po/es_ES.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVDOMATIC\n"
+"Project-Id-Version: DCPOMATIC\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-03-23 21:08-0500\n"
@@ -90,16 +90,16 @@ msgstr "No se pudo cargar la película %s (%s)"
msgid "Could not open film at %s (%s)"
msgstr "No se pudo cargar la película en %s (%s)"
-#: src/tools/dvdomatic.cc:300 src/tools/dvdomatic.cc:431
-#: src/tools/dvdomatic.cc:524
-msgid "DVD-o-matic"
-msgstr "DVD-o-matic"
+#: src/tools/dcpomatic.cc:287 src/tools/dcpomatic.cc:410
+#: src/tools/dcpomatic.cc:531
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
-#: src/tools/dvdomatic.cc:79
+#: src/tools/dcpomatic.cc:75
msgid "Film changed"
msgstr "Película cambiada"
-#: src/tools/dvdomatic.cc:437
+#: src/tools/dvdomatic.cc:425
msgid "Free, open-source DCP generation from almost anything."
msgstr ""
"Generación de DCP a partir de casi cualquier fuente, libre y de código "
diff --git a/src/tools/po/fr_FR.po b/src/tools/po/fr_FR.po
index 8a28a0e01..b9e39b392 100644
--- a/src/tools/po/fr_FR.po
+++ b/src/tools/po/fr_FR.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVD-o-matic FRENCH\n"
+"Project-Id-Version: DCP-o-matic FRENCH\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-05-10 14:09+0100\n"
diff --git a/src/tools/po/sv_SE.po b/src/tools/po/sv_SE.po
index 662b74bb3..157b1fb19 100644
--- a/src/tools/po/sv_SE.po
+++ b/src/tools/po/sv_SE.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVD-o-matic\n"
+"Project-Id-Version: DCP-o-matic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-04-09 10:12+0100\n"
@@ -89,10 +89,10 @@ msgstr "Kunde inte öppna filmen %1 (%2)"
msgid "Could not open film at %s (%s)"
msgstr "Kunde inte öppna filmen vid %s (%s)"
-#: src/tools/dvdomatic.cc:300 src/tools/dvdomatic.cc:431
-#: src/tools/dvdomatic.cc:524
-msgid "DVD-o-matic"
-msgstr "DVD-o-matic"
+#: src/tools/dvdomatic.cc:288 src/tools/dvdomatic.cc:419
+#: src/tools/dvdomatic.cc:506
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
#: src/tools/dvdomatic.cc:79
msgid "Film changed"
@@ -114,7 +114,7 @@ msgstr "&Visa DCP"
#: src/tools/dvdomatic.cc:78
#, fuzzy, c-format
msgid "Save changes to film \"%s\" before closing?"
-msgstr "Spara ändringarna till filmen \"%1\" före avslut?"
+msgstr "Spara ändringarna till filmen \"%s\" före avslut?"
#: src/tools/dvdomatic.cc:340
msgid "Select film to open"
diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc
index 5e1cf49b4..88974eed7 100644
--- a/src/tools/servomatictest.cc
+++ b/src/tools/servomatictest.cc
@@ -28,13 +28,12 @@
#include "scaler.h"
#include "server.h"
#include "dcp_video_frame.h"
-#include "options.h"
#include "decoder.h"
#include "exceptions.h"
#include "scaler.h"
#include "log.h"
-#include "decoder_factory.h"
#include "video_decoder.h"
+#include "player.h"
using std::cout;
using std::cerr;
@@ -146,22 +145,20 @@ main (int argc, char* argv[])
exit (EXIT_FAILURE);
}
- dvdomatic_setup ();
+ dcpomatic_setup ();
server = new ServerDescription (server_host, 1);
- shared_ptr<Film> film (new Film (film_dir, true));
+ shared_ptr<Film> film (new Film (film_dir));
+ film->read_metadata ();
- DecodeOptions opt;
- opt.decode_audio = false;
- opt.decode_subtitles = true;
- opt.video_sync = true;
+ shared_ptr<Player> player = film->player ();
+ player->disable_audio ();
- Decoders decoders = decoder_factory (film, opt);
try {
- decoders.video->Video.connect (boost::bind (process_video, _1, _2, _3));
+ player->Video.connect (boost::bind (process_video, _1, _2, _3));
bool done = false;
while (!done) {
- done = decoders.video->pass ();
+ done = player->pass ();
}
} catch (std::exception& e) {
cerr << "Error: " << e.what() << "\n";
diff --git a/src/tools/wscript b/src/tools/wscript
index 20a92cad2..c7ab44604 100644
--- a/src/tools/wscript
+++ b/src/tools/wscript
@@ -4,31 +4,31 @@ from waflib import Logs
import i18n
def build(bld):
- for t in ['makedcp', 'servomatic_cli', 'servomatictest']:
+ for t in ['dcpomatic_cli', 'dcpomatic_server_cli']:
obj = bld(features = 'cxx cxxprogram')
- obj.uselib = 'BOOST_THREAD OPENJPEG DCP AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
+ obj.uselib = 'BOOST_THREAD OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS'
obj.includes = ['..']
- obj.use = ['libdvdomatic']
+ obj.use = ['libdcpomatic']
obj.source = '%s.cc' % t
obj.target = t
if not bld.env.DISABLE_GUI:
- for t in ['dvdomatic', 'dvdomatic_batch', 'servomatic_gui']:
+ for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']:
obj = bld(features = 'cxx cxxprogram')
obj.uselib = 'DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS'
if bld.env.STATIC:
obj.uselib += ' GTK'
obj.includes = ['..']
- obj.use = ['libdvdomatic', 'libdvdomatic-wx']
+ obj.use = ['libdcpomatic', 'libdcpomatic-wx']
obj.source = '%s.cc' % t
if bld.env.TARGET_WINDOWS:
- obj.source += ' ../../platform/windows/dvdomatic.rc'
+ obj.source += ' ../../platform/windows/dcpomatic.rc'
obj.target = t
- i18n.po_to_mo(os.path.join('src', 'tools'), 'dvdomatic', bld)
+ i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic', bld)
def pot(bld):
- i18n.pot(os.path.join('src', 'tools'), 'dvdomatic.cc', 'dvdomatic')
+ i18n.pot(os.path.join('src', 'tools'), 'dcpomatic.cc', 'dcpomatic')
def pot_merge(bld):
- i18n.pot_merge(os.path.join('src', 'tools'), 'dvdomatic')
+ i18n.pot_merge(os.path.join('src', 'tools'), 'dcpomatic')
diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc
index cbac3ed43..ca19326a1 100644
--- a/src/wx/about_dialog.cc
+++ b/src/wx/about_dialog.cc
@@ -27,7 +27,7 @@
using std::vector;
AboutDialog::AboutDialog (wxWindow* parent)
- : wxDialog (parent, wxID_ANY, _("About DVD-o-matic"))
+ : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
{
wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
@@ -38,15 +38,15 @@ AboutDialog::AboutDialog (wxWindow* parent)
wxFont version_font (*wxNORMAL_FONT);
version_font.SetWeight (wxFONTWEIGHT_BOLD);
- wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DVD-o-matic"));
+ wxStaticText* t = new wxStaticText (this, wxID_ANY, _("DCP-o-matic"));
t->SetFont (title_font);
sizer->Add (t, wxSizerFlags().Centre().Border());
wxString s;
- if (strcmp (dvdomatic_git_commit, "release") == 0) {
- t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dvdomatic_version)));
+ if (strcmp (dcpomatic_git_commit, "release") == 0) {
+ t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1", dcpomatic_version)));
} else {
- t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dvdomatic_version, dvdomatic_git_commit)));
+ t = new wxStaticText (this, wxID_ANY, std_to_wx (String::compose ("Version %1 git %2", dcpomatic_version, dcpomatic_git_commit)));
}
t->SetFont (version_font);
sizer->Add (t, wxSizerFlags().Centre().Border());
@@ -62,8 +62,8 @@ AboutDialog::AboutDialog (wxWindow* parent)
wxHyperlinkCtrl* h = new wxHyperlinkCtrl (
this, wxID_ANY,
- wxT ("www.carlh.net/software/dvdomatic"),
- wxT ("http://www.carlh.net/software/dvdomatic")
+ wxT ("dcpomatic.com"),
+ wxT ("http://dcpomatic.com")
);
sizer->Add (h, wxSizerFlags().Centre().Border());
@@ -117,10 +117,6 @@ AboutDialog::AboutDialog (wxWindow* parent)
sizer->Add (_notebook, wxSizerFlags().Centre().Border().Expand());
-#if 0
- info.SetWebSite (wxT ("http://carlh.net/software/dvdomatic"));
-#endif
-
SetSizerAndFit (sizer);
}
diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc
index 21e4e5940..22e09cc7a 100644
--- a/src/wx/audio_dialog.cc
+++ b/src/wx/audio_dialog.cc
@@ -18,10 +18,10 @@
*/
#include <boost/filesystem.hpp>
+#include "lib/audio_analysis.h"
+#include "lib/film.h"
#include "audio_dialog.h"
#include "audio_plot.h"
-#include "audio_analysis.h"
-#include "film.h"
#include "wx_util.h"
using boost::shared_ptr;
@@ -43,7 +43,6 @@ AudioDialog::AudioDialog (wxWindow* parent)
wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
}
-
for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
_channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
@@ -84,62 +83,39 @@ AudioDialog::AudioDialog (wxWindow* parent)
}
void
-AudioDialog::set_film (boost::shared_ptr<Film> f)
+AudioDialog::set_content (shared_ptr<AudioContent> c)
{
- _film_changed_connection.disconnect ();
- _film_audio_analysis_succeeded_connection.disconnect ();
+ _content_changed_connection.disconnect ();
- _film = f;
+ _content = c;
try_to_load_analysis ();
- setup_channels ();
- _plot->set_gain (_film->audio_gain ());
+ _plot->set_gain (_content->audio_gain ());
- _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
- _film_audio_analysis_succeeded_connection = _film->AudioAnalysisSucceeded.connect (bind (&AudioDialog::try_to_load_analysis, this));
+ _content_changed_connection = _content->Changed.connect (bind (&AudioDialog::content_changed, this, _2));
- SetTitle (wxString::Format (_("DVD-o-matic audio - %s"), std_to_wx(_film->name()).data()));
+ SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->file().filename().string()).data()));
}
void
-AudioDialog::setup_channels ()
-{
- if (!_film->audio_stream()) {
- return;
- }
-
- AudioMapping m (_film);
-
- for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- if (m.dcp_to_source(static_cast<libdcp::Channel>(i))) {
- _channel_checkbox[i]->Show ();
- } else {
- _channel_checkbox[i]->Hide ();
- }
- }
-}
-
-void
AudioDialog::try_to_load_analysis ()
{
shared_ptr<AudioAnalysis> a;
- if (boost::filesystem::exists (_film->audio_analysis_path())) {
- a.reset (new AudioAnalysis (_film->audio_analysis_path ()));
+ if (boost::filesystem::exists (_content->audio_analysis_path())) {
+ a.reset (new AudioAnalysis (_content->audio_analysis_path ()));
} else {
if (IsShown ()) {
- _film->analyse_audio ();
+ _content->analyse_audio (bind (&AudioDialog::try_to_load_analysis, this));
}
}
_plot->set_analysis (a);
- AudioMapping m (_film);
- optional<libdcp::Channel> c = m.source_to_dcp (0);
- if (c) {
- _channel_checkbox[c.get()]->SetValue (true);
- _plot->set_channel_visible (0, true);
+ if (_channel_checkbox[0]) {
+ _channel_checkbox[0]->SetValue (true);
}
+ _plot->set_channel_visible (0, true);
for (int i = 0; i < AudioPoint::COUNT; ++i) {
_type_checkbox[i]->SetValue (true);
@@ -157,27 +133,14 @@ AudioDialog::channel_clicked (wxCommandEvent& ev)
assert (c < MAX_AUDIO_CHANNELS);
- AudioMapping m (_film);
- optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c));
- if (s) {
- _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ());
- }
+ _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
}
void
-AudioDialog::film_changed (Film::Property p)
+AudioDialog::content_changed (int p)
{
- switch (p) {
- case Film::AUDIO_GAIN:
- _plot->set_gain (_film->audio_gain ());
- break;
- case Film::CONTENT_AUDIO_STREAM:
- case Film::EXTERNAL_AUDIO:
- case Film::USE_CONTENT_AUDIO:
- setup_channels ();
- break;
- default:
- break;
+ if (p == AudioContentProperty::AUDIO_GAIN) {
+ _plot->set_gain (_content->audio_gain ());
}
}
diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h
index 514faeea0..c69baf282 100644
--- a/src/wx/audio_dialog.h
+++ b/src/wx/audio_dialog.h
@@ -31,21 +31,19 @@ class AudioDialog : public wxDialog
public:
AudioDialog (wxWindow *);
- void set_film (boost::shared_ptr<Film>);
+ void set_content (boost::shared_ptr<AudioContent>);
private:
- void film_changed (Film::Property);
+ void content_changed (int);
void channel_clicked (wxCommandEvent &);
void type_clicked (wxCommandEvent &);
void smoothing_changed (wxScrollEvent &);
void try_to_load_analysis ();
- void setup_channels ();
- boost::shared_ptr<Film> _film;
+ boost::shared_ptr<AudioContent> _content;
AudioPlot* _plot;
wxCheckBox* _channel_checkbox[MAX_AUDIO_CHANNELS];
wxCheckBox* _type_checkbox[AudioPoint::COUNT];
wxSlider* _smoothing;
- boost::signals2::scoped_connection _film_changed_connection;
- boost::signals2::scoped_connection _film_audio_analysis_succeeded_connection;
+ boost::signals2::scoped_connection _content_changed_connection;
};
diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc
new file mode 100644
index 000000000..3ea0cdd1d
--- /dev/null
+++ b/src/wx/audio_mapping_view.cc
@@ -0,0 +1,174 @@
+/*
+ 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 <wx/wx.h>
+#include <wx/renderer.h>
+#include <wx/grid.h>
+#include <libdcp/types.h>
+#include "lib/audio_mapping.h"
+#include "audio_mapping_view.h"
+#include "wx_util.h"
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+
+/* This could go away with wxWidgets 2.9, which has an API call
+ to find these values.
+*/
+
+#ifdef __WXMSW__
+#define CHECKBOX_WIDTH 16
+#define CHECKBOX_HEIGHT 16
+#endif
+
+#ifdef __WXGTK__
+#define CHECKBOX_WIDTH 20
+#define CHECKBOX_HEIGHT 20
+#endif
+
+
+class NoSelectionStringRenderer : public wxGridCellStringRenderer
+{
+public:
+ void Draw (wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool)
+ {
+ wxGridCellStringRenderer::Draw (grid, attr, dc, rect, row, col, false);
+ }
+};
+
+class CheckBoxRenderer : public wxGridCellRenderer
+{
+public:
+
+ void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool)
+ {
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+ dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxPENSTYLE_SOLID));
+#else
+ dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 0, wxSOLID));
+#endif
+ dc.DrawRectangle (rect);
+
+ wxRendererNative::Get().DrawCheckBox (
+ &grid,
+ dc, rect,
+ grid.GetCellValue (row, col) == wxT("1") ? static_cast<int>(wxCONTROL_CHECKED) : 0
+ );
+ }
+
+ wxSize GetBestSize (wxGrid &, wxGridCellAttr &, wxDC &, int, int)
+ {
+ return wxSize (CHECKBOX_WIDTH + 4, CHECKBOX_HEIGHT + 4);
+ }
+
+ wxGridCellRenderer* Clone () const
+ {
+ return new CheckBoxRenderer;
+ }
+};
+
+
+AudioMappingView::AudioMappingView (wxWindow* parent)
+ : wxPanel (parent, wxID_ANY)
+{
+ _grid = new wxGrid (this, wxID_ANY);
+
+ _grid->CreateGrid (0, 7);
+#if wxMINOR_VERSION == 9
+ _grid->HideRowLabels ();
+#else
+ _grid->SetRowLabelSize (0);
+#endif
+ _grid->DisableDragRowSize ();
+ _grid->DisableDragColSize ();
+ _grid->EnableEditing (false);
+ _grid->SetCellHighlightPenWidth (0);
+ _grid->SetDefaultRenderer (new NoSelectionStringRenderer);
+
+ _grid->SetColLabelValue (0, _("Content channel"));
+ _grid->SetColLabelValue (1, _("L"));
+ _grid->SetColLabelValue (2, _("R"));
+ _grid->SetColLabelValue (3, _("C"));
+ _grid->SetColLabelValue (4, _("Lfe"));
+ _grid->SetColLabelValue (5, _("Ls"));
+ _grid->SetColLabelValue (6, _("Rs"));
+
+ _grid->AutoSize ();
+
+ _sizer = new wxBoxSizer (wxVERTICAL);
+ _sizer->Add (_grid, 1, wxEXPAND | wxALL);
+ SetSizerAndFit (_sizer);
+
+ Connect (wxID_ANY, wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler (AudioMappingView::left_click), 0, this);
+}
+
+void
+AudioMappingView::left_click (wxGridEvent& ev)
+{
+ if (ev.GetCol() == 0) {
+ return;
+ }
+
+ if (_grid->GetCellValue (ev.GetRow(), ev.GetCol()) == wxT("1")) {
+ _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("0"));
+ } else {
+ _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("1"));
+ }
+
+ AudioMapping mapping;
+ for (int i = 0; i < _grid->GetNumberRows(); ++i) {
+ for (int j = 1; j < _grid->GetNumberCols(); ++j) {
+ if (_grid->GetCellValue (i, j) == wxT ("1")) {
+ mapping.add (i, static_cast<libdcp::Channel> (j - 1));
+ }
+ }
+ }
+
+ Changed (mapping);
+}
+
+void
+AudioMappingView::set (AudioMapping map)
+{
+ if (_grid->GetNumberRows ()) {
+ _grid->DeleteRows (0, _grid->GetNumberRows ());
+ }
+
+ list<int> content_channels = map.content_channels ();
+ _grid->InsertRows (0, content_channels.size ());
+
+ for (size_t r = 0; r < content_channels.size(); ++r) {
+ for (int c = 1; c < 7; ++c) {
+ _grid->SetCellRenderer (r, c, new CheckBoxRenderer);
+ }
+ }
+
+ int n = 0;
+ for (list<int>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) {
+ _grid->SetCellValue (n, 0, wxString::Format (wxT("%d"), *i + 1));
+
+ list<libdcp::Channel> const d = map.content_to_dcp (*i);
+ for (list<libdcp::Channel>::const_iterator j = d.begin(); j != d.end(); ++j) {
+ _grid->SetCellValue (n, static_cast<int> (*j) + 1, wxT("1"));
+ }
+ ++n;
+ }
+}
+
diff --git a/src/lib/trimmer.h b/src/wx/audio_mapping_view.h
index 45b3f149a..e0709c8b7 100644
--- a/src/lib/trimmer.h
+++ b/src/wx/audio_mapping_view.h
@@ -17,23 +17,23 @@
*/
-#include "processor.h"
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include <wx/grid.h>
+#include "lib/audio_mapping.h"
-class Trimmer : public AudioVideoProcessor
+class AudioMappingView : public wxPanel
{
public:
- Trimmer (boost::shared_ptr<Log>, int, int, int, int, float, int);
+ AudioMappingView (wxWindow *);
- void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s);
- void process_audio (boost::shared_ptr<const AudioBuffers>);
+ void set (AudioMapping);
+
+ boost::signals2::signal<void (AudioMapping)> Changed;
private:
- friend class trimmer_test;
-
- int _video_start;
- int _video_end;
- int _video_in;
- int64_t _audio_start;
- int64_t _audio_end;
- int64_t _audio_in;
+ void left_click (wxGridEvent &);
+
+ wxGrid* _grid;
+ wxSizer* _sizer;
};
diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc
index 3fec1d3fe..e2e2cdf76 100644
--- a/src/wx/audio_plot.cc
+++ b/src/wx/audio_plot.cc
@@ -21,7 +21,6 @@
#include <boost/bind.hpp>
#include <wx/graphics.h>
#include "audio_plot.h"
-#include "lib/decoder_factory.h"
#include "lib/audio_decoder.h"
#include "lib/audio_analysis.h"
#include "wx/wx_util.h"
@@ -38,7 +37,7 @@ int const AudioPlot::_minimum = -70;
int const AudioPlot::max_smoothing = 128;
AudioPlot::AudioPlot (wxWindow* parent)
- : wxPanel (parent)
+ : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
, _gain (0)
, _smoothing (max_smoothing / 2)
{
@@ -192,6 +191,10 @@ AudioPlot::y_for_linear (float p) const
void
AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const
{
+ if (_analysis->points (channel) == 0) {
+ return;
+ }
+
path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::PEAK]));
float peak = 0;
@@ -212,6 +215,10 @@ AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const
void
AudioPlot::plot_rms (wxGraphicsPath& path, int channel) const
{
+ if (_analysis->points (channel) == 0) {
+ return;
+ }
+
path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::RMS]));
list<float> smoothing;
diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc
index e622c331b..e66be174d 100644
--- a/src/wx/config_dialog.cc
+++ b/src/wx/config_dialog.cc
@@ -28,7 +28,7 @@
#include <wx/notebook.h>
#include "lib/config.h"
#include "lib/server.h"
-#include "lib/format.h"
+#include "lib/ratio.h"
#include "lib/scaler.h"
#include "lib/filter.h"
#include "lib/dcp_content_type.h"
@@ -57,8 +57,6 @@ ConfigDialog::ConfigDialog (wxWindow* parent)
_notebook->AddPage (_metadata_panel, _("Metadata"), false);
make_tms_panel ();
_notebook->AddPage (_tms_panel, _("TMS"), false);
- make_ab_panel ();
- _notebook->AddPage (_ab_panel, _("A/B mode"), false);
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
overall_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
@@ -80,7 +78,7 @@ ConfigDialog::make_misc_panel ()
wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
_misc_panel->SetSizer (s);
- wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
s->Add (table, 1, wxALL | wxEXPAND, 8);
@@ -108,8 +106,13 @@ ConfigDialog::make_misc_panel ()
table->Add (_num_local_encoding_threads, 1);
table->AddSpacer (0);
+ add_label_to_sizer (table, _misc_panel, _("Default duration of still images"), true);
+ _default_still_length = new wxSpinCtrl (_misc_panel);
+ table->Add (_default_still_length, 1, wxEXPAND);
+ add_label_to_sizer (table, _misc_panel, _("s"), false);
+
add_label_to_sizer (table, _misc_panel, _("Default directory for new films"), true);
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
_default_directory = new DirPickerCtrl (_misc_panel);
#else
_default_directory = new wxDirPickerCtrl (_misc_panel, wxDD_DIR_MUST_EXIST);
@@ -122,9 +125,9 @@ ConfigDialog::make_misc_panel ()
table->Add (_default_dci_metadata_button);
table->AddSpacer (1);
- add_label_to_sizer (table, _misc_panel, _("Default format"), true);
- _default_format = new wxChoice (_misc_panel, wxID_ANY);
- table->Add (_default_format);
+ add_label_to_sizer (table, _misc_panel, _("Default container"), true);
+ _default_container = new wxChoice (_misc_panel, wxID_ANY);
+ table->Add (_default_container);
table->AddSpacer (1);
add_label_to_sizer (table, _misc_panel, _("Default content type"), true);
@@ -157,22 +160,26 @@ ConfigDialog::make_misc_panel ()
_num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
_num_local_encoding_threads->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::num_local_encoding_threads_changed), 0, this);
+ _default_still_length->SetRange (1, 3600);
+ _default_still_length->SetValue (config->default_still_length ());
+ _default_still_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::default_still_length_changed), 0, this);
+
_default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()))));
_default_directory->Connect (wxID_ANY, wxEVT_COMMAND_DIRPICKER_CHANGED, wxCommandEventHandler (ConfigDialog::default_directory_changed), 0, this);
_default_dci_metadata_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_default_dci_metadata_clicked), 0, this);
- vector<Format const *> fmt = Format::all ();
+ vector<Ratio const *> ratio = Ratio::all ();
int n = 0;
- for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
- _default_format->Append (std_to_wx ((*i)->name ()));
- if (*i == config->default_format ()) {
- _default_format->SetSelection (n);
+ for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
+ _default_container->Append (std_to_wx ((*i)->nickname ()));
+ if (*i == config->default_container ()) {
+ _default_container->SetSelection (n);
}
++n;
}
- _default_format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_format_changed), 0, this);
+ _default_container->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_container_changed), 0, this);
vector<DCPContentType const *> const ct = DCPContentType::all ();
n = 0;
@@ -194,7 +201,7 @@ ConfigDialog::make_tms_panel ()
wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
_tms_panel->SetSizer (s);
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
s->Add (table, 1, wxALL | wxEXPAND, 8);
@@ -233,7 +240,7 @@ ConfigDialog::make_metadata_panel ()
wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
_metadata_panel->SetSizer (s);
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
s->Add (table, 1, wxALL | wxEXPAND, 8);
@@ -254,55 +261,13 @@ ConfigDialog::make_metadata_panel ()
}
void
-ConfigDialog::make_ab_panel ()
-{
- _ab_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _ab_panel->SetSizer (s);
-
- wxFlexGridSizer* table = new wxFlexGridSizer (3, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
- table->AddGrowableCol (1, 1);
- s->Add (table, 1, wxALL, 8);
-
- add_label_to_sizer (table, _ab_panel, _("Reference scaler"), true);
- _reference_scaler = new wxChoice (_ab_panel, wxID_ANY);
- vector<Scaler const *> const sc = Scaler::all ();
- for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
- _reference_scaler->Append (std_to_wx ((*i)->name ()));
- }
-
- table->Add (_reference_scaler, 1, wxEXPAND);
- table->AddSpacer (0);
-
- {
- add_label_to_sizer (table, _ab_panel, _("Reference filters"), true);
- wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _reference_filters = new wxStaticText (_ab_panel, wxID_ANY, wxT (""));
- s->Add (_reference_filters, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 6);
- _reference_filters_button = new wxButton (_ab_panel, wxID_ANY, _("Edit..."));
- s->Add (_reference_filters_button, 0);
- table->Add (s, 1, wxEXPAND);
- table->AddSpacer (0);
- }
-
- Config* config = Config::instance ();
-
- _reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ()));
- _reference_scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::reference_scaler_changed), 0, this);
-
- pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ());
- _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
- _reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this);
-}
-
-void
ConfigDialog::make_servers_panel ()
{
_servers_panel = new wxPanel (_notebook);
wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
_servers_panel->SetSizer (s);
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (0, 1);
s->Add (table, 1, wxALL | wxEXPAND, 8);
@@ -475,32 +440,6 @@ ConfigDialog::server_selection_changed (wxListEvent &)
}
void
-ConfigDialog::reference_scaler_changed (wxCommandEvent &)
-{
- int const n = _reference_scaler->GetSelection ();
- if (n >= 0) {
- Config::instance()->set_reference_scaler (Scaler::from_index (n));
- }
-}
-
-void
-ConfigDialog::edit_reference_filters_clicked (wxCommandEvent &)
-{
- FilterDialog* d = new FilterDialog (this, Config::instance()->reference_filters ());
- d->ActiveChanged.connect (boost::bind (&ConfigDialog::reference_filters_changed, this, _1));
- d->ShowModal ();
- d->Destroy ();
-}
-
-void
-ConfigDialog::reference_filters_changed (vector<Filter const *> f)
-{
- Config::instance()->set_reference_filters (f);
- pair<string, string> p = Filter::ffmpeg_strings (Config::instance()->reference_filters ());
- _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
-}
-
-void
ConfigDialog::edit_default_dci_metadata_clicked (wxCommandEvent &)
{
DCIMetadataDialog* d = new DCIMetadataDialog (this, Config::instance()->default_dci_metadata ());
@@ -527,10 +466,16 @@ ConfigDialog::setup_language_sensitivity ()
}
void
-ConfigDialog::default_format_changed (wxCommandEvent &)
+ConfigDialog::default_still_length_changed (wxCommandEvent &)
+{
+ Config::instance()->set_default_still_length (_default_still_length->GetValue ());
+}
+
+void
+ConfigDialog::default_container_changed (wxCommandEvent &)
{
- vector<Format const *> fmt = Format::all ();
- Config::instance()->set_default_format (fmt[_default_format->GetSelection()]);
+ vector<Ratio const *> ratio = Ratio::all ();
+ Config::instance()->set_default_container (ratio[_default_container->GetSelection()]);
}
void
diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h
index a97788942..459d64dd7 100644
--- a/src/wx/config_dialog.h
+++ b/src/wx/config_dialog.h
@@ -48,16 +48,14 @@ private:
void tms_user_changed (wxCommandEvent &);
void tms_password_changed (wxCommandEvent &);
void num_local_encoding_threads_changed (wxCommandEvent &);
+ void default_still_length_changed (wxCommandEvent &);
void default_directory_changed (wxCommandEvent &);
void edit_default_dci_metadata_clicked (wxCommandEvent &);
- void reference_scaler_changed (wxCommandEvent &);
- void edit_reference_filters_clicked (wxCommandEvent &);
- void reference_filters_changed (std::vector<Filter const *>);
void add_server_clicked (wxCommandEvent &);
void edit_server_clicked (wxCommandEvent &);
void remove_server_clicked (wxCommandEvent &);
void server_selection_changed (wxListEvent &);
- void default_format_changed (wxCommandEvent &);
+ void default_container_changed (wxCommandEvent &);
void default_dcp_content_type_changed (wxCommandEvent &);
void issuer_changed (wxCommandEvent &);
void creator_changed (wxCommandEvent &);
@@ -68,33 +66,29 @@ private:
void make_misc_panel ();
void make_tms_panel ();
void make_metadata_panel ();
- void make_ab_panel ();
void make_servers_panel ();
wxNotebook* _notebook;
wxPanel* _misc_panel;
wxPanel* _tms_panel;
- wxPanel* _ab_panel;
wxPanel* _servers_panel;
wxPanel* _metadata_panel;
wxCheckBox* _set_language;
wxChoice* _language;
- wxChoice* _default_format;
+ wxChoice* _default_container;
wxChoice* _default_dcp_content_type;
wxTextCtrl* _tms_ip;
wxTextCtrl* _tms_path;
wxTextCtrl* _tms_user;
wxTextCtrl* _tms_password;
wxSpinCtrl* _num_local_encoding_threads;
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+ wxSpinCtrl* _default_still_length;
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
DirPickerCtrl* _default_directory;
#else
wxDirPickerCtrl* _default_directory;
#endif
wxButton* _default_dci_metadata_button;
- wxChoice* _reference_scaler;
- wxStaticText* _reference_filters;
- wxButton* _reference_filters_button;
wxListCtrl* _servers;
wxButton* _add_server;
wxButton* _edit_server;
diff --git a/src/wx/dci_metadata_dialog.cc b/src/wx/dci_metadata_dialog.cc
index 9c124ed74..df3a24f62 100644
--- a/src/wx/dci_metadata_dialog.cc
+++ b/src/wx/dci_metadata_dialog.cc
@@ -27,7 +27,7 @@ using boost::shared_ptr;
DCIMetadataDialog::DCIMetadataDialog (wxWindow* parent, DCIMetadata dm)
: wxDialog (parent, wxID_ANY, _("DCI name"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"), true);
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index a54b412b3..2d8c752a1 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -25,19 +25,20 @@
#include <iomanip>
#include <wx/wx.h>
#include <wx/notebook.h>
+#include <wx/listctrl.h>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
-#include "lib/format.h"
#include "lib/film.h"
#include "lib/transcode_job.h"
#include "lib/exceptions.h"
-#include "lib/ab_transcode_job.h"
#include "lib/job_manager.h"
#include "lib/filter.h"
+#include "lib/ratio.h"
#include "lib/config.h"
-#include "lib/ffmpeg_decoder.h"
-#include "lib/log.h"
+#include "lib/imagemagick_content.h"
+#include "lib/sndfile_content.h"
+#include "lib/dcp_content_type.h"
#include "filter_dialog.h"
#include "wx_util.h"
#include "film_editor.h"
@@ -46,6 +47,10 @@
#include "dci_metadata_dialog.h"
#include "scaler.h"
#include "audio_dialog.h"
+#include "imagemagick_content_dialog.h"
+#include "timeline_dialog.h"
+#include "audio_mapping_view.h"
+#include "timecode.h"
using std::string;
using std::cout;
@@ -55,60 +60,60 @@ using std::fixed;
using std::setprecision;
using std::list;
using std::vector;
+using std::max;
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
/** @param f Film to edit */
FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
: wxPanel (parent)
- , _film (f)
, _generally_sensitive (true)
, _audio_dialog (0)
+ , _timeline_dialog (0)
{
wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- SetSizer (s);
- _notebook = new wxNotebook (this, wxID_ANY);
- s->Add (_notebook, 1);
- make_film_panel ();
- _notebook->AddPage (_film_panel, _("Film"), true);
- make_video_panel ();
- _notebook->AddPage (_video_panel, _("Video"), false);
- make_audio_panel ();
- _notebook->AddPage (_audio_panel, _("Audio"), false);
- make_subtitle_panel ();
- _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
+ _main_notebook = new wxNotebook (this, wxID_ANY);
+ s->Add (_main_notebook, 1);
+
+ make_content_panel ();
+ _main_notebook->AddPage (_content_panel, _("Content"), true);
+ make_dcp_panel ();
+ _main_notebook->AddPage (_dcp_panel, _("DCP"), false);
+
+ setup_ratios ();
- set_film (_film);
+ set_film (f);
connect_to_widgets ();
JobManager::instance()->ActiveJobsChanged.connect (
bind (&FilmEditor::active_jobs_changed, this, _1)
);
- setup_visibility ();
- setup_formats ();
+ SetSizerAndFit (s);
}
void
-FilmEditor::make_film_panel ()
+FilmEditor::make_dcp_panel ()
{
- _film_panel = new wxPanel (_notebook);
- _film_sizer = new wxBoxSizer (wxVERTICAL);
- _film_panel->SetSizer (_film_sizer);
+ _dcp_panel = new wxPanel (_main_notebook);
+ _dcp_sizer = new wxBoxSizer (wxVERTICAL);
+ _dcp_panel->SetSizer (_dcp_sizer);
- wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
- _film_sizer->Add (grid, 0, wxALL, 8);
+ wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
int r = 0;
- add_label_to_grid_bag_sizer (grid, _film_panel, _("Name"), true, wxGBPosition (r, 0));
- _name = new wxTextCtrl (_film_panel, wxID_ANY);
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0));
+ _name = new wxTextCtrl (_dcp_panel, wxID_ANY);
grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT);
++r;
- add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Name"), true, wxGBPosition (r, 0));
- _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, wxGBPosition (r, 0));
+ _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT (""));
grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++r;
@@ -116,94 +121,58 @@ FilmEditor::make_film_panel ()
#ifdef __WXOSX__
flags |= wxALIGN_RIGHT;
#endif
-
- _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name"));
+
+ _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name"));
grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, flags);
- _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details..."));
+ _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
++r;
- add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), true, wxGBPosition (r, 0));
- _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*"));
- grid->Add (_content, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
- ++r;
-
- _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
- video_control (_trust_content_header);
- grid->Add (_trust_content_header, wxGBPosition (r, 0), wxGBSpan(1, 2));
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0));
+ _container = new wxChoice (_dcp_panel, wxID_ANY);
+ grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
++r;
- add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), true, wxGBPosition (r, 0));
- _dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, wxGBPosition (r, 0));
+ _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY);
grid->Add (_dcp_content_type, wxGBPosition (r, 1));
++r;
- video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Frame Rate"), true, wxGBPosition (r, 0)));
- _source_frame_rate = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
- grid->Add (video_control (_source_frame_rate), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
- ++r;
-
{
- add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0));
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Frame Rate"), true, wxGBPosition (r, 0));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _dcp_frame_rate = new wxChoice (_film_panel, wxID_ANY);
+ _dcp_frame_rate = new wxChoice (_dcp_panel, wxID_ANY);
s->Add (_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
- _best_dcp_frame_rate = new wxButton (_film_panel, wxID_ANY, _("Use best"));
- s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 6);
+ _best_dcp_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
+ s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
grid->Add (s, wxGBPosition (r, 1));
}
++r;
- _frame_rate_description = new wxStaticText (_film_panel, wxID_ANY, wxT ("\n \n "), wxDefaultPosition, wxDefaultSize);
- grid->Add (video_control (_frame_rate_description), wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
- wxFont font = _frame_rate_description->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _frame_rate_description->SetFont(font);
- ++r;
-
- video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), true, wxGBPosition (r, 0)));
- _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
- grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
- ++r;
-
-
{
- video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), true, wxGBPosition (r, 0)));
- wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- video_control (add_label_to_sizer (s, _film_panel, _("Start"), true));
- _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (video_control (_trim_start));
- video_control (add_label_to_sizer (s, _film_panel, _("End"), true));
- _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (video_control (_trim_end));
-
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0));
+ wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY);
+ s->Add (_j2k_bandwidth, 1);
+ add_label_to_sizer (s, _dcp_panel, _("MBps"), false);
grid->Add (s, wxGBPosition (r, 1));
}
++r;
- video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), true, wxGBPosition (r, 0)));
- _trim_type = new wxChoice (_film_panel, wxID_ANY);
- grid->Add (video_control (_trim_type), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+ add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), true, wxGBPosition (r, 0));
+ _scaler = new wxChoice (_dcp_panel, wxID_ANY);
+ grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++r;
- _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
- video_control (_dcp_ab);
- grid->Add (_dcp_ab, wxGBPosition (r, 0));
- ++r;
+ vector<Scaler const *> const sc = Scaler::all ();
+ for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
+ _scaler->Append (std_to_wx ((*i)->name()));
+ }
- /* STILL-only stuff */
- {
- still_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Duration"), true, wxGBPosition (r, 0)));
- wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _still_duration = new wxSpinCtrl (_film_panel);
- still_control (_still_duration);
- s->Add (_still_duration, 1, wxEXPAND);
- /// TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time
- still_control (add_label_to_sizer (s, _film_panel, _("s"), false));
- grid->Add (s, wxGBPosition (r, 1));
+ vector<Ratio const *> const ratio = Ratio::all ();
+ for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) {
+ _container->Append (std_to_wx ((*i)->nickname ()));
}
- ++r;
vector<DCPContentType const *> const ct = DCPContentType::all ();
for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
@@ -215,73 +184,64 @@ FilmEditor::make_film_panel ()
_dcp_frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
}
- _trim_type->Append (_("encode all frames and play the subset"));
- _trim_type->Append (_("encode only the subset"));
+ _j2k_bandwidth->SetRange (50, 250);
}
void
FilmEditor::connect_to_widgets ()
{
- _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
- _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
- _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
- _format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
- _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
- _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this);
- _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
- _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
- _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
- _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
- _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
- _scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
- _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
- _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
- _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
- _pad_with_silence->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this);
- _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this);
- _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
- _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
- _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
- _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
- _trim_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::trim_type_changed), 0, this);
- _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
- _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
- _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
- _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
- _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
- _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
- _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
- _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
+ _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
+ _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
+ _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
+ _container->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::container_changed), 0, this);
+ _ratio->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ratio_changed), 0, this);
+ _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this);
+ _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this);
+ _content_add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
+ _content_remove->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
+ _content_timeline->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_timeline_clicked), 0, this);
+ _loop_content->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this);
+ _loop_count->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::loop_count_changed), 0, this);
+ _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
+ _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
+ _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
+ _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
+ _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
+ _scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
+ _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
+ _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
+ _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
+// _pad_with_silence->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::pad_with_silence_toggled), 0, this);
+// _minimum_audio_channels->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::minimum_audio_channels_changed), 0, this);
+ _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
+ _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
+ _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
+ _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
+ _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
+ _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
_audio_gain_calculate_button->Connect (
wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
);
- _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
- _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
- _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
- _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
- for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- _external_audio[i]->Connect (
- wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this
- );
- }
+ _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
+ _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
+ _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
+ _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
+ _audio_mapping->Changed.connect (boost::bind (&FilmEditor::audio_mapping_changed, this, _1));
+ _start->Changed.connect (boost::bind (&FilmEditor::start_changed, this));
+ _length->Changed.connect (boost::bind (&FilmEditor::length_changed, this));
}
void
FilmEditor::make_video_panel ()
{
- _video_panel = new wxPanel (_notebook);
- _video_sizer = new wxBoxSizer (wxVERTICAL);
- _video_panel->SetSizer (_video_sizer);
+ _video_panel = new wxPanel (_content_notebook);
+ wxBoxSizer* video_sizer = new wxBoxSizer (wxVERTICAL);
+ _video_panel->SetSizer (video_sizer);
- wxGridBagSizer* grid = new wxGridBagSizer (DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
- _video_sizer->Add (grid, 0, wxALL, 8);
+ wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ video_sizer->Add (grid, 0, wxALL, 8);
int r = 0;
- add_label_to_grid_bag_sizer (grid, _video_panel, _("Format"), true, wxGBPosition (r, 0));
- _format = new wxChoice (_video_panel, wxID_ANY);
- grid->Add (_format, wxGBPosition (r, 1));
- ++r;
-
add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), true, wxGBPosition (r, 0));
_left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
grid->Add (_left_crop, wxGBPosition (r, 1));
@@ -302,6 +262,11 @@ FilmEditor::make_video_panel ()
grid->Add (_bottom_crop, wxGBPosition (r, 1));
++r;
+ add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), true, wxGBPosition (r, 0));
+ _ratio = new wxChoice (_video_panel, wxID_ANY);
+ grid->Add (_ratio, wxGBPosition (r, 1));
+ ++r;
+
_scaling_description = new wxStaticText (_video_panel, wxID_ANY, wxT ("\n \n \n \n"), wxDefaultPosition, wxDefaultSize);
grid->Add (_scaling_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
wxFont font = _scaling_description->GetFont();
@@ -312,28 +277,16 @@ FilmEditor::make_video_panel ()
/* VIDEO-only stuff */
{
- video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0)));
+ add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), true, wxGBPosition (r, 0));
wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_filters = new wxStaticText (_video_panel, wxID_ANY, _("None"));
- video_control (_filters);
s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
_filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit..."));
- video_control (_filters_button);
s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL);
grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
}
++r;
- video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), true, wxGBPosition (r, 0)));
- _scaler = new wxChoice (_video_panel, wxID_ANY);
- grid->Add (video_control (_scaler), wxGBPosition (r, 1));
- ++r;
-
- vector<Scaler const *> const sc = Scaler::all ();
- for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
- _scaler->Append (std_to_wx ((*i)->name()));
- }
-
add_label_to_grid_bag_sizer (grid, _video_panel, _("Colour look-up table"), true, wxGBPosition (r, 0));
_colour_lut = new wxChoice (_video_panel, wxID_ANY);
for (int i = 0; i < 2; ++i) {
@@ -343,62 +296,117 @@ FilmEditor::make_video_panel ()
grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
++r;
- {
- add_label_to_grid_bag_sizer (grid, _video_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0));
- wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY);
- s->Add (_j2k_bandwidth, 1);
- add_label_to_sizer (s, _video_panel, _("MBps"), false);
- grid->Add (s, wxGBPosition (r, 1));
- }
- ++r;
-
_left_crop->SetRange (0, 1024);
_top_crop->SetRange (0, 1024);
_right_crop->SetRange (0, 1024);
_bottom_crop->SetRange (0, 1024);
- _still_duration->SetRange (1, 60 * 60);
- _trim_start->SetRange (0, 24 * 60 * 60);
- _trim_end->SetRange (0, 24 * 60 * 60);
- _j2k_bandwidth->SetRange (50, 250);
+}
+
+void
+FilmEditor::make_content_panel ()
+{
+ _content_panel = new wxPanel (_main_notebook);
+ _content_sizer = new wxBoxSizer (wxVERTICAL);
+ _content_panel->SetSizer (_content_sizer);
+
+ {
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+
+ _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL);
+ s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
+
+ _content->InsertColumn (0, wxT(""));
+ _content->SetColumnWidth (0, 512);
+
+ wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
+ _content_add = new wxButton (_content_panel, wxID_ANY, _("Add..."));
+ b->Add (_content_add, 1, wxEXPAND | wxLEFT | wxRIGHT);
+ _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
+ b->Add (_content_remove, 1, wxEXPAND | wxLEFT | wxRIGHT);
+ _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
+ b->Add (_content_timeline, 1, wxEXPAND | wxLEFT | wxRIGHT);
+
+ s->Add (b, 0, wxALL, 4);
+
+ _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6);
+ }
+
+ wxBoxSizer* h = new wxBoxSizer (wxHORIZONTAL);
+ _loop_content = new wxCheckBox (_content_panel, wxID_ANY, _("Loop everything"));
+ h->Add (_loop_content, 0, wxALL, 6);
+ _loop_count = new wxSpinCtrl (_content_panel, wxID_ANY);
+ h->Add (_loop_count, 0, wxALL, 6);
+ add_label_to_sizer (h, _content_panel, _("times"), false);
+ _content_sizer->Add (h, 0, wxALL, 6);
+
+ _content_notebook = new wxNotebook (_content_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_LEFT);
+ _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
+
+ make_video_panel ();
+ _content_notebook->AddPage (_video_panel, _("Video"), false);
+ make_audio_panel ();
+ _content_notebook->AddPage (_audio_panel, _("Audio"), false);
+ make_subtitle_panel ();
+ _content_notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
+ make_timing_panel ();
+ _content_notebook->AddPage (_timing_panel, _("Timing"), false);
+
+ _loop_count->SetRange (2, 1024);
}
void
FilmEditor::make_audio_panel ()
{
- _audio_panel = new wxPanel (_notebook);
- _audio_sizer = new wxBoxSizer (wxVERTICAL);
- _audio_panel->SetSizer (_audio_sizer);
+ _audio_panel = new wxPanel (_content_notebook);
+ wxBoxSizer* audio_sizer = new wxBoxSizer (wxVERTICAL);
+ _audio_panel->SetSizer (audio_sizer);
- wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
- _audio_sizer->Add (grid, 0, wxALL, 8);
+ wxFlexGridSizer* grid = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ audio_sizer->Add (grid, 0, wxALL, 8);
_show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
grid->Add (_show_audio, 1);
grid->AddSpacer (0);
+ grid->AddSpacer (0);
+ add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true);
{
- video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain"), true));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_audio_gain = new wxSpinCtrl (_audio_panel);
- s->Add (video_control (_audio_gain), 1);
- video_control (add_label_to_sizer (s, _audio_panel, _("dB"), false));
- _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
- video_control (_audio_gain_calculate_button);
- s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
- grid->Add (s);
+ s->Add (_audio_gain, 1);
+ add_label_to_sizer (s, _audio_panel, _("dB"), false);
+ grid->Add (s, 1);
}
+
+ _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
+ grid->Add (_audio_gain_calculate_button);
+ add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), false);
{
- video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay"), true));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_audio_delay = new wxSpinCtrl (_audio_panel);
- s->Add (video_control (_audio_delay), 1);
+ s->Add (_audio_delay, 1);
/// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
- video_control (add_label_to_sizer (s, _audio_panel, _("ms"), false));
+ add_label_to_sizer (s, _audio_panel, _("ms"), false);
grid->Add (s);
}
+ grid->AddSpacer (0);
+
+ add_label_to_sizer (grid, _audio_panel, _("Audio Stream"), true);
+ _audio_stream = new wxChoice (_audio_panel, wxID_ANY);
+ grid->Add (_audio_stream, 1);
+ _audio_description = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
+ grid->AddSpacer (0);
+
+ grid->Add (_audio_description, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
+ grid->AddSpacer (0);
+ grid->AddSpacer (0);
+
+ _audio_mapping = new AudioMappingView (_audio_panel);
+ audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
+
+#if 0
{
_pad_with_silence = new wxCheckBox (_audio_panel, wxID_ANY, _("Pad with silence to"));
grid->Add (_pad_with_silence);
@@ -419,139 +427,117 @@ FilmEditor::make_audio_panel ()
s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
grid->Add (s);
}
-
- _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
- grid->Add (_use_external_audio);
- grid->AddSpacer (0);
-
- for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- add_label_to_sizer (grid, _audio_panel, std_to_wx (audio_channel_name (i)), true);
- _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
- grid->Add (_external_audio[i], 1, wxEXPAND);
- }
+#endif
_audio_gain->SetRange (-60, 60);
_audio_delay->SetRange (-1000, 1000);
- _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
+// _minimum_audio_channels->SetRange (0, MAX_AUDIO_CHANNELS);
}
void
FilmEditor::make_subtitle_panel ()
{
- _subtitle_panel = new wxPanel (_notebook);
- _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
- _subtitle_panel->SetSizer (_subtitle_sizer);
- wxFlexGridSizer* grid = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
- _subtitle_sizer->Add (grid, 0, wxALL, 8);
+ _subtitle_panel = new wxPanel (_content_notebook);
+ wxBoxSizer* subtitle_sizer = new wxBoxSizer (wxVERTICAL);
+ _subtitle_panel->SetSizer (subtitle_sizer);
+ wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ subtitle_sizer->Add (grid, 0, wxALL, 8);
_with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
- video_control (_with_subtitles);
grid->Add (_with_subtitles, 1);
+ grid->AddSpacer (0);
- _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
- grid->Add (video_control (_subtitle_stream));
-
{
- video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true));
+ add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"), true);
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_subtitle_offset = new wxSpinCtrl (_subtitle_panel);
s->Add (_subtitle_offset);
- video_control (add_label_to_sizer (s, _subtitle_panel, _("pixels"), false));
+ add_label_to_sizer (s, _subtitle_panel, _("pixels"), false);
grid->Add (s);
}
{
- video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true));
+ add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"), true);
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_subtitle_scale = new wxSpinCtrl (_subtitle_panel);
- s->Add (video_control (_subtitle_scale));
- video_control (add_label_to_sizer (s, _subtitle_panel, _("%"), false));
+ s->Add (_subtitle_scale);
+ add_label_to_sizer (s, _subtitle_panel, _("%"), false);
grid->Add (s);
}
+ add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"), true);
+ _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
+ grid->Add (_subtitle_stream, 1, wxEXPAND | wxALL, 6);
+ grid->AddSpacer (0);
+
_subtitle_offset->SetRange (-1024, 1024);
_subtitle_scale->SetRange (1, 1000);
}
+void
+FilmEditor::make_timing_panel ()
+{
+ _timing_panel = new wxPanel (_content_notebook);
+ wxBoxSizer* timing_sizer = new wxBoxSizer (wxVERTICAL);
+ _timing_panel->SetSizer (timing_sizer);
+ wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+ timing_sizer->Add (grid, 0, wxALL, 8);
+
+ add_label_to_sizer (grid, _timing_panel, _("Start time"), true);
+ _start = new Timecode (_timing_panel);
+ grid->Add (_start);
+ add_label_to_sizer (grid, _timing_panel, _("Length"), true);
+ _length = new Timecode (_timing_panel);
+ grid->Add (_length);
+}
+
+
/** Called when the left crop widget has been changed */
void
FilmEditor::left_crop_changed (wxCommandEvent &)
{
- if (!_film) {
+ shared_ptr<VideoContent> c = selected_video_content ();
+ if (!c) {
return;
}
- _film->set_left_crop (_left_crop->GetValue ());
+ c->set_left_crop (_left_crop->GetValue ());
}
/** Called when the right crop widget has been changed */
void
FilmEditor::right_crop_changed (wxCommandEvent &)
{
- if (!_film) {
+ shared_ptr<VideoContent> c = selected_video_content ();
+ if (!c) {
return;
}
- _film->set_right_crop (_right_crop->GetValue ());
+ c->set_right_crop (_right_crop->GetValue ());
}
/** Called when the top crop widget has been changed */
void
FilmEditor::top_crop_changed (wxCommandEvent &)
{
- if (!_film) {
+ shared_ptr<VideoContent> c = selected_video_content ();
+ if (!c) {
return;
}
- _film->set_top_crop (_top_crop->GetValue ());
+ c->set_top_crop (_top_crop->GetValue ());
}
/** Called when the bottom crop value has been changed */
void
FilmEditor::bottom_crop_changed (wxCommandEvent &)
{
- if (!_film) {
- return;
- }
-
- _film->set_bottom_crop (_bottom_crop->GetValue ());
-}
-
-/** Called when the content filename has been changed */
-void
-FilmEditor::content_changed (wxCommandEvent &)
-{
- if (!_film) {
- return;
- }
-
- try {
- _film->set_content (wx_to_std (_content->GetPath ()));
- } catch (std::exception& e) {
- _content->SetPath (std_to_wx (_film->directory ()));
- error_dialog (this, wxString::Format (_("Could not set content: %s"), std_to_wx (e.what()).data()));
- }
-}
-
-void
-FilmEditor::trust_content_header_changed (wxCommandEvent &)
-{
- if (!_film) {
+ shared_ptr<VideoContent> c = selected_video_content ();
+ if (!c) {
return;
}
- _film->set_trust_content_header (_trust_content_header->GetValue ());
-}
-
-/** Called when the DCP A/B switch has been toggled */
-void
-FilmEditor::dcp_ab_toggled (wxCommandEvent &)
-{
- if (!_film) {
- return;
- }
-
- _film->set_dcp_ab (_dcp_ab->GetValue ());
+ c->set_bottom_crop (_bottom_crop->GetValue ());
}
/** Called when the name widget has been changed */
@@ -612,7 +598,7 @@ FilmEditor::dcp_frame_rate_changed (wxCommandEvent &)
return;
}
- _film->set_dcp_frame_rate (
+ _film->set_dcp_video_frame_rate (
boost::lexical_cast<int> (
wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ()))
)
@@ -639,118 +625,30 @@ FilmEditor::film_changed (Film::Property p)
case Film::NONE:
break;
case Film::CONTENT:
- checked_set (_content, _film->content ());
- setup_visibility ();
- setup_formats ();
- setup_subtitle_control_sensitivity ();
- setup_streams ();
- setup_show_audio_sensitivity ();
- setup_frame_rate_description ();
- setup_minimum_audio_channels ();
- break;
- case Film::TRUST_CONTENT_HEADER:
- checked_set (_trust_content_header, _film->trust_content_header ());
- break;
- case Film::SUBTITLE_STREAMS:
+ setup_content ();
setup_subtitle_control_sensitivity ();
- setup_streams ();
- break;
- case Film::CONTENT_AUDIO_STREAMS:
- setup_streams ();
setup_show_audio_sensitivity ();
- setup_frame_rate_description ();
setup_minimum_audio_channels ();
break;
- case Film::FORMAT:
- {
- int n = 0;
- vector<Format const *>::iterator i = _formats.begin ();
- while (i != _formats.end() && *i != _film->format ()) {
- ++i;
- ++n;
- }
- if (i == _formats.end()) {
- checked_set (_format, -1);
- } else {
- checked_set (_format, n);
- }
- setup_dcp_name ();
- setup_scaling_description ();
- break;
- }
- case Film::CROP:
- checked_set (_left_crop, _film->crop().left);
- checked_set (_right_crop, _film->crop().right);
- checked_set (_top_crop, _film->crop().top);
- checked_set (_bottom_crop, _film->crop().bottom);
- setup_scaling_description ();
+ case Film::LOOP:
+ checked_set (_loop_content, _film->loop() > 1);
+ checked_set (_loop_count, _film->loop());
+ setup_loop_sensitivity ();
break;
- case Film::FILTERS:
- {
- pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
- if (p.first.empty () && p.second.empty ()) {
- _filters->SetLabel (_("None"));
- } else {
- string const b = p.first + " " + p.second;
- _filters->SetLabel (std_to_wx (b));
- }
- _film_sizer->Layout ();
+ case Film::CONTAINER:
+ setup_container ();
break;
- }
case Film::NAME:
checked_set (_name, _film->name());
setup_dcp_name ();
break;
- case Film::SOURCE_FRAME_RATE:
- s << fixed << setprecision(2) << _film->source_frame_rate();
- _source_frame_rate->SetLabel (std_to_wx (s.str ()));
- setup_frame_rate_description ();
- break;
- case Film::SIZE:
- setup_scaling_description ();
- break;
- case Film::LENGTH:
- if (_film->source_frame_rate() > 0 && _film->length()) {
- s << _film->length().get() << " "
- << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->length().get() / _film->source_frame_rate());
- } else if (_film->length()) {
- s << _film->length().get() << " "
- << wx_to_std (_("frames"));
- }
- _length->SetLabel (std_to_wx (s.str ()));
- if (_film->length()) {
- _trim_start->SetRange (0, _film->length().get());
- _trim_end->SetRange (0, _film->length().get());
- }
- break;
case Film::DCP_CONTENT_TYPE:
checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
setup_dcp_name ();
break;
- case Film::DCP_AB:
- checked_set (_dcp_ab, _film->dcp_ab ());
- break;
case Film::SCALER:
checked_set (_scaler, Scaler::as_index (_film->scaler ()));
break;
- case Film::TRIM_START:
- checked_set (_trim_start, _film->trim_start());
- break;
- case Film::TRIM_END:
- checked_set (_trim_end, _film->trim_end());
- break;
- case Film::TRIM_TYPE:
- checked_set (_trim_type, _film->trim_type() == Film::CPL ? 0 : 1);
- break;
- case Film::AUDIO_GAIN:
- checked_set (_audio_gain, _film->audio_gain ());
- break;
- case Film::AUDIO_DELAY:
- checked_set (_audio_delay, _film->audio_delay ());
- break;
- case Film::STILL_DURATION:
- checked_set (_still_duration, _film->still_duration ());
- break;
case Film::WITH_SUBTITLES:
checked_set (_with_subtitles, _film->with_subtitles ());
setup_subtitle_control_sensitivity ();
@@ -775,109 +673,177 @@ FilmEditor::film_changed (Film::Property p)
case Film::DCI_METADATA:
setup_dcp_name ();
break;
- case Film::CONTENT_AUDIO_STREAM:
- if (_film->content_audio_stream()) {
- checked_set (_audio_stream, _film->content_audio_stream()->to_string());
- }
- setup_dcp_name ();
- setup_audio_details ();
- setup_audio_control_sensitivity ();
- setup_show_audio_sensitivity ();
- setup_frame_rate_description ();
- setup_minimum_audio_channels ();
- break;
- case Film::USE_CONTENT_AUDIO:
- _film->log()->log (String::compose ("Film::USE_CONTENT_AUDIO changed; setting GUI using %1", _film->use_content_audio ()));
- checked_set (_use_content_audio, _film->use_content_audio());
- checked_set (_use_external_audio, !_film->use_content_audio());
- setup_dcp_name ();
- setup_audio_details ();
- setup_audio_control_sensitivity ();
- setup_show_audio_sensitivity ();
- setup_frame_rate_description ();
- setup_minimum_audio_channels ();
- break;
- case Film::SUBTITLE_STREAM:
- if (_film->subtitle_stream()) {
- checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
- }
- break;
- case Film::EXTERNAL_AUDIO:
+ case Film::DCP_VIDEO_FRAME_RATE:
{
- vector<string> a = _film->external_audio ();
- for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) {
- checked_set (_external_audio[i], a[i]);
- }
- setup_audio_details ();
- setup_show_audio_sensitivity ();
- setup_frame_rate_description ();
- setup_minimum_audio_channels ();
- break;
- }
- case Film::DCP_FRAME_RATE:
+ bool done = false;
for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) {
- if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) {
- if (_dcp_frame_rate->GetSelection() != int(i)) {
- _dcp_frame_rate->SetSelection (i);
- break;
- }
+ if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_video_frame_rate())) {
+ checked_set (_dcp_frame_rate, i);
+ done = true;
+ break;
}
}
- if (_film->source_frame_rate()) {
- _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->source_frame_rate ()) != _film->dcp_frame_rate ());
- } else {
- _best_dcp_frame_rate->Disable ();
+ if (!done) {
+ checked_set (_dcp_frame_rate, -1);
}
- setup_frame_rate_description ();
+ _best_dcp_frame_rate->Enable (_film->best_dcp_video_frame_rate () != _film->dcp_video_frame_rate ());
+ break;
+ }
case Film::MINIMUM_AUDIO_CHANNELS:
- checked_set (_minimum_audio_channels, _film->minimum_audio_channels ());
+// checked_set (_minimum_audio_channels, _film->minimum_audio_channels ());
setup_minimum_audio_channels ();
break;
}
}
void
-FilmEditor::setup_frame_rate_description ()
+FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
{
- wxString d;
- int lines = 0;
-
- if (_film->source_frame_rate()) {
- d << std_to_wx (FrameRateConversion (_film->source_frame_rate(), _film->dcp_frame_rate()).description);
- ++lines;
-#ifdef HAVE_SWRESAMPLE
- if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate ()) {
- d << wxString::Format (
- _("Audio will be resampled from %dHz to %dHz\n"),
- _film->audio_stream()->sample_rate(),
- _film->target_audio_sample_rate()
- );
- ++lines;
- }
-#endif
+ if (!_film) {
+ /* We call this method ourselves (as well as using it as a signal handler)
+ so _film can be 0.
+ */
+ return;
}
- for (int i = lines; i < 2; ++i) {
- d << wxT ("\n ");
+ shared_ptr<Content> content = weak_content.lock ();
+ shared_ptr<VideoContent> video_content;
+ shared_ptr<AudioContent> audio_content;
+ shared_ptr<FFmpegContent> ffmpeg_content;
+ if (content) {
+ video_content = dynamic_pointer_cast<VideoContent> (content);
+ audio_content = dynamic_pointer_cast<AudioContent> (content);
+ ffmpeg_content = dynamic_pointer_cast<FFmpegContent> (content);
}
- _frame_rate_description->SetLabel (d);
+ /* We can't use case {} here */
+
+ if (property == ContentProperty::START) {
+ if (content) {
+ _start->set (content->start (), _film->dcp_video_frame_rate ());
+ } else {
+ _start->set (0, 24);
+ }
+ } else if (property == ContentProperty::LENGTH) {
+ if (content) {
+ _length->set (content->length (), _film->dcp_video_frame_rate ());
+ } else {
+ _length->set (0, 24);
+ }
+ } else if (property == VideoContentProperty::VIDEO_CROP) {
+ checked_set (_left_crop, video_content ? video_content->crop().left : 0);
+ checked_set (_right_crop, video_content ? video_content->crop().right : 0);
+ checked_set (_top_crop, video_content ? video_content->crop().top : 0);
+ checked_set (_bottom_crop, video_content ? video_content->crop().bottom : 0);
+ setup_scaling_description ();
+ } else if (property == VideoContentProperty::VIDEO_RATIO) {
+ if (video_content) {
+ int n = 0;
+ vector<Ratio const *> ratios = Ratio::all ();
+ vector<Ratio const *>::iterator i = ratios.begin ();
+ while (i != ratios.end() && *i != video_content->ratio()) {
+ ++i;
+ ++n;
+ }
+
+ if (i == ratios.end()) {
+ checked_set (_ratio, -1);
+ } else {
+ checked_set (_ratio, n);
+ }
+ } else {
+ checked_set (_ratio, -1);
+ }
+ } else if (property == AudioContentProperty::AUDIO_GAIN) {
+ checked_set (_audio_gain, audio_content ? audio_content->audio_gain() : 0);
+ } else if (property == AudioContentProperty::AUDIO_DELAY) {
+ checked_set (_audio_delay, audio_content ? audio_content->audio_delay() : 0);
+ } else if (property == AudioContentProperty::AUDIO_MAPPING) {
+ _audio_mapping->set (audio_content ? audio_content->audio_mapping () : AudioMapping ());
+ } else if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
+ _subtitle_stream->Clear ();
+ if (ffmpeg_content) {
+ vector<shared_ptr<FFmpegSubtitleStream> > s = ffmpeg_content->subtitle_streams ();
+ if (s.empty ()) {
+ _subtitle_stream->Enable (false);
+ }
+ for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
+ _subtitle_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+ }
+
+ if (ffmpeg_content->subtitle_stream()) {
+ checked_set (_subtitle_stream, lexical_cast<string> (ffmpeg_content->subtitle_stream()->id));
+ } else {
+ _subtitle_stream->SetSelection (wxNOT_FOUND);
+ }
+ }
+ setup_subtitle_control_sensitivity ();
+ } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
+ _audio_stream->Clear ();
+ if (ffmpeg_content) {
+ vector<shared_ptr<FFmpegAudioStream> > a = ffmpeg_content->audio_streams ();
+ for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
+ _audio_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+ }
+
+ if (ffmpeg_content->audio_stream()) {
+ checked_set (_audio_stream, lexical_cast<string> (ffmpeg_content->audio_stream()->id));
+ }
+ }
+ setup_show_audio_sensitivity ();
+ } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
+ setup_dcp_name ();
+ setup_show_audio_sensitivity ();
+ } else if (property == FFmpegContentProperty::FILTERS) {
+ if (ffmpeg_content) {
+ pair<string, string> p = Filter::ffmpeg_strings (ffmpeg_content->filters ());
+ if (p.first.empty () && p.second.empty ()) {
+ _filters->SetLabel (_("None"));
+ } else {
+ string const b = p.first + " " + p.second;
+ _filters->SetLabel (std_to_wx (b));
+ }
+ _dcp_sizer->Layout ();
+ }
+ }
}
-/** Called when the format widget has been changed */
void
-FilmEditor::format_changed (wxCommandEvent &)
+FilmEditor::setup_container ()
+{
+ int n = 0;
+ vector<Ratio const *> ratios = Ratio::all ();
+ vector<Ratio const *>::iterator i = ratios.begin ();
+ while (i != ratios.end() && *i != _film->container ()) {
+ ++i;
+ ++n;
+ }
+
+ if (i == ratios.end()) {
+ checked_set (_container, -1);
+ } else {
+ checked_set (_container, n);
+ }
+
+ setup_dcp_name ();
+ setup_scaling_description ();
+}
+
+/** Called when the container widget has been changed */
+void
+FilmEditor::container_changed (wxCommandEvent &)
{
if (!_film) {
return;
}
- int const n = _format->GetSelection ();
+ int const n = _container->GetSelection ();
if (n >= 0) {
- assert (n < int (_formats.size()));
- _film->set_format (_formats[n]);
+ vector<Ratio const *> ratios = Ratio::all ();
+ assert (n < int (ratios.size()));
+ _film->set_container (ratios[n]);
}
}
@@ -899,12 +865,17 @@ FilmEditor::dcp_content_type_changed (wxCommandEvent &)
void
FilmEditor::set_film (shared_ptr<Film> f)
{
- _film = f;
+ set_things_sensitive (f != 0);
- set_things_sensitive (_film != 0);
+ if (_film == f) {
+ return;
+ }
+
+ _film = f;
if (_film) {
_film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
+ _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2));
}
if (_film) {
@@ -913,41 +884,23 @@ FilmEditor::set_film (shared_ptr<Film> f)
FileChanged ("");
}
- if (_audio_dialog) {
- _audio_dialog->set_film (_film);
- }
-
film_changed (Film::NAME);
film_changed (Film::USE_DCI_NAME);
film_changed (Film::CONTENT);
- film_changed (Film::TRUST_CONTENT_HEADER);
+ film_changed (Film::LOOP);
film_changed (Film::DCP_CONTENT_TYPE);
- film_changed (Film::FORMAT);
- film_changed (Film::CROP);
- film_changed (Film::FILTERS);
+ film_changed (Film::CONTAINER);
film_changed (Film::SCALER);
- film_changed (Film::TRIM_START);
- film_changed (Film::TRIM_END);
- film_changed (Film::TRIM_TYPE);
- film_changed (Film::DCP_AB);
- film_changed (Film::CONTENT_AUDIO_STREAM);
- film_changed (Film::EXTERNAL_AUDIO);
- film_changed (Film::USE_CONTENT_AUDIO);
- film_changed (Film::AUDIO_GAIN);
- film_changed (Film::AUDIO_DELAY);
- film_changed (Film::STILL_DURATION);
film_changed (Film::WITH_SUBTITLES);
film_changed (Film::SUBTITLE_OFFSET);
film_changed (Film::SUBTITLE_SCALE);
film_changed (Film::COLOUR_LUT);
film_changed (Film::J2K_BANDWIDTH);
film_changed (Film::DCI_METADATA);
- film_changed (Film::SIZE);
- film_changed (Film::LENGTH);
- film_changed (Film::CONTENT_AUDIO_STREAMS);
- film_changed (Film::SUBTITLE_STREAMS);
- film_changed (Film::SOURCE_FRAME_RATE);
- film_changed (Film::DCP_FRAME_RATE);
+ film_changed (Film::DCP_VIDEO_FRAME_RATE);
+
+ wxListEvent ev;
+ content_selection_changed (ev);
}
/** Updates the sensitivity of lots of widgets to a given value.
@@ -961,41 +914,48 @@ FilmEditor::set_things_sensitive (bool s)
_name->Enable (s);
_use_dci_name->Enable (s);
_edit_dci_button->Enable (s);
- _format->Enable (s);
+ _ratio->Enable (s);
_content->Enable (s);
- _trust_content_header->Enable (s);
_left_crop->Enable (s);
_right_crop->Enable (s);
_top_crop->Enable (s);
_bottom_crop->Enable (s);
_filters_button->Enable (s);
_scaler->Enable (s);
- _audio_stream->Enable (s);
_dcp_content_type->Enable (s);
+ _best_dcp_frame_rate->Enable (s);
_dcp_frame_rate->Enable (s);
- _trim_start->Enable (s);
- _trim_end->Enable (s);
- _trim_type->Enable (s);
- _dcp_ab->Enable (s);
_colour_lut->Enable (s);
_j2k_bandwidth->Enable (s);
_audio_gain->Enable (s);
_audio_gain_calculate_button->Enable (s);
_show_audio->Enable (s);
_audio_delay->Enable (s);
- _still_duration->Enable (s);
+ _container->Enable (s);
+ _loop_content->Enable (s);
+ _loop_count->Enable (s);
setup_subtitle_control_sensitivity ();
- setup_audio_control_sensitivity ();
setup_show_audio_sensitivity ();
+ setup_content_sensitivity ();
}
/** Called when the `Edit filters' button has been clicked */
void
FilmEditor::edit_filters_clicked (wxCommandEvent &)
{
- FilterDialog* d = new FilterDialog (this, _film->filters());
- d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1));
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+ if (!fc) {
+ return;
+ }
+
+ FilterDialog* d = new FilterDialog (this, fc->filters());
+ d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1));
d->ShowModal ();
d->Destroy ();
}
@@ -1017,106 +977,40 @@ FilmEditor::scaler_changed (wxCommandEvent &)
void
FilmEditor::audio_gain_changed (wxCommandEvent &)
{
- if (!_film) {
+ shared_ptr<AudioContent> ac = selected_audio_content ();
+ if (!ac) {
return;
}
- _film->set_audio_gain (_audio_gain->GetValue ());
+ ac->set_audio_gain (_audio_gain->GetValue ());
}
void
FilmEditor::audio_delay_changed (wxCommandEvent &)
{
- if (!_film) {
+ shared_ptr<AudioContent> ac = selected_audio_content ();
+ if (!ac) {
return;
}
- _film->set_audio_delay (_audio_delay->GetValue ());
-}
-
-wxControl *
-FilmEditor::video_control (wxControl* c)
-{
- _video_controls.push_back (c);
- return c;
-}
-
-wxControl *
-FilmEditor::still_control (wxControl* c)
-{
- _still_controls.push_back (c);
- return c;
+ ac->set_audio_delay (_audio_delay->GetValue ());
}
void
-FilmEditor::setup_visibility ()
+FilmEditor::setup_main_notebook_size ()
{
- ContentType c = VIDEO;
-
- if (_film) {
- c = _film->content_type ();
- }
-
- for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
- (*i)->Show (c == VIDEO);
- }
-
- for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
- (*i)->Show (c == STILL);
- }
+ _main_notebook->InvalidateBestSize ();
- setup_notebook_size ();
-}
+ _content_sizer->Layout ();
+ _content_sizer->SetSizeHints (_content_panel);
+ _dcp_sizer->Layout ();
+ _dcp_sizer->SetSizeHints (_dcp_panel);
-void
-FilmEditor::setup_notebook_size ()
-{
- _notebook->InvalidateBestSize ();
-
- _film_sizer->Layout ();
- _film_sizer->SetSizeHints (_film_panel);
- _video_sizer->Layout ();
- _video_sizer->SetSizeHints (_video_panel);
- _audio_sizer->Layout ();
- _audio_sizer->SetSizeHints (_audio_panel);
- _subtitle_sizer->Layout ();
- _subtitle_sizer->SetSizeHints (_subtitle_panel);
-
- _notebook->Fit ();
+ _main_notebook->Fit ();
Fit ();
}
void
-FilmEditor::still_duration_changed (wxCommandEvent &)
-{
- if (!_film) {
- return;
- }
-
- _film->set_still_duration (_still_duration->GetValue ());
-}
-
-void
-FilmEditor::trim_start_changed (wxCommandEvent &)
-{
- if (!_film) {
- return;
- }
-
- _film->set_trim_start (_trim_start->GetValue ());
-}
-
-void
-FilmEditor::trim_end_changed (wxCommandEvent &)
-{
- if (!_film) {
- return;
- }
-
- _film->set_trim_end (_trim_end->GetValue ());
-}
-
-void
FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
{
GainCalculatorDialog* d = new GainCalculatorDialog (this);
@@ -1144,29 +1038,16 @@ FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
}
void
-FilmEditor::setup_formats ()
+FilmEditor::setup_ratios ()
{
- ContentType c = VIDEO;
+ _ratios = Ratio::all ();
- if (_film) {
- c = _film->content_type ();
+ _ratio->Clear ();
+ for (vector<Ratio const *>::iterator i = _ratios.begin(); i != _ratios.end(); ++i) {
+ _ratio->Append (std_to_wx ((*i)->nickname ()));
}
-
- _formats.clear ();
- vector<Format const *> fmt = Format::all ();
- for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
- if (c == VIDEO || (c == STILL && dynamic_cast<VariableFormat const *> (*i))) {
- _formats.push_back (*i);
- }
- }
-
- _format->Clear ();
- for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
- _format->Append (std_to_wx ((*i)->name ()));
- }
-
- _film_sizer->Layout ();
+ _dcp_sizer->Layout ();
}
void
@@ -1184,7 +1065,7 @@ FilmEditor::setup_subtitle_control_sensitivity ()
{
bool h = false;
if (_generally_sensitive && _film) {
- h = !_film->subtitle_streams().empty();
+ h = _film->has_subtitles ();
}
_with_subtitles->Enable (h);
@@ -1194,30 +1075,11 @@ FilmEditor::setup_subtitle_control_sensitivity ()
j = _film->with_subtitles ();
}
- _subtitle_stream->Enable (j);
_subtitle_offset->Enable (j);
_subtitle_scale->Enable (j);
}
void
-FilmEditor::setup_audio_control_sensitivity ()
-{
- _use_content_audio->Enable (_generally_sensitive && _film && !_film->content_audio_streams().empty());
- _use_external_audio->Enable (_generally_sensitive);
-
- bool const source = _generally_sensitive && _use_content_audio->GetValue();
- bool const external = _generally_sensitive && _use_external_audio->GetValue();
-
- _audio_stream->Enable (source);
- for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- _external_audio[i]->Enable (external);
- }
-
- _pad_with_silence->Enable (_generally_sensitive && _film && _film->audio_stream() && _film->audio_stream()->channels() < MAX_AUDIO_CHANNELS);
- _minimum_audio_channels->Enable (_generally_sensitive && _pad_with_silence->GetValue ());
-}
-
-void
FilmEditor::use_dci_name_toggled (wxCommandEvent &)
{
if (!_film) {
@@ -1241,143 +1103,202 @@ FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
}
void
-FilmEditor::setup_streams ()
+FilmEditor::active_jobs_changed (bool a)
{
- _audio_stream->Clear ();
- vector<shared_ptr<AudioStream> > a = _film->content_audio_streams ();
- for (vector<shared_ptr<AudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
- shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (*i);
- assert (ffa);
- _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ())));
- }
-
- if (_film->use_content_audio() && _film->audio_stream()) {
- checked_set (_audio_stream, _film->audio_stream()->to_string());
- }
+ set_things_sensitive (!a);
+}
- _subtitle_stream->Clear ();
- vector<shared_ptr<SubtitleStream> > s = _film->subtitle_streams ();
- for (vector<shared_ptr<SubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
- _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ())));
- }
- if (_film->subtitle_stream()) {
- checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
+void
+FilmEditor::setup_dcp_name ()
+{
+ string s = _film->dcp_name (true);
+ if (s.length() > 28) {
+ _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
+ _dcp_name->SetToolTip (std_to_wx (s));
} else {
- _subtitle_stream->SetSelection (wxNOT_FOUND);
+ _dcp_name->SetLabel (std_to_wx (s));
}
}
void
-FilmEditor::audio_stream_changed (wxCommandEvent &)
+FilmEditor::show_audio_clicked (wxCommandEvent &)
{
- if (!_film) {
+ if (_audio_dialog) {
+ _audio_dialog->Destroy ();
+ _audio_dialog = 0;
+ }
+
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
return;
}
- _film->set_content_audio_stream (
- audio_stream_factory (
- string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
- Film::state_version
- )
- );
+ shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
+ if (!ac) {
+ return;
+ }
+
+ _audio_dialog = new AudioDialog (this);
+ _audio_dialog->Show ();
+ _audio_dialog->set_content (ac);
}
void
-FilmEditor::subtitle_stream_changed (wxCommandEvent &)
+FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
{
if (!_film) {
return;
}
+
+ _film->set_dcp_video_frame_rate (_film->best_dcp_video_frame_rate ());
+}
- _film->set_subtitle_stream (
- subtitle_stream_factory (
- string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
- Film::state_version
- )
- );
+void
+FilmEditor::setup_show_audio_sensitivity ()
+{
+ _show_audio->Enable (_film);
}
void
-FilmEditor::setup_audio_details ()
+FilmEditor::setup_content ()
{
- if (!_film->content_audio_stream()) {
- _audio->SetLabel (wxT (""));
- } else {
- wxString s;
- if (_film->audio_stream()->channels() == 1) {
- s << _("1 channel");
- } else {
- s << _film->audio_stream()->channels () << wxT (" ") << _("channels");
+ string selected_summary;
+ int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (s != -1) {
+ selected_summary = wx_to_std (_content->GetItemText (s));
+ }
+
+ _content->DeleteAllItems ();
+
+ Playlist::ContentList content = _film->content ();
+ for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+ int const t = _content->GetItemCount ();
+ _content->InsertItem (t, std_to_wx ((*i)->summary ()));
+ if ((*i)->summary() == selected_summary) {
+ _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
}
- s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz");
- _audio->SetLabel (s);
}
- setup_notebook_size ();
+ if (selected_summary.empty () && !content.empty ()) {
+ /* Select the item of content if none was selected before */
+ _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+ }
}
void
-FilmEditor::active_jobs_changed (bool a)
+FilmEditor::content_add_clicked (wxCommandEvent &)
{
- set_things_sensitive (!a);
+ wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
+ int const r = d->ShowModal ();
+ d->Destroy ();
+
+ if (r != wxID_OK) {
+ return;
+ }
+
+ wxArrayString paths;
+ d->GetPaths (paths);
+
+ for (unsigned int i = 0; i < paths.GetCount(); ++i) {
+ boost::filesystem::path p (wx_to_std (paths[i]));
+
+ shared_ptr<Content> c;
+
+ if (ImageMagickContent::valid_file (p)) {
+ c.reset (new ImageMagickContent (_film, p));
+ } else if (SndfileContent::valid_file (p)) {
+ c.reset (new SndfileContent (_film, p));
+ } else {
+ c.reset (new FFmpegContent (_film, p));
+ }
+
+ _film->examine_and_add_content (c);
+ }
}
void
-FilmEditor::use_audio_changed (wxCommandEvent &)
+FilmEditor::content_remove_clicked (wxCommandEvent &)
{
- _film->set_use_content_audio (_use_content_audio->GetValue());
+ shared_ptr<Content> c = selected_content ();
+ if (c) {
+ _film->remove_content (c);
+ }
}
void
-FilmEditor::external_audio_changed (wxCommandEvent &)
+FilmEditor::content_selection_changed (wxListEvent &)
{
- vector<string> a;
- for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- a.push_back (wx_to_std (_external_audio[i]->GetPath()));
+ setup_content_sensitivity ();
+ shared_ptr<Content> s = selected_content ();
+
+ if (_audio_dialog && s && dynamic_pointer_cast<AudioContent> (s)) {
+ _audio_dialog->set_content (dynamic_pointer_cast<AudioContent> (s));
}
-
- _film->set_external_audio (a);
+
+ film_content_changed (s, ContentProperty::START);
+ film_content_changed (s, ContentProperty::LENGTH);
+ film_content_changed (s, VideoContentProperty::VIDEO_CROP);
+ film_content_changed (s, VideoContentProperty::VIDEO_RATIO);
+ film_content_changed (s, AudioContentProperty::AUDIO_GAIN);
+ film_content_changed (s, AudioContentProperty::AUDIO_DELAY);
+ film_content_changed (s, AudioContentProperty::AUDIO_MAPPING);
+ film_content_changed (s, FFmpegContentProperty::AUDIO_STREAM);
+ film_content_changed (s, FFmpegContentProperty::AUDIO_STREAMS);
+ film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAM);
}
void
-FilmEditor::setup_dcp_name ()
+FilmEditor::setup_content_sensitivity ()
{
- string s = _film->dcp_name (true);
- if (s.length() > 28) {
- _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
- _dcp_name->SetToolTip (std_to_wx (s));
- } else {
- _dcp_name->SetLabel (std_to_wx (s));
- }
+ _content_add->Enable (_generally_sensitive);
+
+ shared_ptr<Content> selection = selected_content ();
+
+ _content_remove->Enable (selection && _generally_sensitive);
+ _content_timeline->Enable (_generally_sensitive);
+
+ _video_panel->Enable (selection && dynamic_pointer_cast<VideoContent> (selection) && _generally_sensitive);
+ _audio_panel->Enable (selection && dynamic_pointer_cast<AudioContent> (selection) && _generally_sensitive);
+ _subtitle_panel->Enable (selection && dynamic_pointer_cast<FFmpegContent> (selection) && _generally_sensitive);
+ _timing_panel->Enable (selection && _generally_sensitive);
}
-void
-FilmEditor::show_audio_clicked (wxCommandEvent &)
+shared_ptr<Content>
+FilmEditor::selected_content ()
{
- if (_audio_dialog) {
- _audio_dialog->Destroy ();
- _audio_dialog = 0;
+ int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (s == -1) {
+ return shared_ptr<Content> ();
+ }
+
+ Playlist::ContentList c = _film->content ();
+ if (s < 0 || size_t (s) >= c.size ()) {
+ return shared_ptr<Content> ();
}
- _audio_dialog = new AudioDialog (this);
- _audio_dialog->Show ();
- _audio_dialog->set_film (_film);
+ return c[s];
}
-void
-FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
+shared_ptr<VideoContent>
+FilmEditor::selected_video_content ()
{
- if (!_film) {
- return;
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return shared_ptr<VideoContent> ();
}
-
- _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ()));
+
+ return dynamic_pointer_cast<VideoContent> (c);
}
-void
-FilmEditor::setup_show_audio_sensitivity ()
+shared_ptr<AudioContent>
+FilmEditor::selected_audio_content ()
{
- _show_audio->Enable (_film && _film->has_audio ());
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return shared_ptr<AudioContent> ();
+ }
+
+ return dynamic_pointer_cast<AudioContent> (c);
}
void
@@ -1385,13 +1306,15 @@ FilmEditor::setup_scaling_description ()
{
wxString d;
+#if 0
+XXX
int lines = 0;
- if (_film->size().width && _film->size().height) {
+ if (_film->video_size().width && _film->video_size().height) {
d << wxString::Format (
_("Original video is %dx%d (%.2f:1)\n"),
- _film->size().width, _film->size().height,
- float (_film->size().width) / _film->size().height
+ _film->video_size().width, _film->video_size().height,
+ float (_film->video_size().width) / _film->video_size().height
);
++lines;
}
@@ -1433,18 +1356,194 @@ FilmEditor::setup_scaling_description ()
d << wxT ("\n ");
}
+#endif
_scaling_description->SetLabel (d);
}
void
-FilmEditor::trim_type_changed (wxCommandEvent &)
+FilmEditor::loop_content_toggled (wxCommandEvent &)
+{
+ if (_loop_content->GetValue ()) {
+ _film->set_loop (_loop_count->GetValue ());
+ } else {
+ _film->set_loop (1);
+ }
+
+ setup_loop_sensitivity ();
+}
+
+void
+FilmEditor::loop_count_changed (wxCommandEvent &)
{
- _film->set_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE);
+ _film->set_loop (_loop_count->GetValue ());
+}
+
+void
+FilmEditor::setup_loop_sensitivity ()
+{
+ _loop_count->Enable (_loop_content->GetValue ());
+}
+
+void
+FilmEditor::content_timeline_clicked (wxCommandEvent &)
+{
+ if (_timeline_dialog) {
+ _timeline_dialog->Destroy ();
+ _timeline_dialog = 0;
+ }
+
+ _timeline_dialog = new TimelineDialog (this, _film);
+ _timeline_dialog->Show ();
+}
+
+void
+FilmEditor::audio_stream_changed (wxCommandEvent &)
+{
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+ if (!fc) {
+ return;
+ }
+
+ vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams ();
+ vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
+ string const s = string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ()));
+ while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+ ++i;
+ }
+
+ if (i != a.end ()) {
+ fc->set_audio_stream (*i);
+ }
+
+ if (!fc->audio_stream ()) {
+ _audio_description->SetLabel (wxT (""));
+ } else {
+ wxString s;
+ if (fc->audio_channels() == 1) {
+ s << _("1 channel");
+ } else {
+ s << fc->audio_channels() << wxT (" ") << _("channels");
+ }
+ s << wxT (", ") << fc->content_audio_frame_rate() << _("Hz");
+ _audio_description->SetLabel (s);
+ }
+}
+
+
+
+void
+FilmEditor::subtitle_stream_changed (wxCommandEvent &)
+{
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
+ if (!fc) {
+ return;
+ }
+
+ vector<shared_ptr<FFmpegSubtitleStream> > a = fc->subtitle_streams ();
+ vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
+ string const s = string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ()));
+ while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+ ++i;
+ }
+
+ if (i != a.end ()) {
+ fc->set_subtitle_stream (*i);
+ }
+}
+
+void
+FilmEditor::audio_mapping_changed (AudioMapping m)
+{
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
+ if (!ac) {
+ return;
+ }
+
+ ac->set_audio_mapping (m);
+}
+
+void
+FilmEditor::start_changed ()
+{
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ c->set_start (_start->get (_film->dcp_video_frame_rate ()));
+}
+
+void
+FilmEditor::length_changed ()
+{
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ shared_ptr<ImageMagickContent> ic = dynamic_pointer_cast<ImageMagickContent> (c);
+ if (ic) {
+ ic->set_video_length (_length->get(_film->dcp_video_frame_rate()) * ic->video_frame_rate() / TIME_HZ);
+ }
+}
+
+void
+FilmEditor::set_selection (weak_ptr<Content> wc)
+{
+ Playlist::ContentList content = _film->content ();
+ for (size_t i = 0; i < content.size(); ++i) {
+ if (content[i] == wc.lock ()) {
+ _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+ } else {
+ _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
+ }
+ }
+}
+
+void
+FilmEditor::ratio_changed (wxCommandEvent &)
+{
+ if (!_film) {
+ return;
+ }
+
+ shared_ptr<Content> c = selected_content ();
+ if (!c) {
+ return;
+ }
+
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
+ if (!vc) {
+ return;
+ }
+
+ int const n = _ratio->GetSelection ();
+ if (n >= 0) {
+ vector<Ratio const *> ratios = Ratio::all ();
+ assert (n < int (ratios.size()));
+ vc->set_ratio (ratios[n]);
+ }
}
void
FilmEditor::setup_minimum_audio_channels ()
{
+#if 0
if (!_film || !_film->audio_stream ()) {
_pad_with_silence->SetValue (false);
return;
@@ -1454,12 +1553,13 @@ FilmEditor::setup_minimum_audio_channels ()
AudioMapping m (_film);
_minimum_audio_channels->SetRange (m.minimum_dcp_channels() + 1, MAX_AUDIO_CHANNELS);
+#endif
}
void
FilmEditor::pad_with_silence_toggled (wxCommandEvent &)
{
- setup_audio_control_sensitivity ();
+
}
void
@@ -1469,5 +1569,5 @@ FilmEditor::minimum_audio_channels_changed (wxCommandEvent &)
return;
}
- _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ());
+// _film->set_minimum_audio_channels (_minimum_audio_channels->GetValue ());
}
diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h
index c2d064ca2..705eb16af 100644
--- a/src/wx/film_editor.h
+++ b/src/wx/film_editor.h
@@ -16,7 +16,7 @@
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-
+
/** @file src/film_editor.h
* @brief A wx widget to edit a film's metadata, and perform various functions.
*/
@@ -29,8 +29,14 @@
#include "lib/film.h"
class wxNotebook;
+class wxListCtrl;
+class wxListEvent;
class Film;
class AudioDialog;
+class TimelineDialog;
+class AudioMappingView;
+class Ratio;
+class Timecode;
/** @class FilmEditor
* @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -41,15 +47,17 @@ public:
FilmEditor (boost::shared_ptr<Film>, wxWindow *);
void set_film (boost::shared_ptr<Film>);
- void setup_visibility ();
+ void set_selection (boost::weak_ptr<Content>);
boost::signals2::signal<void (std::string)> FileChanged;
private:
- void make_film_panel ();
+ void make_dcp_panel ();
+ void make_content_panel ();
void make_video_panel ();
void make_audio_panel ();
void make_subtitle_panel ();
+ void make_timing_panel ();
void connect_to_widgets ();
/* Handle changes to the view */
@@ -60,14 +68,13 @@ private:
void right_crop_changed (wxCommandEvent &);
void top_crop_changed (wxCommandEvent &);
void bottom_crop_changed (wxCommandEvent &);
- void content_changed (wxCommandEvent &);
- void trust_content_header_changed (wxCommandEvent &);
- void format_changed (wxCommandEvent &);
- void trim_start_changed (wxCommandEvent &);
- void trim_end_changed (wxCommandEvent &);
- void trim_type_changed (wxCommandEvent &);
+ void trust_content_headers_changed (wxCommandEvent &);
+ void content_selection_changed (wxListEvent &);
+ void content_add_clicked (wxCommandEvent &);
+ void content_remove_clicked (wxCommandEvent &);
+ void imagemagick_video_length_changed (wxCommandEvent &);
+ void container_changed (wxCommandEvent &);
void dcp_content_type_changed (wxCommandEvent &);
- void dcp_ab_toggled (wxCommandEvent &);
void scaler_changed (wxCommandEvent &);
void audio_gain_changed (wxCommandEvent &);
void audio_gain_calculate_button_clicked (wxCommandEvent &);
@@ -78,121 +85,103 @@ private:
void subtitle_scale_changed (wxCommandEvent &);
void colour_lut_changed (wxCommandEvent &);
void j2k_bandwidth_changed (wxCommandEvent &);
- void still_duration_changed (wxCommandEvent &);
- void audio_stream_changed (wxCommandEvent &);
- void subtitle_stream_changed (wxCommandEvent &);
- void use_audio_changed (wxCommandEvent &);
- void external_audio_changed (wxCommandEvent &);
void dcp_frame_rate_changed (wxCommandEvent &);
void best_dcp_frame_rate_clicked (wxCommandEvent &);
+ void edit_filters_clicked (wxCommandEvent &);
+ void loop_content_toggled (wxCommandEvent &);
+ void loop_count_changed (wxCommandEvent &);
+ void content_timeline_clicked (wxCommandEvent &);
+ void audio_stream_changed (wxCommandEvent &);
+ void subtitle_stream_changed (wxCommandEvent &);
+ void audio_mapping_changed (AudioMapping);
+ void start_changed ();
+ void length_changed ();
+ void ratio_changed (wxCommandEvent &);
void pad_with_silence_toggled (wxCommandEvent &);
void minimum_audio_channels_changed (wxCommandEvent &);
/* Handle changes to the model */
void film_changed (Film::Property);
-
- /* Button clicks */
- void edit_filters_clicked (wxCommandEvent &);
+ void film_content_changed (boost::weak_ptr<Content>, int);
void set_things_sensitive (bool);
- void setup_formats ();
+ void setup_ratios ();
void setup_subtitle_control_sensitivity ();
- void setup_audio_control_sensitivity ();
- void setup_streams ();
- void setup_audio_details ();
void setup_dcp_name ();
void setup_show_audio_sensitivity ();
void setup_scaling_description ();
- void setup_notebook_size ();
- void setup_frame_rate_description ();
+ void setup_main_notebook_size ();
+ void setup_content ();
+ void setup_container ();
+ void setup_content_sensitivity ();
+ void setup_loop_sensitivity ();
void setup_minimum_audio_channels ();
- wxControl* video_control (wxControl *);
- wxControl* still_control (wxControl *);
-
void active_jobs_changed (bool);
-
- wxNotebook* _notebook;
- wxPanel* _film_panel;
- wxSizer* _film_sizer;
+ boost::shared_ptr<Content> selected_content ();
+ boost::shared_ptr<VideoContent> selected_video_content ();
+ boost::shared_ptr<AudioContent> selected_audio_content ();
+
+ wxNotebook* _main_notebook;
+ wxNotebook* _content_notebook;
+ wxPanel* _dcp_panel;
+ wxSizer* _dcp_sizer;
+ wxPanel* _content_panel;
+ wxSizer* _content_sizer;
wxPanel* _video_panel;
- wxSizer* _video_sizer;
wxPanel* _audio_panel;
- wxSizer* _audio_sizer;
wxPanel* _subtitle_panel;
- wxSizer* _subtitle_sizer;
+ wxPanel* _timing_panel;
/** The film we are editing */
boost::shared_ptr<Film> _film;
- /** The Film's name */
wxTextCtrl* _name;
wxStaticText* _dcp_name;
wxCheckBox* _use_dci_name;
+ wxChoice* _container;
+ wxListCtrl* _content;
+ wxButton* _content_add;
+ wxButton* _content_remove;
+ wxButton* _content_earlier;
+ wxButton* _content_later;
+ wxButton* _content_timeline;
+ wxCheckBox* _loop_content;
+ wxSpinCtrl* _loop_count;
wxButton* _edit_dci_button;
- /** The Film's format */
- wxChoice* _format;
+ wxChoice* _ratio;
+ wxStaticText* _ratio_description;
wxStaticText* _scaling_description;
- /** The Film's content file */
- wxFilePickerCtrl* _content;
- wxCheckBox* _trust_content_header;
- /** The Film's left crop */
wxSpinCtrl* _left_crop;
- /** The Film's right crop */
wxSpinCtrl* _right_crop;
- /** The Film's top crop */
wxSpinCtrl* _top_crop;
- /** The Film's bottom crop */
wxSpinCtrl* _bottom_crop;
- /** Currently-applied filters */
wxStaticText* _filters;
- /** Button to open the filters dialogue */
wxButton* _filters_button;
- /** The Film's scaler */
wxChoice* _scaler;
- wxRadioButton* _use_content_audio;
- wxChoice* _audio_stream;
- wxRadioButton* _use_external_audio;
- wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
- /** The Film's audio gain */
wxSpinCtrl* _audio_gain;
- /** A button to open the gain calculation dialogue */
wxButton* _audio_gain_calculate_button;
wxButton* _show_audio;
- /** The Film's audio delay */
wxSpinCtrl* _audio_delay;
wxCheckBox* _with_subtitles;
- wxChoice* _subtitle_stream;
wxSpinCtrl* _subtitle_offset;
wxSpinCtrl* _subtitle_scale;
wxChoice* _colour_lut;
wxSpinCtrl* _j2k_bandwidth;
- /** The Film's DCP content type */
wxChoice* _dcp_content_type;
- /** The Film's source frame rate */
- wxStaticText* _source_frame_rate;
wxChoice* _dcp_frame_rate;
wxButton* _best_dcp_frame_rate;
wxCheckBox* _pad_with_silence;
wxSpinCtrl* _minimum_audio_channels;
- wxStaticText* _frame_rate_description;
- /** The Film's length */
- wxStaticText* _length;
- /** The Film's audio details */
- wxStaticText* _audio;
- /** The Film's duration for still sources */
- wxSpinCtrl* _still_duration;
-
- wxSpinCtrl* _trim_start;
- wxSpinCtrl* _trim_end;
- wxChoice* _trim_type;
- /** Selector to generate an A/B comparison DCP */
- wxCheckBox* _dcp_ab;
-
- std::list<wxControl*> _video_controls;
- std::list<wxControl*> _still_controls;
+ wxChoice* _audio_stream;
+ wxStaticText* _audio_description;
+ wxChoice* _subtitle_stream;
+ AudioMappingView* _audio_mapping;
+ Timecode* _start;
+ Timecode* _length;
- std::vector<Format const *> _formats;
+ std::vector<Ratio const *> _ratios;
bool _generally_sensitive;
AudioDialog* _audio_dialog;
+ TimelineDialog* _timeline_dialog;
};
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index 6845031cf..8ef64d509 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -25,26 +25,31 @@
#include <iomanip>
#include <wx/tglbtn.h>
#include "lib/film.h"
-#include "lib/format.h"
+#include "lib/ratio.h"
#include "lib/util.h"
#include "lib/job_manager.h"
-#include "lib/options.h"
-#include "lib/subtitle.h"
#include "lib/image.h"
#include "lib/scaler.h"
#include "lib/exceptions.h"
#include "lib/examine_content_job.h"
#include "lib/filter.h"
+#include "lib/player.h"
+#include "lib/video_content.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/imagemagick_content.h"
#include "film_viewer.h"
#include "wx_util.h"
#include "video_decoder.h"
using std::string;
using std::pair;
+using std::min;
using std::max;
using std::cout;
using std::list;
using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::weak_ptr;
using libdcp::Size;
FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
@@ -56,7 +61,6 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
, _frame (new wxStaticText (this, wxID_ANY, wxT("")))
, _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
, _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
- , _display_frame_x (0)
, _got_frame (false)
{
#ifndef __WXOSX__
@@ -111,49 +115,27 @@ void
FilmViewer::film_changed (Film::Property p)
{
switch (p) {
- case Film::FORMAT:
+ case Film::CONTAINER:
calculate_sizes ();
update_from_raw ();
break;
case Film::CONTENT:
{
- DecodeOptions o;
- o.decode_audio = false;
- o.decode_subtitles = true;
- o.video_sync = false;
-
- try {
- _decoders = decoder_factory (_film, o);
- } catch (StringError& e) {
- error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data()));
- return;
- }
-
- if (_decoders.video == 0) {
- break;
- }
- _decoders.video->set_subtitle_stream (_film->subtitle_stream());
- _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3, _4));
- _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
calculate_sizes ();
- get_frame ();
- _panel->Refresh ();
- _slider->Show (_film->content_type() == VIDEO);
- _play_button->Show (_film->content_type() == VIDEO);
- _v_sizer->Layout ();
+ wxScrollEvent ev;
+ slider_moved (ev);
break;
}
case Film::WITH_SUBTITLES:
case Film::SUBTITLE_OFFSET:
case Film::SUBTITLE_SCALE:
- case Film::SCALER:
- case Film::FILTERS:
- update_from_raw ();
+ update_from_decoder ();
+ raw_to_display ();
+ _panel->Refresh ();
+ _panel->Update ();
break;
- case Film::SUBTITLE_STREAM:
- if (_decoders.video) {
- _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
- }
+ case Film::SCALER:
+ update_from_decoder ();
break;
default:
break;
@@ -166,7 +148,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
if (_film == f) {
return;
}
-
+
_film = f;
_raw_frame.reset ();
@@ -178,23 +160,28 @@ FilmViewer::set_film (shared_ptr<Film> f)
return;
}
+ _player = f->player ();
+ _player->disable_audio ();
+ _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
+
_film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
+ _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
film_changed (Film::CONTENT);
- film_changed (Film::FORMAT);
+ film_changed (Film::CONTAINER);
film_changed (Film::WITH_SUBTITLES);
film_changed (Film::SUBTITLE_OFFSET);
film_changed (Film::SUBTITLE_SCALE);
- film_changed (Film::SUBTITLE_STREAM);
}
void
-FilmViewer::decoder_changed ()
+FilmViewer::update_from_decoder ()
{
- if (_decoders.video == 0 || _decoders.video->seek_to_last ()) {
+ if (!_player) {
return;
}
+ _player->seek (_player->video_position() - _film->video_frames_to_time (1));
get_frame ();
_panel->Refresh ();
_panel->Update ();
@@ -203,14 +190,14 @@ FilmViewer::decoder_changed ()
void
FilmViewer::timer (wxTimerEvent &)
{
- if (!_film || !_decoders.video) {
+ if (!_player) {
return;
}
get_frame ();
if (_film->length()) {
- int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate());
+ int const new_slider_position = 4096 * _player->video_position() / _film->length();
if (new_slider_position != _slider->GetValue()) {
_slider->SetValue (new_slider_position);
}
@@ -231,22 +218,9 @@ FilmViewer::paint_panel (wxPaintEvent &)
return;
}
- if (_display_frame_x) {
- dc.SetPen(*wxBLACK_PEN);
- dc.SetBrush(*wxBLACK_BRUSH);
- dc.DrawRectangle (0, 0, _display_frame_x, _film_size.height);
- dc.DrawRectangle (_display_frame_x + _film_size.width, 0, _display_frame_x, _film_size.height);
- }
-
- wxImage frame (_film_size.width, _film_size.height, _display_frame->data()[0], true);
+ wxImage frame (_out_size.width, _out_size.height, _display_frame->data()[0], true);
wxBitmap frame_bitmap (frame);
- dc.DrawBitmap (frame_bitmap, _display_frame_x, 0);
-
- if (_film->with_subtitles() && _display_sub) {
- wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true);
- wxBitmap sub_bitmap (sub);
- dc.DrawBitmap (sub_bitmap, _display_sub_position.x, _display_sub_position.y);
- }
+ dc.DrawBitmap (frame_bitmap, 0, 0);
if (_out_size.width < _panel_size.width) {
wxPen p (GetBackgroundColour ());
@@ -269,12 +243,10 @@ FilmViewer::paint_panel (wxPaintEvent &)
void
FilmViewer::slider_moved (wxScrollEvent &)
{
- if (!_film || !_film->length() || !_decoders.video) {
- return;
- }
+ cout << "slider " << _slider->GetValue() << " " << _film->length() << "\n";
- if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) {
- return;
+ if (_film && _player) {
+ _player->seek (_slider->GetValue() * _film->length() / 4096);
}
get_frame ();
@@ -311,49 +283,21 @@ FilmViewer::raw_to_display ()
return;
}
- boost::shared_ptr<const Image> input = _raw_frame;
-
- pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
- if (!s.second.empty ()) {
- input = input->post_process (s.second, true);
- }
-
/* Get a compacted image as we have to feed it to wxWidgets */
- _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
-
- if (_raw_sub) {
-
- /* Our output is already cropped by the decoder, so we need to account for that
- when working out the scale that we are applying.
- */
-
- Size const cropped_size = _film->cropped_size (_film->size ());
-
- dvdomatic::Rect tx = subtitle_transformed_area (
- float (_film_size.width) / cropped_size.width,
- float (_film_size.height) / cropped_size.height,
- _raw_sub->area(), _film->subtitle_offset(), _film->subtitle_scale()
- );
-
- _display_sub.reset (new RGBPlusAlphaImage (_raw_sub->image()->scale (tx.size(), _film->scaler(), false)));
- _display_sub_position = tx.position();
- _display_sub_position.x += _display_frame_x;
- } else {
- _display_sub.reset ();
- }
+ _display_frame.reset (new SimpleImage (_raw_frame, false));
}
void
FilmViewer::calculate_sizes ()
{
- if (!_film) {
+ if (!_film || !_player) {
return;
}
- Format const * format = _film->format ();
+ Ratio const * container = _film->container ();
float const panel_ratio = static_cast<float> (_panel_size.width) / _panel_size.height;
- float const film_ratio = format ? format->container_ratio () : 1.78;
+ float const film_ratio = container ? container->ratio () : 1.78;
if (panel_ratio < film_ratio) {
/* panel is less widscreen than the film; clamp width */
@@ -365,21 +309,12 @@ FilmViewer::calculate_sizes ()
_out_size.width = _out_size.height * film_ratio;
}
- /* Work out how much padding there is in terms of our display; this will be the x position
- of our _display_frame.
- */
- _display_frame_x = 0;
- if (format) {
- _display_frame_x = static_cast<float> (format->dcp_padding (_film)) * _out_size.width / format->dcp_size().width;
- }
-
- _film_size = _out_size;
- _film_size.width -= _display_frame_x * 2;
-
/* Catch silly values */
- if (_out_size.width < 64) {
- _out_size.width = 64;
- }
+ _out_size.width = max (64, _out_size.width);
+ _out_size.height = max (64, _out_size.height);
+
+ _player->set_video_container_size (_out_size);
+ update_from_decoder ();
}
void
@@ -391,32 +326,31 @@ FilmViewer::play_clicked (wxCommandEvent &)
void
FilmViewer::check_play_state ()
{
- if (!_film) {
+ if (!_film || _film->dcp_video_frame_rate() == 0) {
return;
}
if (_play_button->GetValue()) {
- _timer.Start (1000 / _film->source_frame_rate());
+ _timer.Start (1000 / _film->dcp_video_frame_rate());
} else {
_timer.Stop ();
}
}
void
-FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, double t)
+FilmViewer::process_video (shared_ptr<const Image> image, bool, Time t)
{
_raw_frame = image;
- _raw_sub = sub;
raw_to_display ();
_got_frame = true;
- double const fps = _decoders.video->frames_per_second ();
+ double const fps = _film->dcp_video_frame_rate ();
/* Count frame number from 1 ... not sure if this is the best idea */
- _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps)) + 1));
+ _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
- double w = t;
+ double w = static_cast<double>(t) / TIME_HZ;
int const h = (w / 3600);
w -= h * 3600;
int const m = (w / 60);
@@ -427,13 +361,16 @@ FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subti
_timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d:%02d"), h, m, s, f));
}
+/** Get a new _raw_frame from the decoder and then do
+ * raw_to_display ().
+ */
void
FilmViewer::get_frame ()
{
/* Clear our raw frame in case we don't get a new one */
_raw_frame.reset ();
- if (_decoders.video == 0) {
+ if (!_player) {
_display_frame.reset ();
return;
}
@@ -441,7 +378,7 @@ FilmViewer::get_frame ()
try {
_got_frame = false;
while (!_got_frame) {
- if (_decoders.video->pass ()) {
+ if (_player->pass ()) {
/* We didn't get a frame before the decoder gave up,
so clear our display frame.
*/
@@ -477,13 +414,25 @@ FilmViewer::active_jobs_changed (bool a)
}
void
+FilmViewer::film_content_changed (weak_ptr<Content>, int p)
+{
+ if (p == ContentProperty::LENGTH) {
+ /* Force an update to our frame */
+ wxScrollEvent ev;
+ slider_moved (ev);
+ } else if (p == VideoContentProperty::VIDEO_CROP || p == VideoContentProperty::VIDEO_RATIO) {
+ update_from_decoder ();
+ }
+}
+
+void
FilmViewer::back_clicked (wxCommandEvent &)
{
- if (!_decoders.video) {
+ if (!_player) {
return;
}
- _decoders.video->seek_back ();
+ _player->seek_back ();
get_frame ();
_panel->Refresh ();
_panel->Update ();
@@ -492,11 +441,10 @@ FilmViewer::back_clicked (wxCommandEvent &)
void
FilmViewer::forward_clicked (wxCommandEvent &)
{
- if (!_decoders.video) {
+ if (!_player) {
return;
}
- _decoders.video->seek_forward ();
get_frame ();
_panel->Refresh ();
_panel->Update ();
diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h
index ed5874fbc..6c18c7c5b 100644
--- a/src/wx/film_viewer.h
+++ b/src/wx/film_viewer.h
@@ -23,16 +23,31 @@
#include <wx/wx.h>
#include "lib/film.h"
-#include "lib/decoder_factory.h"
class wxToggleButton;
class FFmpegPlayer;
class Image;
class RGBPlusAlphaImage;
-class Subtitle;
/** @class FilmViewer
* @brief A wx widget to view a preview of a Film.
+ *
+ * The film takes the following path through the viewer:
+ *
+ * 1. get_frame() asks our _player to decode some data. If it does, process_video()
+ * will be called.
+ *
+ * 2. process_video() takes the image from the decoder (_raw_frame) and calls raw_to_display().
+ *
+ * 3. raw_to_display() copies _raw_frame to _display_frame, processing it and scaling it.
+ *
+ * 4. calling _panel->Refresh() and _panel->Update() results in paint_panel() being called;
+ * this creates frame_bitmap from _display_frame and blits it to the display.
+ *
+ * update_from_decoder() asks the player to re-emit its current frame on the next pass(), and then
+ * starts from step #1.
+ *
+ * update_from_raw() starts at step #3, then calls _panel->Refresh and _panel->Update.
*/
class FilmViewer : public wxPanel
{
@@ -43,16 +58,17 @@ public:
private:
void film_changed (Film::Property);
+ void film_content_changed (boost::weak_ptr<Content>, int);
void paint_panel (wxPaintEvent &);
void panel_sized (wxSizeEvent &);
void slider_moved (wxScrollEvent &);
void play_clicked (wxCommandEvent &);
void timer (wxTimerEvent &);
- void process_video (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double);
+ void process_video (boost::shared_ptr<const Image>, bool, Time);
void calculate_sizes ();
void check_play_state ();
void update_from_raw ();
- void decoder_changed ();
+ void update_from_decoder ();
void raw_to_display ();
void get_frame ();
void active_jobs_changed (bool);
@@ -60,6 +76,7 @@ private:
void forward_clicked (wxCommandEvent &);
boost::shared_ptr<Film> _film;
+ boost::shared_ptr<Player> _player;
wxSizer* _v_sizer;
wxPanel* _panel;
@@ -71,22 +88,12 @@ private:
wxToggleButton* _play_button;
wxTimer _timer;
- Decoders _decoders;
boost::shared_ptr<const Image> _raw_frame;
- boost::shared_ptr<Subtitle> _raw_sub;
boost::shared_ptr<const Image> _display_frame;
- /* The x offset at which we display the actual film content; this corresponds
- to the film's padding converted to our coordinates.
- */
- int _display_frame_x;
- boost::shared_ptr<RGBPlusAlphaImage> _display_sub;
- Position _display_sub_position;
bool _got_frame;
/** Size of our output (including padding if we have any) */
libdcp::Size _out_size;
- /** Size that we will make our film (equal to _out_size unless we have padding) */
- libdcp::Size _film_size;
/** Size of the panel that we have available */
libdcp::Size _panel_size;
};
diff --git a/src/wx/gain_calculator_dialog.cc b/src/wx/gain_calculator_dialog.cc
index 7499cbf8b..17ebbb983 100644
--- a/src/wx/gain_calculator_dialog.cc
+++ b/src/wx/gain_calculator_dialog.cc
@@ -26,7 +26,7 @@ using namespace boost;
GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent)
: wxDialog (parent, wxID_ANY, _("Gain Calculator"))
{
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
add_label_to_sizer (table, this, _("I want to play this back at fader"), true);
diff --git a/src/wx/imagemagick_content_dialog.cc b/src/wx/imagemagick_content_dialog.cc
new file mode 100644
index 000000000..6aa756260
--- /dev/null
+++ b/src/wx/imagemagick_content_dialog.cc
@@ -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.
+
+*/
+
+#include <wx/spinctrl.h>
+#include "lib/imagemagick_content.h"
+#include "imagemagick_content_dialog.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+ImageMagickContentDialog::ImageMagickContentDialog (wxWindow* parent, shared_ptr<ImageMagickContent> content)
+ : wxDialog (parent, wxID_ANY, _("Image"))
+ , _content (content)
+{
+ wxFlexGridSizer* grid = new wxFlexGridSizer (3, 6, 6);
+ grid->AddGrowableCol (1, 1);
+
+ {
+ add_label_to_sizer (grid, this, _("Duration"), true);
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _video_length = new wxSpinCtrl (this);
+ s->Add (_video_length);
+ /// TRANSLATORS: this is an abbreviation for seconds, the unit of time
+ add_label_to_sizer (s, this, _("s"), false);
+ grid->Add (s);
+ }
+
+ wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+ overall_sizer->Add (grid, 1, wxEXPAND | wxALL, 6);
+
+ wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+ if (buttons) {
+ overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ SetSizer (overall_sizer);
+ overall_sizer->Layout ();
+ overall_sizer->SetSizeHints (this);
+
+ checked_set (_video_length, content->video_length () / 24);
+ _video_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ImageMagickContentDialog::video_length_changed), 0, this);
+}
+
+void
+ImageMagickContentDialog::video_length_changed (wxCommandEvent &)
+{
+ shared_ptr<ImageMagickContent> c = _content.lock ();
+ if (!c) {
+ return;
+ }
+
+ c->set_video_length (_video_length->GetValue() * 24);
+}
diff --git a/src/lib/audio_sink.h b/src/wx/imagemagick_content_dialog.h
index 69b3a4b75..23722f183 100644
--- a/src/lib/audio_sink.h
+++ b/src/wx/imagemagick_content_dialog.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,21 +17,20 @@
*/
-#ifndef DVDOMATIC_AUDIO_SINK_H
-#define DVDOMATIC_AUDIO_SINK_H
+#include <wx/wx.h>
-class AudioSink
-{
-public:
- /** Call with some audio data */
- virtual void process_audio (boost::shared_ptr<const AudioBuffers>) = 0;
-};
+class wxSpinCtrl;
+class ImageMagickContent;
+class Region;
-class TimedAudioSink
+class ImageMagickContentDialog : public wxDialog
{
public:
- /** Call with some audio data */
- virtual void process_audio (boost::shared_ptr<const AudioBuffers>, double t) = 0;
-};
+ ImageMagickContentDialog (wxWindow *, boost::shared_ptr<ImageMagickContent>);
+
+private:
+ void video_length_changed (wxCommandEvent &);
-#endif
+ boost::weak_ptr<ImageMagickContent> _content;
+ wxSpinCtrl* _video_length;
+};
diff --git a/src/wx/job_manager_view.cc b/src/wx/job_manager_view.cc
index cfe09aec8..b5c66bd3e 100644
--- a/src/wx/job_manager_view.cc
+++ b/src/wx/job_manager_view.cc
@@ -86,6 +86,7 @@ JobManagerView::update ()
JobRecord r;
int n = 1;
r.finalised = false;
+ r.scroll_nudged = false;
r.gauge = new wxGauge (_panel, wxID_ANY, 100);
_table->Insert (index + n, r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
++n;
@@ -113,6 +114,7 @@ JobManagerView::update ()
++n;
_job_records[*i] = r;
+
}
string const st = (*i)->status ();
@@ -126,8 +128,25 @@ JobManagerView::update ()
checked_set (_job_records[*i].message, wx_to_std (_("Running")));
_job_records[*i].gauge->Pulse ();
}
+
}
+ if (!_job_records[*i].scroll_nudged && ((*i)->running () || (*i)->finished())) {
+ int x, y;
+ _job_records[*i].gauge->GetPosition (&x, &y);
+ int px, py;
+ GetScrollPixelsPerUnit (&px, &py);
+ int vx, vy;
+ GetViewStart (&vx, &vy);
+ int sx, sy;
+ GetClientSize (&sx, &sy);
+
+ if (y > (vy * py + sy / 2)) {
+ Scroll (-1, y / py);
+ _job_records[*i].scroll_nudged = true;
+ }
+ }
+
if ((*i)->finished() && !_job_records[*i].finalised) {
checked_set (_job_records[*i].message, st);
if (!(*i)->finished_cancelled()) {
@@ -155,7 +174,7 @@ JobManagerView::details_clicked (wxCommandEvent& ev)
{
wxObject* o = ev.GetEventObject ();
- for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+ for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
if (i->second.details == o) {
string s = i->first->error_summary();
s[0] = toupper (s[0]);
@@ -169,7 +188,7 @@ JobManagerView::cancel_clicked (wxCommandEvent& ev)
{
wxObject* o = ev.GetEventObject ();
- for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+ for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
if (i->second.cancel == o) {
i->first->cancel ();
}
diff --git a/src/wx/job_manager_view.h b/src/wx/job_manager_view.h
index fc29eadb4..3d1ad30c0 100644
--- a/src/wx/job_manager_view.h
+++ b/src/wx/job_manager_view.h
@@ -57,6 +57,7 @@ private:
wxButton* pause;
wxButton* details;
bool finalised;
+ bool scroll_nudged;
};
std::map<boost::shared_ptr<Job>, JobRecord> _job_records;
diff --git a/src/wx/new_film_dialog.cc b/src/wx/new_film_dialog.cc
index 289926b8e..d4b78d5bf 100644
--- a/src/wx/new_film_dialog.cc
+++ b/src/wx/new_film_dialog.cc
@@ -22,7 +22,7 @@
#include "lib/config.h"
#include "new_film_dialog.h"
#include "wx_util.h"
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
#include "dir_picker_ctrl.h"
#endif
@@ -37,7 +37,7 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent)
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
SetSizer (overall_sizer);
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
@@ -47,7 +47,7 @@ NewFilmDialog::NewFilmDialog (wxWindow* parent)
add_label_to_sizer (table, this, _("Create in folder"), true);
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
_folder = new DirPickerCtrl (this);
#else
_folder = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST);
diff --git a/src/wx/new_film_dialog.h b/src/wx/new_film_dialog.h
index 220bba732..f8f3aa08d 100644
--- a/src/wx/new_film_dialog.h
+++ b/src/wx/new_film_dialog.h
@@ -33,7 +33,7 @@ public:
private:
wxTextCtrl* _name;
-#ifdef DVDOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
DirPickerCtrl* _folder;
#else
wxDirPickerCtrl* _folder;
diff --git a/src/wx/po/es_ES.po b/src/wx/po/es_ES.po
index 8bc806755..34646c2b7 100644
--- a/src/wx/po/es_ES.po
+++ b/src/wx/po/es_ES.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: libdvdomatic-wx\n"
+"Project-Id-Version: libdcpomatic-wx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-04-02 19:08-0500\n"
@@ -21,10 +21,6 @@ msgstr ""
msgid "%"
msgstr "%"
-#: src/wx/config_dialog.cc:98
-msgid "(restart DVD-o-matic to see language changes)"
-msgstr ""
-
#: src/wx/film_editor.cc:1276
msgid "1 channel"
msgstr "1 canal"
@@ -149,9 +145,13 @@ msgstr "Velocidad DCP"
msgid "DCP Name"
msgstr "Nombre DCP"
-#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71
-msgid "DVD-o-matic"
-msgstr "DVD-o-matic"
+#: src/wx/wx_util.cc:61
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:44
+msgid "DCP-o-matic Preferences"
+msgstr "Preferencias DCP-o-matic"
#: src/wx/config_dialog.cc:46
#, fuzzy
@@ -160,8 +160,8 @@ msgstr "Preferencias DVD-o-matic"
#: src/wx/audio_dialog.cc:101
#, fuzzy, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "Audio DVD-o-matic - %1"
+msgid "DCP-o-matic audio - %s"
+msgstr "Audio DCP-o-matic - %1"
#: src/wx/config_dialog.cc:120
msgid "Default DCI name details"
diff --git a/src/wx/po/fr_FR.po b/src/wx/po/fr_FR.po
index 5c0a7b63c..3f150bb0e 100644
--- a/src/wx/po/fr_FR.po
+++ b/src/wx/po/fr_FR.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVD-o-matic FRENCH\n"
+"Project-Id-Version: DCP-o-matic FRENCH\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-05-10 14:19+0100\n"
@@ -148,9 +148,13 @@ msgstr "Cadence image du DCP"
msgid "DCP Name"
msgstr "Nom du DCP"
-#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71
-msgid "DVD-o-matic"
-msgstr "DVD-o-matic"
+#: src/wx/wx_util.cc:61
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:44
+msgid "DCP-o-matic Preferences"
+msgstr "Préférences DCP-o-matic"
#: src/wx/config_dialog.cc:46
#, fuzzy
@@ -159,8 +163,8 @@ msgstr "Préférences de DCP-o-matic"
#: src/wx/audio_dialog.cc:101
#, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "Son DVD-o-matic - %s"
+msgid "DCP-o-matic audio - %s"
+msgstr "Son DCP-o-matic - %s"
#: src/wx/config_dialog.cc:120
msgid "Default DCI name details"
diff --git a/src/wx/po/it_IT.po b/src/wx/po/it_IT.po
index 518bd7dde..92161172a 100644
--- a/src/wx/po/it_IT.po
+++ b/src/wx/po/it_IT.po
@@ -21,10 +21,9 @@ msgstr ""
msgid "%"
msgstr "%"
-#: src/wx/config_dialog.cc:98
-#, fuzzy
-msgid "(restart DVD-o-matic to see language changes)"
-msgstr "(riavviare DVD-o-matic per vedere i cambiamenti di lingua)"
+#: src/wx/config_dialog.cc:61
+msgid "(restart DCP-o-matic to see language changes)"
+msgstr "(riavviare DCP-o-matic per vedere i cambiamenti di lingua)"
#: src/wx/film_editor.cc:1276
msgid "1 channel"
@@ -150,9 +149,13 @@ msgstr "Frequenza fotogrammi del DCP"
msgid "DCP Name"
msgstr "Nome del DCP"
-#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71
-msgid "DVD-o-matic"
-msgstr "DVD-o-matic"
+#: src/wx/wx_util.cc:61
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:44
+msgid "DCP-o-matic Preferences"
+msgstr "Preferenze DCP-o-matic"
#: src/wx/config_dialog.cc:46
#, fuzzy
@@ -161,8 +164,8 @@ msgstr "Preferenze DVD-o-matic"
#: src/wx/audio_dialog.cc:101
#, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "Audio DVD-o-matic - %s"
+msgid "DCP-o-matic audio - %s"
+msgstr "Audio DCP-o-matic - %s"
#: src/wx/config_dialog.cc:120
msgid "Default DCI name details"
diff --git a/src/wx/po/sv_SE.po b/src/wx/po/sv_SE.po
index 6eda1cf4b..02df467ca 100644
--- a/src/wx/po/sv_SE.po
+++ b/src/wx/po/sv_SE.po
@@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: DVD-o-matic\n"
+"Project-Id-Version: DCP-o-matic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 23:59+0100\n"
"PO-Revision-Date: 2013-04-09 10:13+0100\n"
@@ -21,10 +21,9 @@ msgstr ""
msgid "%"
msgstr "%"
-#: src/wx/config_dialog.cc:98
-#, fuzzy
-msgid "(restart DVD-o-matic to see language changes)"
-msgstr "(starta om DVD-o-matic för att se språkändringar)"
+#: src/wx/config_dialog.cc:61
+msgid "(restart DCP-o-matic to see language changes)"
+msgstr "(starta om DCP-o-matic för att se språkändringar)"
#: src/wx/film_editor.cc:1276
msgid "1 channel"
@@ -150,9 +149,13 @@ msgstr "DCP bildhastighet"
msgid "DCP Name"
msgstr "DCP Namn"
-#: src/wx/wx_util.cc:63 src/wx/wx_util.cc:71
-msgid "DVD-o-matic"
-msgstr "DVD-o-matic"
+#: src/wx/wx_util.cc:61
+msgid "DCP-o-matic"
+msgstr "DCP-o-matic"
+
+#: src/wx/config_dialog.cc:44
+msgid "DCP-o-matic Preferences"
+msgstr "DCP-o-matic Inställningar"
#: src/wx/config_dialog.cc:46
#, fuzzy
@@ -161,8 +164,8 @@ msgstr "DVD-o-matic Inställningar"
#: src/wx/audio_dialog.cc:101
#, c-format
-msgid "DVD-o-matic audio - %s"
-msgstr "DVD-o-matic audio - %s"
+msgid "DCP-o-matic audio - %s"
+msgstr "DCP-o-matic audio - %s"
#: src/wx/config_dialog.cc:120
msgid "Default DCI name details"
diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc
index aa97623cd..d525fe38b 100644
--- a/src/wx/properties_dialog.cc
+++ b/src/wx/properties_dialog.cc
@@ -36,7 +36,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
: wxDialog (parent, wxID_ANY, _("Film Properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
, _film (film)
{
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
add_label_to_sizer (table, this, _("Frames"), true);
_frames = new wxStaticText (this, wxID_ANY, wxT (""));
@@ -50,18 +50,11 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
_encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
- if (_film->length()) {
- _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get())));
- FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
- int const dcp_length = _film->length().get() * frc.factor();
- double const disk = ((double) _film->j2k_bandwidth() / 8) * dcp_length / (_film->dcp_frame_rate() * 1073741824.0f);
- stringstream s;
- s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
- _disk->SetLabel (std_to_wx (s.str ()));
- } else {
- _frames->SetLabel (_("unknown"));
- _disk->SetLabel (_("unknown"));
- }
+ _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
+ double const disk = ((double) _film->j2k_bandwidth() / 8) * _film->length() / (TIME_HZ * 1073741824.0f);
+ stringstream s;
+ s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
+ _disk->SetLabel (std_to_wx (s.str ()));
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
overall_sizer->Add (table, 0, wxALL, 6);
@@ -87,7 +80,7 @@ PropertiesDialog::frames_already_encoded () const
if (_film->length()) {
/* XXX: encoded_frames() should check which frames have been encoded */
- u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
+ u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
}
return u.str ();
}
diff --git a/src/wx/server_dialog.cc b/src/wx/server_dialog.cc
index 30e3c0f83..33cb392bf 100644
--- a/src/wx/server_dialog.cc
+++ b/src/wx/server_dialog.cc
@@ -30,7 +30,7 @@ ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server)
_server = new ServerDescription (wx_to_std (N_("localhost")), 1);
}
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DVDOMATIC_SIZER_X_GAP, DVDOMATIC_SIZER_Y_GAP);
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
table->AddGrowableCol (1, 1);
add_label_to_sizer (table, this, _("Host name or IP address"), true);
diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc
new file mode 100644
index 000000000..6ce1c1cb8
--- /dev/null
+++ b/src/wx/timecode.cc
@@ -0,0 +1,115 @@
+/*
+ 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 "timecode.h"
+#include "wx_util.h"
+
+using std::string;
+using std::cout;
+using boost::lexical_cast;
+
+Timecode::Timecode (wxWindow* parent)
+ : wxPanel (parent)
+ , _in_set (false)
+{
+ wxClientDC dc (parent);
+ wxSize size = dc.GetTextExtent (wxT ("9999"));
+ size.SetHeight (-1);
+
+ wxTextValidator validator (wxFILTER_INCLUDE_CHAR_LIST);
+ wxArrayString list;
+
+ wxString n (wxT ("0123456789"));
+ for (size_t i = 0; i < n.Length(); ++i) {
+ list.Add (n[i]);
+ }
+
+ validator.SetIncludes (list);
+
+ wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+ _hours = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+ _hours->SetMaxLength (2);
+ sizer->Add (_hours);
+ add_label_to_sizer (sizer, this, wxT (":"), false);
+ _minutes = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+ _minutes->SetMaxLength (2);
+ sizer->Add (_minutes);
+ add_label_to_sizer (sizer, this, wxT (":"), false);
+ _seconds = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+ _seconds->SetMaxLength (2);
+ sizer->Add (_seconds);
+ add_label_to_sizer (sizer, this, wxT ("."), false);
+ _frames = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+ _frames->SetMaxLength (2);
+ sizer->Add (_frames);
+
+ _hours->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+ _minutes->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+ _seconds->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+ _frames->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (Timecode::changed), 0, this);
+
+ SetSizerAndFit (sizer);
+}
+
+void
+Timecode::set (Time t, int fps)
+{
+ _in_set = true;
+
+ int const h = t / (3600 * TIME_HZ);
+ t -= h * 3600 * TIME_HZ;
+ int const m = t / (60 * TIME_HZ);
+ t -= m * 60 * TIME_HZ;
+ int const s = t / TIME_HZ;
+ t -= s * TIME_HZ;
+ int const f = t * fps / TIME_HZ;
+
+ _hours->SetValue (wxString::Format (wxT ("%d"), h));
+ _minutes->SetValue (wxString::Format (wxT ("%d"), m));
+ _seconds->SetValue (wxString::Format (wxT ("%d"), s));
+ _frames->SetValue (wxString::Format (wxT ("%d"), f));
+
+ _in_set = false;
+}
+
+Time
+Timecode::get (int fps) const
+{
+ Time t = 0;
+ string const h = wx_to_std (_hours->GetValue ());
+ t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
+ string const m = wx_to_std (_minutes->GetValue());
+ t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
+ string const s = wx_to_std (_seconds->GetValue());
+ t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
+ string const f = wx_to_std (_frames->GetValue());
+ t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
+ return t;
+}
+
+void
+Timecode::changed (wxCommandEvent &)
+{
+ if (_in_set) {
+ return;
+ }
+
+ Changed ();
+}
diff --git a/src/lib/delay_line.h b/src/wx/timecode.h
index 781dce88a..9b6fe6654 100644
--- a/src/lib/delay_line.h
+++ b/src/wx/timecode.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,18 +17,27 @@
*/
-#include <boost/shared_ptr.hpp>
-#include "processor.h"
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include "lib/types.h"
-/** A delay line */
-class DelayLine : public TimedAudioVideoProcessor
+class Timecode : public wxPanel
{
public:
- DelayLine (boost::shared_ptr<Log> log, double);
-
- void process_video (boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, double);
- void process_audio (boost::shared_ptr<const AudioBuffers>, double);
+ Timecode (wxWindow *);
+
+ void set (Time, int);
+ Time get (int) const;
+
+ boost::signals2::signal<void ()> Changed;
private:
- double _seconds;
+ void changed (wxCommandEvent &);
+
+ wxTextCtrl* _hours;
+ wxTextCtrl* _minutes;
+ wxTextCtrl* _seconds;
+ wxTextCtrl* _frames;
+
+ bool _in_set;
};
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
new file mode 100644
index 000000000..113e883fc
--- /dev/null
+++ b/src/wx/timeline.cc
@@ -0,0 +1,561 @@
+/*
+ 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 <list>
+#include <wx/graphics.h>
+#include <boost/weak_ptr.hpp>
+#include "film.h"
+#include "film_editor.h"
+#include "timeline.h"
+#include "wx_util.h"
+#include "lib/playlist.h"
+
+using std::list;
+using std::cout;
+using std::max;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+using boost::bind;
+
+class View
+{
+public:
+ View (Timeline& t)
+ : _timeline (t)
+ {
+
+ }
+
+ void paint (wxGraphicsContext* g)
+ {
+ _last_paint_bbox = bbox ();
+ do_paint (g);
+ }
+
+ void force_redraw ()
+ {
+ _timeline.force_redraw (_last_paint_bbox);
+ _timeline.force_redraw (bbox ());
+ }
+
+ virtual dcpomatic::Rect bbox () const = 0;
+
+protected:
+ virtual void do_paint (wxGraphicsContext *) = 0;
+
+ int time_x (Time t) const
+ {
+ return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
+ }
+
+ Timeline& _timeline;
+
+private:
+ dcpomatic::Rect _last_paint_bbox;
+};
+
+class ContentView : public View
+{
+public:
+ ContentView (Timeline& tl, shared_ptr<Content> c, int t)
+ : View (tl)
+ , _content (c)
+ , _track (t)
+ , _selected (false)
+ {
+ _content_connection = c->Changed.connect (bind (&ContentView::content_changed, this, _2));
+ }
+
+ dcpomatic::Rect bbox () const
+ {
+ shared_ptr<const Film> film = _timeline.film ();
+ shared_ptr<const Content> content = _content.lock ();
+ if (!film || !content) {
+ return dcpomatic::Rect ();
+ }
+
+ return dcpomatic::Rect (
+ time_x (content->start ()) - 8,
+ y_pos (_track) - 8,
+ content->length () * _timeline.pixels_per_time_unit() + 16,
+ _timeline.track_height() + 16
+ );
+ }
+
+ void set_selected (bool s) {
+ _selected = s;
+ force_redraw ();
+ }
+
+ bool selected () const {
+ return _selected;
+ }
+
+ weak_ptr<Content> content () const {
+ return _content;
+ }
+
+ void set_track (int t) {
+ _track = t;
+ }
+
+ int track () const {
+ return _track;
+ }
+
+ virtual wxString type () const = 0;
+ virtual wxColour colour () const = 0;
+
+private:
+
+ void do_paint (wxGraphicsContext* gc)
+ {
+ shared_ptr<const Film> film = _timeline.film ();
+ shared_ptr<const Content> content = _content.lock ();
+ if (!film || !content) {
+ return;
+ }
+
+ Time const start = content->start ();
+ Time const len = content->length ();
+
+ wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
+
+ gc->SetPen (*wxBLACK_PEN);
+
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+ gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID));
+ if (_selected) {
+ gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxBRUSHSTYLE_SOLID));
+ } else {
+ gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxBRUSHSTYLE_SOLID));
+ }
+#else
+ gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxSOLID));
+ if (_selected) {
+ gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxSOLID));
+ } else {
+ gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxSOLID));
+ }
+#endif
+
+ wxGraphicsPath path = gc->CreatePath ();
+ path.MoveToPoint (time_x (start), y_pos (_track) + 4);
+ path.AddLineToPoint (time_x (start + len), y_pos (_track) + 4);
+ path.AddLineToPoint (time_x (start + len), y_pos (_track + 1) - 4);
+ path.AddLineToPoint (time_x (start), y_pos (_track + 1) - 4);
+ path.AddLineToPoint (time_x (start), y_pos (_track) + 4);
+ gc->StrokePath (path);
+ gc->FillPath (path);
+
+ wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (content->file().filename().string()).data(), type().data());
+ wxDouble name_width;
+ wxDouble name_height;
+ wxDouble name_descent;
+ wxDouble name_leading;
+ gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
+
+ gc->Clip (wxRegion (time_x (start), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
+ gc->DrawText (name, time_x (start) + 12, y_pos (_track + 1) - name_height - 4);
+ gc->ResetClip ();
+ }
+
+ int y_pos (int t) const
+ {
+ return _timeline.tracks_position().y + t * _timeline.track_height();
+ }
+
+ void content_changed (int p)
+ {
+ if (p == ContentProperty::START || p == ContentProperty::LENGTH) {
+ force_redraw ();
+ }
+ }
+
+ boost::weak_ptr<Content> _content;
+ int _track;
+ bool _selected;
+
+ boost::signals2::scoped_connection _content_connection;
+};
+
+class AudioContentView : public ContentView
+{
+public:
+ AudioContentView (Timeline& tl, shared_ptr<Content> c, int t)
+ : ContentView (tl, c, t)
+ {}
+
+private:
+ wxString type () const
+ {
+ return _("audio");
+ }
+
+ wxColour colour () const
+ {
+ return wxColour (149, 121, 232, 255);
+ }
+};
+
+class VideoContentView : public ContentView
+{
+public:
+ VideoContentView (Timeline& tl, shared_ptr<Content> c, int t)
+ : ContentView (tl, c, t)
+ {}
+
+private:
+
+ wxString type () const
+ {
+ return _("video");
+ }
+
+ wxColour colour () const
+ {
+ return wxColour (242, 92, 120, 255);
+ }
+};
+
+class TimeAxisView : public View
+{
+public:
+ TimeAxisView (Timeline& tl, int y)
+ : View (tl)
+ , _y (y)
+ {}
+
+ dcpomatic::Rect bbox () const
+ {
+ return dcpomatic::Rect (0, _y - 4, _timeline.width(), 24);
+ }
+
+ void set_y (int y)
+ {
+ _y = y;
+ force_redraw ();
+ }
+
+private:
+
+ void do_paint (wxGraphicsContext* gc)
+ {
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+ gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
+#else
+ gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxSOLID));
+#endif
+
+ int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ()));
+ if (mark_interval > 5) {
+ mark_interval -= mark_interval % 5;
+ }
+ if (mark_interval > 10) {
+ mark_interval -= mark_interval % 10;
+ }
+ if (mark_interval > 60) {
+ mark_interval -= mark_interval % 60;
+ }
+ if (mark_interval > 3600) {
+ mark_interval -= mark_interval % 3600;
+ }
+
+ if (mark_interval < 1) {
+ mark_interval = 1;
+ }
+
+ wxGraphicsPath path = gc->CreatePath ();
+ path.MoveToPoint (_timeline.x_offset(), _y);
+ path.AddLineToPoint (_timeline.width(), _y);
+ gc->StrokePath (path);
+
+ Time t = 0;
+ while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
+ wxGraphicsPath path = gc->CreatePath ();
+ path.MoveToPoint (time_x (t), _y - 4);
+ path.AddLineToPoint (time_x (t), _y + 4);
+ gc->StrokePath (path);
+
+ int tc = t / TIME_HZ;
+ int const h = tc / 3600;
+ tc -= h * 3600;
+ int const m = tc / 60;
+ tc -= m * 60;
+ int const s = tc;
+
+ wxString str = wxString::Format (wxT ("%02d:%02d:%02d"), h, m, s);
+ wxDouble str_width;
+ wxDouble str_height;
+ wxDouble str_descent;
+ wxDouble str_leading;
+ gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
+
+ int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit();
+ if ((tx + str_width) < _timeline.width()) {
+ gc->DrawText (str, time_x (t), _y + 16);
+ }
+
+ t += mark_interval * TIME_HZ;
+ }
+ }
+
+private:
+ int _y;
+};
+
+Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
+ : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
+ , _film_editor (ed)
+ , _film (film)
+ , _time_axis_view (new TimeAxisView (*this, 32))
+ , _tracks (0)
+ , _pixels_per_time_unit (0)
+ , _left_down (false)
+ , _down_view_start (0)
+ , _first_move (false)
+{
+ SetDoubleBuffered (true);
+
+ setup_pixels_per_time_unit ();
+
+ Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this);
+ Connect (wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler (Timeline::left_down), 0, this);
+ Connect (wxID_ANY, wxEVT_LEFT_UP, wxMouseEventHandler (Timeline::left_up), 0, this);
+ Connect (wxID_ANY, wxEVT_MOTION, wxMouseEventHandler (Timeline::mouse_moved), 0, this);
+ Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Timeline::resized), 0, this);
+
+ playlist_changed ();
+
+ SetMinSize (wxSize (640, tracks() * track_height() + 96));
+
+ _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+
+ _views.push_back (_time_axis_view);
+}
+
+void
+Timeline::paint (wxPaintEvent &)
+{
+ wxPaintDC dc (this);
+
+ wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+ if (!gc) {
+ return;
+ }
+
+ gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+
+ for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
+ (*i)->paint (gc);
+ }
+
+ delete gc;
+}
+
+void
+Timeline::playlist_changed ()
+{
+ shared_ptr<const Film> fl = _film.lock ();
+ if (!fl) {
+ return;
+ }
+
+ _views.clear ();
+
+ Playlist::ContentList content = fl->playlist()->content ();
+
+ for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+ if (dynamic_pointer_cast<VideoContent> (*i)) {
+ _views.push_back (shared_ptr<View> (new VideoContentView (*this, *i, 0)));
+ }
+ if (dynamic_pointer_cast<AudioContent> (*i)) {
+ _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i, 0)));
+ }
+ }
+
+ assign_tracks ();
+ Refresh ();
+}
+
+void
+Timeline::assign_tracks ()
+{
+ for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
+ shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+ if (cv) {
+ cv->set_track (0);
+ _tracks = 1;
+ }
+ }
+
+ for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
+ shared_ptr<AudioContentView> acv = dynamic_pointer_cast<AudioContentView> (*i);
+ if (!acv) {
+ continue;
+ }
+
+ shared_ptr<Content> acv_content = acv->content().lock ();
+ assert (acv_content);
+
+ int t = 1;
+ while (1) {
+ list<shared_ptr<View> >::iterator j = _views.begin();
+ while (j != _views.end()) {
+ shared_ptr<AudioContentView> test = dynamic_pointer_cast<AudioContentView> (*j);
+ if (!test) {
+ ++j;
+ continue;
+ }
+
+ shared_ptr<Content> test_content = test->content().lock ();
+ assert (test_content);
+
+ if (test && test->track() == t) {
+ if ((acv_content->start() <= test_content->start() && test_content->start() <= acv_content->end()) ||
+ (acv_content->start() <= test_content->end() && test_content->end() <= acv_content->end())) {
+ /* we have an overlap on track `t' */
+ ++t;
+ break;
+ }
+ }
+
+ ++j;
+ }
+
+ if (j == _views.end ()) {
+ /* no overlap on `t' */
+ break;
+ }
+ }
+
+ acv->set_track (t);
+ _tracks = max (_tracks, t + 1);
+ }
+
+ _time_axis_view->set_y (tracks() * track_height() + 32);
+}
+
+int
+Timeline::tracks () const
+{
+ return _tracks;
+}
+
+void
+Timeline::setup_pixels_per_time_unit ()
+{
+ shared_ptr<const Film> film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length();
+}
+
+void
+Timeline::left_down (wxMouseEvent& ev)
+{
+ list<shared_ptr<View> >::iterator i = _views.begin();
+ Position const p (ev.GetX(), ev.GetY());
+ while (i != _views.end() && !(*i)->bbox().contains (p)) {
+ ++i;
+ }
+
+ _down_view.reset ();
+
+ if (i != _views.end ()) {
+ shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+ if (cv) {
+ _down_view = cv;
+ shared_ptr<Content> c = cv->content().lock();
+ assert (c);
+ _down_view_start = c->start ();
+ }
+ }
+
+ for (list<shared_ptr<View> >::iterator j = _views.begin(); j != _views.end(); ++j) {
+ shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*j);
+ if (cv) {
+ cv->set_selected (i == j);
+ if (i == j) {
+ _film_editor->set_selection (cv->content ());
+ }
+ }
+ }
+
+ _left_down = true;
+ _down_point = ev.GetPosition ();
+ _first_move = false;
+}
+
+void
+Timeline::left_up (wxMouseEvent &)
+{
+ _left_down = false;
+}
+
+void
+Timeline::mouse_moved (wxMouseEvent& ev)
+{
+ if (!_left_down) {
+ return;
+ }
+
+ wxPoint const p = ev.GetPosition();
+
+ if (!_first_move) {
+ int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
+ if (dist < 8) {
+ return;
+ }
+ _first_move = true;
+ }
+
+ Time const time_diff = (p.x - _down_point.x) / _pixels_per_time_unit;
+ if (_down_view) {
+ shared_ptr<Content> c = _down_view->content().lock();
+ if (c) {
+ c->set_start (max (static_cast<Time> (0), _down_view_start + time_diff));
+
+ shared_ptr<Film> film = _film.lock ();
+ assert (film);
+ film->set_sequence_video (false);
+ }
+ }
+}
+
+void
+Timeline::force_redraw (dcpomatic::Rect const & r)
+{
+ RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
+}
+
+shared_ptr<const Film>
+Timeline::film () const
+{
+ return _film.lock ();
+}
+
+void
+Timeline::resized (wxSizeEvent &)
+{
+ setup_pixels_per_time_unit ();
+}
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
new file mode 100644
index 000000000..5c25a6426
--- /dev/null
+++ b/src/wx/timeline.h
@@ -0,0 +1,86 @@
+/*
+ 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/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include "util.h"
+
+class Film;
+class View;
+class ContentView;
+class FilmEditor;
+class TimeAxisView;
+
+class Timeline : public wxPanel
+{
+public:
+ Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>);
+
+ boost::shared_ptr<const Film> film () const;
+
+ void force_redraw (dcpomatic::Rect const &);
+
+ int x_offset () const {
+ return 8;
+ }
+
+ int width () const {
+ return GetSize().GetWidth ();
+ }
+
+ int track_height () const {
+ return 48;
+ }
+
+ double pixels_per_time_unit () const {
+ return _pixels_per_time_unit;
+ }
+
+ Position tracks_position () const {
+ return Position (8, 8);
+ }
+
+ int tracks () const;
+
+private:
+ void paint (wxPaintEvent &);
+ void left_down (wxMouseEvent &);
+ void mouse_moved (wxMouseEvent &);
+ void left_up (wxMouseEvent &);
+ void playlist_changed ();
+ void setup_pixels_per_time_unit ();
+ void resized (wxSizeEvent &);
+ void assign_tracks ();
+
+ FilmEditor* _film_editor;
+ boost::weak_ptr<Film> _film;
+ std::list<boost::shared_ptr<View> > _views;
+ boost::shared_ptr<TimeAxisView> _time_axis_view;
+ int _tracks;
+ double _pixels_per_time_unit;
+ bool _left_down;
+ wxPoint _down_point;
+ boost::shared_ptr<ContentView> _down_view;
+ Time _down_view_start;
+ bool _first_move;
+
+ boost::signals2::scoped_connection _playlist_connection;
+};
diff --git a/src/lib/video_source.cc b/src/wx/timeline_dialog.cc
index 539243402..35d5eec21 100644
--- a/src/lib/video_source.cc
+++ b/src/wx/timeline_dialog.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,28 +17,26 @@
*/
-#include "video_source.h"
-#include "video_sink.h"
+#include <list>
+#include <wx/graphics.h>
+#include "film_editor.h"
+#include "timeline_dialog.h"
+#include "wx_util.h"
+#include "playlist.h"
+using std::list;
+using std::cout;
using boost::shared_ptr;
-using boost::bind;
-void
-VideoSource::connect_video (shared_ptr<VideoSink> s)
+TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
+ : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+ , _timeline (this, ed, film)
{
- Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
-}
-
-void
-TimedVideoSource::connect_video (shared_ptr<TimedVideoSink> s)
-{
- Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4));
-}
+ wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+
+ sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
-void
-TimedVideoSource::connect_video (shared_ptr<VideoSink> s)
-{
- Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
+ SetSizer (sizer);
+ sizer->Layout ();
+ sizer->SetSizeHints (this);
}
-
-
diff --git a/src/lib/gain.h b/src/wx/timeline_dialog.h
index 61fef5e85..17ca22c49 100644
--- a/src/lib/gain.h
+++ b/src/wx/timeline_dialog.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,18 @@
*/
-#include "processor.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <wx/wx.h>
+#include "timeline.h"
-class Gain : public AudioProcessor
+class Playlist;
+
+class TimelineDialog : public wxDialog
{
public:
- Gain (boost::shared_ptr<Log> log, float gain);
-
- void process_audio (boost::shared_ptr<const AudioBuffers>);
+ TimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
private:
- float _gain;
+ Timeline _timeline;
};
diff --git a/src/wx/wscript b/src/wx/wscript
index 345c02b08..1205fb21b 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -6,6 +6,7 @@ import i18n
sources = """
about_dialog.cc
audio_dialog.cc
+ audio_mapping_view.cc
audio_plot.cc
config_dialog.cc
dci_metadata_dialog.cc
@@ -15,11 +16,15 @@ sources = """
filter_dialog.cc
filter_view.cc
gain_calculator_dialog.cc
+ imagemagick_content_dialog.cc
job_manager_view.cc
job_wrapper.cc
new_film_dialog.cc
properties_dialog.cc
server_dialog.cc
+ timecode.cc
+ timeline.cc
+ timeline_dialog.cc
wx_util.cc
wx_ui_signaller.cc
"""
@@ -47,20 +52,20 @@ def build(bld):
else:
obj = bld(features = 'cxx cxxshlib')
- obj.name = 'libdvdomatic-wx'
+ obj.name = 'libdcpomatic-wx'
obj.includes = [ '..' ]
obj.export_includes = ['.']
obj.uselib = 'WXWIDGETS'
if bld.env.TARGET_LINUX:
obj.uselib += ' GTK'
- obj.use = 'libdvdomatic'
+ obj.use = 'libdcpomatic'
obj.source = sources
- obj.target = 'dvdomatic-wx'
+ obj.target = 'dcpomatic-wx'
- i18n.po_to_mo(os.path.join('src', 'wx'), 'libdvdomatic-wx', bld)
+ i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld)
def pot(bld):
- i18n.pot(os.path.join('src', 'wx'), sources, 'libdvdomatic-wx')
+ i18n.pot(os.path.join('src', 'wx'), sources, 'libdcpomatic-wx')
def pot_merge(bld):
- i18n.pot_merge(os.path.join('src', 'wx'), 'libdvdomatic-wx')
+ i18n.pot_merge(os.path.join('src', 'wx'), 'libdcpomatic-wx')
diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc
index b9e78932a..c5887e17d 100644
--- a/src/wx/wx_util.cc
+++ b/src/wx/wx_util.cc
@@ -76,7 +76,7 @@ add_label_to_grid_bag_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool le
void
error_dialog (wxWindow* parent, wxString m)
{
- wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxOK);
+ wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK);
d->ShowModal ();
d->Destroy ();
}
@@ -84,7 +84,7 @@ error_dialog (wxWindow* parent, wxString m)
bool
confirm_dialog (wxWindow* parent, wxString m)
{
- wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxYES_NO | wxICON_QUESTION);
+ wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
int const r = d->ShowModal ();
d->Destroy ();
return r == wxID_YES;
@@ -231,7 +231,7 @@ checked_set (wxRadioButton* widget, bool value)
}
void
-dvdomatic_setup_i18n ()
+dcpomatic_setup_i18n ()
{
int language = wxLANGUAGE_DEFAULT;
@@ -247,12 +247,12 @@ dvdomatic_setup_i18n ()
if (wxLocale::IsAvailable (language)) {
locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
-#ifdef DVDOMATIC_WINDOWS
+#ifdef DCPOMATIC_WINDOWS
locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
#endif
- locale->AddCatalog (wxT ("libdvdomatic-wx"));
- locale->AddCatalog (wxT ("dvdomatic"));
+ locale->AddCatalog (wxT ("libdcpomatic-wx"));
+ locale->AddCatalog (wxT ("dcpomatic"));
if (!locale->IsOk()) {
delete locale;
@@ -262,6 +262,6 @@ dvdomatic_setup_i18n ()
}
if (locale) {
- dvdomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
+ dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
}
}
diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h
index 18a9f251c..de6a09c35 100644
--- a/src/wx/wx_util.h
+++ b/src/wx/wx_util.h
@@ -17,8 +17,8 @@
*/
-#ifndef DVDOMATIC_WX_UTIL_H
-#define DVDOMATIC_WX_UTIL_H
+#ifndef DCPOMATIC_WX_UTIL_H
+#define DCPOMATIC_WX_UTIL_H
#include <wx/wx.h>
#include <wx/gbsizer.h>
@@ -32,8 +32,8 @@ class wxFilePickerCtrl;
class wxSpinCtrl;
class wxGridBagSizer;
-#define DVDOMATIC_SIZER_X_GAP 8
-#define DVDOMATIC_SIZER_Y_GAP 8
+#define DCPOMATIC_SIZER_X_GAP 8
+#define DCPOMATIC_SIZER_Y_GAP 8
/** @file src/wx/wx_util.h
* @brief Some utility functions and classes.
@@ -45,7 +45,7 @@ extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, bool l
extern wxStaticText* add_label_to_grid_bag_sizer (wxGridBagSizer *, wxWindow *, wxString, bool, wxGBPosition, wxGBSpan span = wxDefaultSpan);
extern std::string wx_to_std (wxString);
extern wxString std_to_wx (std::string);
-extern void dvdomatic_setup_i18n ();
+extern void dcpomatic_setup_i18n ();
/** @class ThreadedStaticText
*
@@ -83,7 +83,7 @@ extern void checked_set (wxStaticText* widget, std::string value);
Use our own dir picker as this is the least bad option I can think of.
*/
#if defined(__WXMSW__) || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION == 24 && GTK_MICRO_VERSION == 17)
-#define DVDOMATIC_USE_OWN_DIR_PICKER
+#define DCPOMATIC_USE_OWN_DIR_PICKER
#endif
#endif