summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/analyse_audio_job.cc108
-rw-r--r--src/lib/analyse_audio_job.h44
-rw-r--r--src/lib/audio_analysis.cc151
-rw-r--r--src/lib/audio_analysis.h69
-rw-r--r--src/lib/encoder.cc26
-rw-r--r--src/lib/ffmpeg_decoder.cc14
-rw-r--r--src/lib/film.cc46
-rw-r--r--src/lib/film.h8
-rw-r--r--src/lib/job.cc1
-rw-r--r--src/lib/job.h1
-rw-r--r--src/lib/options.h6
-rw-r--r--src/lib/util.cc85
-rw-r--r--src/lib/util.h16
-rw-r--r--src/lib/writer.cc6
-rw-r--r--src/lib/wscript2
-rw-r--r--src/tools/dvdomatic.cc12
-rw-r--r--src/wx/audio_dialog.cc191
-rw-r--r--src/wx/audio_dialog.h51
-rw-r--r--src/wx/audio_plot.cc220
-rw-r--r--src/wx/audio_plot.h50
-rw-r--r--src/wx/film_editor.cc41
-rw-r--r--src/wx/film_editor.h5
-rw-r--r--src/wx/film_viewer.cc9
-rw-r--r--src/wx/job_manager_view.cc1
-rw-r--r--src/wx/wscript2
25 files changed, 1098 insertions, 67 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
new file mode 100644
index 000000000..bcabb6c91
--- /dev/null
+++ b/src/lib/analyse_audio_job.cc
@@ -0,0 +1,108 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_analysis.h"
+#include "analyse_audio_job.h"
+#include "compose.hpp"
+#include "film.h"
+#include "options.h"
+#include "decoder_factory.h"
+#include "audio_decoder.h"
+
+using std::string;
+using std::max;
+using std::cout;
+using boost::shared_ptr;
+
+int const AnalyseAudioJob::_num_points = 1024;
+
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
+ : Job (f)
+ , _done (0)
+ , _samples_per_point (1)
+{
+
+}
+
+string
+AnalyseAudioJob::name () const
+{
+ return String::compose ("Analyse audio of %1", _film->name());
+}
+
+void
+AnalyseAudioJob::run ()
+{
+ if (!_film->audio_stream () || !_film->length()) {
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+ return;
+ }
+
+ DecodeOptions options;
+ options.decode_video = false;
+
+ Decoders decoders = decoder_factory (_film, options);
+ assert (decoders.audio);
+
+ decoders.audio->set_audio_stream (_film->audio_stream ());
+ decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+
+ int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->frames_per_second());
+ _samples_per_point = total_audio_frames / _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);
+ }
+
+ _analysis->write (_film->audio_analysis_path ());
+
+ set_progress (1);
+ set_state (FINISHED_OK);
+}
+
+void
+AnalyseAudioJob::audio (shared_ptr<AudioBuffers> b)
+{
+ for (int i = 0; i < b->frames(); ++i) {
+ for (int j = 0; j < b->channels(); ++j) {
+ float s = b->data(j)[i];
+ if (fabsf (s) < 10e-7) {
+ /* stringstream can't serialise and recover inf or -inf, so prevent such
+ values by replacing with this (140dB down) */
+ s = 10e-7;
+ }
+ _current[j][AudioPoint::RMS] += pow (s, 2);
+ _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s));
+
+ if ((_done % _samples_per_point) == 0) {
+ _current[j][AudioPoint::RMS] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point);
+ _analysis->add_point (j, _current[j]);
+
+ _current[j] = AudioPoint ();
+ }
+ }
+
+ ++_done;
+ }
+}
+
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
new file mode 100644
index 000000000..dc1e073ee
--- /dev/null
+++ b/src/lib/analyse_audio_job.h
@@ -0,0 +1,44 @@
+/*
+ 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 "job.h"
+#include "audio_analysis.h"
+
+class AudioBuffers;
+
+class AnalyseAudioJob : public Job
+{
+public:
+ AnalyseAudioJob (boost::shared_ptr<Film> f);
+
+ std::string name () const;
+ void run ();
+
+private:
+ void audio (boost::shared_ptr<AudioBuffers>);
+
+ int64_t _done;
+ int64_t _samples_per_point;
+ std::vector<AudioPoint> _current;
+
+ boost::shared_ptr<AudioAnalysis> _analysis;
+
+ static const int _num_points;
+};
+
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
new file mode 100644
index 000000000..0cf08c5bd
--- /dev/null
+++ b/src/lib/audio_analysis.cc
@@ -0,0 +1,151 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include <cmath>
+#include <cassert>
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include "audio_analysis.h"
+
+using std::ostream;
+using std::istream;
+using std::string;
+using std::ofstream;
+using std::ifstream;
+using std::vector;
+using std::cout;
+using std::max;
+using std::list;
+
+AudioPoint::AudioPoint ()
+{
+ for (int i = 0; i < COUNT; ++i) {
+ _data[i] = 0;
+ }
+}
+
+AudioPoint::AudioPoint (istream& s)
+{
+ for (int i = 0; i < COUNT; ++i) {
+ s >> _data[i];
+ }
+}
+
+void
+AudioPoint::write (ostream& s) const
+{
+ for (int i = 0; i < COUNT; ++i) {
+ s << _data[i] << "\n";
+ }
+}
+
+
+AudioAnalysis::AudioAnalysis (int channels)
+{
+ _data.resize (channels);
+}
+
+AudioAnalysis::AudioAnalysis (string filename)
+{
+ ifstream f (filename.c_str ());
+
+ int channels;
+ f >> channels;
+ _data.resize (channels);
+
+ for (int i = 0; i < channels; ++i) {
+ int points;
+ f >> points;
+ for (int j = 0; j < points; ++j) {
+ _data[i].push_back (AudioPoint (f));
+ }
+ }
+}
+
+void
+AudioAnalysis::add_point (int c, AudioPoint const & p)
+{
+ assert (c < channels ());
+ _data[c].push_back (p);
+}
+
+AudioPoint
+AudioAnalysis::get_point (int c, int p) const
+{
+ assert (p < points (c));
+ return _data[c][p];
+}
+
+int
+AudioAnalysis::channels () const
+{
+ return _data.size ();
+}
+
+int
+AudioAnalysis::points (int c) const
+{
+ assert (c < channels ());
+ return _data[c].size ();
+}
+
+void
+AudioAnalysis::write (string filename)
+{
+ string tmp = filename + ".tmp";
+
+ ofstream f (tmp.c_str ());
+ f << _data.size() << "\n";
+ for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) {
+ f << i->size () << "\n";
+ for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) {
+ j->write (f);
+ }
+ }
+
+ f.close ();
+ boost::filesystem::rename (tmp, filename);
+}
+
+float
+AudioAnalysis::smooth (list<float> const & data, AudioPoint::Type t)
+{
+ float val;
+
+ switch (t) {
+ case AudioPoint::PEAK:
+ /* XXX: fall-off, or something...? */
+ val = -200;
+ for (list<float>::const_iterator i = data.begin(); i != data.end(); ++i) {
+ val = max (val, *i);
+ }
+ return val;
+ case AudioPoint::RMS:
+ val = 0;
+ for (list<float>::const_iterator i = data.begin(); i != data.end(); ++i) {
+ val += pow (*i, 2);
+ }
+ return sqrt (val / data.size());
+ default:
+ assert (false);
+ }
+
+ return 0;
+}
diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h
new file mode 100644
index 000000000..a8cfbdeca
--- /dev/null
+++ b/src/lib/audio_analysis.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_ANALYSIS_H
+#define DVDOMATIC_AUDIO_ANALYSIS_H
+
+#include <iostream>
+#include <vector>
+#include <list>
+
+class AudioPoint
+{
+public:
+ enum Type {
+ PEAK,
+ RMS,
+ COUNT
+ };
+
+ AudioPoint ();
+ AudioPoint (std::istream &);
+
+ void write (std::ostream &) const;
+
+ float& operator[] (int t) {
+ return _data[t];
+ }
+
+private:
+ float _data[COUNT];
+};
+
+class AudioAnalysis
+{
+public:
+ AudioAnalysis (int c);
+ AudioAnalysis (std::string);
+
+ void add_point (int c, AudioPoint const & p);
+
+ AudioPoint get_point (int c, int p) const;
+ int points (int c) const;
+ int channels () const;
+
+ void write (std::string);
+
+ static float smooth (std::list<float> const &, AudioPoint::Type);
+
+private:
+ std::vector<std::vector<AudioPoint> > _data;
+};
+
+#endif
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index f25256379..07139968d 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -423,18 +423,20 @@ Encoder::encoder_thread (ServerDescription* server)
void
Encoder::write_audio (shared_ptr<const AudioBuffers> data)
{
- if (_film->audio_channels() == 1) {
- /* We need to switch things around so that the mono channel is on
- the centre channel of a 5.1 set (with other channels silent).
- */
-
- shared_ptr<AudioBuffers> b (new AudioBuffers (6, data->frames ()));
- b->make_silent (libdcp::LEFT);
- b->make_silent (libdcp::RIGHT);
- memcpy (b->data()[libdcp::CENTRE], data->data()[0], data->frames() * sizeof(float));
- b->make_silent (libdcp::LFE);
- b->make_silent (libdcp::LS);
- b->make_silent (libdcp::RS);
+ AudioMapping m (_film->audio_channels ());
+ if (m.dcp_channels() != _film->audio_channels()) {
+
+ /* Remap (currently just for mono -> 5.1) */
+
+ 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;
}
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index d4ed76e37..fa2355cc2 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -197,7 +197,7 @@ FFmpegDecoder::setup_audio ()
void
FFmpegDecoder::setup_subtitle ()
{
- if (!_subtitle_stream) {
+ if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
return;
}
@@ -236,8 +236,10 @@ FFmpegDecoder::pass ()
int frame_finished;
- while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- filter_and_emit_video (_frame);
+ if (_opt.decode_video) {
+ while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ filter_and_emit_video (_frame);
+ }
}
if (_audio_stream && _opt.decode_audio) {
@@ -258,7 +260,7 @@ FFmpegDecoder::pass ()
shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
- if (_packet.stream_index == _video_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);
@@ -288,9 +290,9 @@ FFmpegDecoder::pass ()
was before this packet. Until then audio is thrown away.
*/
- if (_first_video && _first_video.get() <= source_pts_seconds) {
+ if ((_first_video && _first_video.get() <= source_pts_seconds) || !_opt.decode_video) {
- if (!_first_audio) {
+ if (!_first_audio && _opt.decode_video) {
_first_audio = source_pts_seconds;
/* This is our first audio frame, and if we've arrived here we must have had our
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 1cf161259..ab636bdfc 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -51,6 +51,7 @@
#include "video_decoder.h"
#include "audio_decoder.h"
#include "external_audio_decoder.h"
+#include "analyse_audio_job.h"
using std::string;
using std::stringstream;
@@ -237,6 +238,15 @@ Film::video_mxf_filename () const
return video_state_identifier() + ".mxf";
}
+string
+Film::audio_analysis_path () const
+{
+ boost::filesystem::path p;
+ p /= "analysis";
+ p /= content_digest();
+ return file (p.string ());
+}
+
/** Add suitable Jobs to the JobManager to create a DCP for this Film */
void
Film::make_dcp ()
@@ -303,6 +313,19 @@ Film::make_dcp ()
}
}
+/** 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 ()
@@ -317,6 +340,15 @@ Film::examine_content ()
}
void
+Film::analyse_audio_finished ()
+{
+ ensure_ui_thread ();
+ _analyse_audio_job.reset ();
+
+ AudioAnalysisFinished ();
+}
+
+void
Film::examine_content_finished ()
{
_examine_content_job.reset ();
@@ -870,6 +902,13 @@ Film::set_content (string c)
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());
@@ -879,13 +918,6 @@ Film::set_content (string c)
set_subtitle_stream (d.video->subtitle_streams().front());
}
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _content = c;
- }
-
- signal_changed (CONTENT);
-
examine_content ();
} catch (...) {
diff --git a/src/lib/film.h b/src/lib/film.h
index 04a483998..847ab434e 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -45,6 +45,7 @@ class Job;
class Filter;
class Log;
class ExamineContentJob;
+class AnalyseAudioJob;
class ExternalAudioStream;
/** @class Film
@@ -65,8 +66,10 @@ public:
std::string info_path (int f) const;
std::string video_mxf_dir () const;
std::string video_mxf_filename () const;
+ std::string audio_analysis_path () const;
void examine_content ();
+ void analyse_audio ();
void send_dcp_to_tms ();
void make_dcp ();
@@ -364,6 +367,8 @@ public:
/** Emitted when some property has changed */
mutable boost::signals2::signal<void (Property)> Changed;
+ boost::signals2::signal<void ()> AudioAnalysisFinished;
+
/** Current version number of the state file */
static int const state_version;
@@ -374,9 +379,12 @@ private:
/** 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;
/** Complete path to directory containing the film metadata;
diff --git a/src/lib/job.cc b/src/lib/job.cc
index bfad65a0a..6a53e629c 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -147,7 +147,6 @@ Job::set_state (State s)
if (_state == FINISHED_OK || _state == FINISHED_ERROR) {
_ran_for = elapsed_time ();
- Finished ();
}
}
diff --git a/src/lib/job.h b/src/lib/job.h
index 1538e2779..c98dbaea1 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -65,6 +65,7 @@ public:
void descend (float);
float overall_progress () const;
+ /** Emitted by the JobManagerView from the UI thread */
boost::signals2::signal<void()> Finished;
protected:
diff --git a/src/lib/options.h b/src/lib/options.h
index 2cd7dffde..0d2c07fd5 100644
--- a/src/lib/options.h
+++ b/src/lib/options.h
@@ -28,11 +28,13 @@ class DecodeOptions
{
public:
DecodeOptions ()
- : decode_audio (true)
+ : decode_video (true)
+ , decode_audio (true)
, decode_subtitles (false)
, video_sync (true)
{}
-
+
+ bool decode_video;
bool decode_audio;
bool decode_subtitles;
bool video_sync;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 4ee304600..8ad980361 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -445,19 +445,6 @@ dcp_audio_sample_rate (int fs)
return 96000;
}
-int
-dcp_audio_channels (int f)
-{
- if (f == 1) {
- /* The source is mono, so to put the mono channel into
- the centre we need to generate a 5.1 soundtrack.
- */
- return 6;
- }
-
- return f;
-}
-
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);
@@ -898,3 +885,75 @@ cpu_info ()
return info;
}
+
+string
+audio_channel_name (int c)
+{
+ assert (MAX_AUDIO_CHANNELS == 6);
+
+ /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
+ enhancement channel (sub-woofer)./
+ */
+ string const channels[] = {
+ "Left",
+ "Right",
+ "Centre",
+ "Lfe (sub)",
+ "Left surround",
+ "Right surround",
+ };
+
+ return channels[c];
+}
+
+AudioMapping::AudioMapping (int c)
+ : _source_channels (c)
+{
+
+}
+
+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);
+}
+
+int
+AudioMapping::dcp_channels () const
+{
+ if (_source_channels == 1) {
+ /* The source is mono, so to put the mono channel into
+ the centre we need to generate a 5.1 soundtrack.
+ */
+ return 6;
+ }
+
+ return _source_channels;
+}
diff --git a/src/lib/util.h b/src/lib/util.h
index 87735ea8e..22c6ea95b 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -29,6 +29,7 @@
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
+#include <boost/optional.hpp>
#include <libdcp/util.h>
extern "C" {
#include <libavcodec/avcodec.h>
@@ -57,6 +58,7 @@ extern std::vector<std::string> split_at_spaces_considering_quotes (std::string)
extern std::string md5_digest (std::string);
extern std::string md5_digest (void const *, int);
extern void ensure_ui_thread ();
+extern std::string audio_channel_name (int);
typedef int SourceFrame;
@@ -178,7 +180,6 @@ struct Rect
extern std::string crop_string (Position, libdcp::Size);
extern int dcp_audio_sample_rate (int);
-extern int dcp_audio_channels (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);
@@ -268,6 +269,19 @@ private:
float** _data;
};
+class AudioMapping
+{
+public:
+ AudioMapping (int);
+
+ boost::optional<libdcp::Channel> source_to_dcp (int c) const;
+ boost::optional<int> dcp_to_source (libdcp::Channel c) const;
+ int dcp_channels () const;
+
+private:
+ int _source_channels;
+};
+
extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
extern bool still_image_file (std::string);
extern std::pair<std::string, int> cpu_info ();
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index c2cc00328..1dad4357d 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -72,13 +72,15 @@ Writer::Writer (shared_ptr<Film> f)
_picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
- if (dcp_audio_channels (_film->audio_channels()) > 0) {
+ AudioMapping m (_film->audio_channels ());
+
+ if (m.dcp_channels() > 0) {
_sound_asset.reset (
new libdcp::SoundAsset (
_film->dir (_film->dcp_name()),
"audio.mxf",
DCPFrameRate (_film->frames_per_second()).frames_per_second,
- dcp_audio_channels (_film->audio_channels()),
+ m.dcp_channels (),
dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
)
);
diff --git a/src/lib/wscript b/src/lib/wscript
index eee04190c..c2b46112c 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -16,6 +16,8 @@ def build(bld):
obj.source = """
ab_transcode_job.cc
ab_transcoder.cc
+ analyse_audio_job.cc
+ audio_analysis.cc
audio_decoder.cc
audio_source.cc
config.cc
diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc
index 1b76132f6..4f380b5ad 100644
--- a/src/tools/dvdomatic.cc
+++ b/src/tools/dvdomatic.cc
@@ -145,7 +145,7 @@ enum {
ID_jobs_make_dcp,
ID_jobs_send_dcp_to_tms,
ID_jobs_show_dcp,
- ID_jobs_examine_content,
+ ID_jobs_analyse_audio,
ID_help_about
};
@@ -170,7 +170,7 @@ setup_menu (wxMenuBar* m)
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, "&Examine content", ID_jobs_examine_content, NEEDS_FILM);
+ add_item (jobs_menu, "&Analyse audio", ID_jobs_analyse_audio, NEEDS_FILM);
wxMenu* help = new wxMenu;
add_item (help, "About", ID_help_about, ALWAYS);
@@ -207,7 +207,7 @@ 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_examine_content, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_examine_content));
+ Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
Connect (ID_help_about, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
@@ -386,10 +386,10 @@ private:
}
#endif
}
-
- void jobs_examine_content (wxCommandEvent &)
+
+ void jobs_analyse_audio (wxCommandEvent &)
{
- film->examine_content ();
+ film->analyse_audio ();
}
void help_about (wxCommandEvent &)
diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc
new file mode 100644
index 000000000..bcec01332
--- /dev/null
+++ b/src/wx/audio_dialog.cc
@@ -0,0 +1,191 @@
+/*
+ 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/filesystem.hpp>
+#include "audio_dialog.h"
+#include "audio_plot.h"
+#include "audio_analysis.h"
+#include "film.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+using boost::bind;
+using boost::optional;
+
+AudioDialog::AudioDialog (wxWindow* parent)
+ : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ , _plot (0)
+{
+ wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+
+ _plot = new AudioPlot (this);
+ sizer->Add (_plot, 1, wxALL, 12);
+
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
+
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, audio_channel_name (i));
+ table->Add (_channel_checkbox[i], 1, wxEXPAND);
+ table->AddSpacer (0);
+ _channel_checkbox[i]->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (AudioDialog::channel_clicked), 0, this);
+ }
+
+ table->AddSpacer (0);
+ table->AddSpacer (0);
+
+ wxString const types[] = {
+ _("Peak"),
+ _("RMS")
+ };
+
+ for (int i = 0; i < AudioPoint::COUNT; ++i) {
+ _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
+ table->Add (_type_checkbox[i], 1, wxEXPAND);
+ table->AddSpacer (0);
+ _type_checkbox[i]->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (AudioDialog::type_clicked), 0, this);
+ }
+
+ _smoothing = new wxSlider (this, wxID_ANY, 1, 1, 128);
+ _smoothing->Connect (wxID_ANY, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler (AudioDialog::smoothing_changed), 0, this);
+ table->Add (_smoothing, 1, wxEXPAND);
+ table->AddSpacer (0);
+
+ sizer->Add (table, 0, wxALL, 12);
+
+ SetSizer (sizer);
+ sizer->Layout ();
+ sizer->SetSizeHints (this);
+}
+
+void
+AudioDialog::set_film (boost::shared_ptr<Film> f)
+{
+ _film_changed_connection.disconnect ();
+ _film_audio_analysis_finished_connection.disconnect ();
+
+ _film = f;
+
+ try_to_load_analysis ();
+ setup_channels ();
+ _plot->set_gain (_film->audio_gain ());
+
+ _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
+ _film_audio_analysis_finished_connection = _film->AudioAnalysisFinished.connect (bind (&AudioDialog::try_to_load_analysis, this));
+
+ SetTitle (String::compose ("DVD-o-matic audio - %1", _film->name()));
+}
+
+void
+AudioDialog::setup_channels ()
+{
+ if (!_film->audio_stream()) {
+ return;
+ }
+
+ AudioMapping m (_film->audio_stream()->channels ());
+
+ 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 ()));
+ } else {
+ if (IsShown ()) {
+ _film->analyse_audio ();
+ }
+ }
+
+ _plot->set_analysis (a);
+
+ AudioMapping m (_film->audio_stream()->channels ());
+ optional<libdcp::Channel> c = m.source_to_dcp (0);
+ if (c) {
+ _channel_checkbox[c.get()]->SetValue (true);
+ _plot->set_channel_visible (0, true);
+ }
+
+ for (int i = 0; i < AudioPoint::COUNT; ++i) {
+ _type_checkbox[i]->SetValue (true);
+ _plot->set_type_visible (i, true);
+ }
+}
+
+void
+AudioDialog::channel_clicked (wxCommandEvent& ev)
+{
+ int c = 0;
+ while (c < MAX_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
+ ++c;
+ }
+
+ assert (c < MAX_AUDIO_CHANNELS);
+
+ AudioMapping m (_film->audio_stream()->channels ());
+ optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c));
+ if (s) {
+ _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ());
+ }
+}
+
+void
+AudioDialog::film_changed (Film::Property 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;
+ }
+}
+
+void
+AudioDialog::type_clicked (wxCommandEvent& ev)
+{
+ int t = 0;
+ while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
+ ++t;
+ }
+
+ assert (t < AudioPoint::COUNT);
+
+ _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
+}
+
+void
+AudioDialog::smoothing_changed (wxScrollEvent &)
+{
+ _plot->set_smoothing (_smoothing->GetValue ());
+}
diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h
new file mode 100644
index 000000000..16cb356fe
--- /dev/null
+++ b/src/wx/audio_dialog.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.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include "lib/film.h"
+#include "lib/audio_analysis.h"
+
+class AudioPlot;
+class Film;
+
+class AudioDialog : public wxDialog
+{
+public:
+ AudioDialog (wxWindow *);
+
+ void set_film (boost::shared_ptr<Film>);
+
+private:
+ void film_changed (Film::Property);
+ 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;
+ 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_finished_connection;
+};
diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc
new file mode 100644
index 000000000..d938d0c27
--- /dev/null
+++ b/src/wx/audio_plot.cc
@@ -0,0 +1,220 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/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"
+
+using std::cout;
+using std::vector;
+using std::list;
+using std::max;
+using std::min;
+using boost::bind;
+using boost::shared_ptr;
+
+int const AudioPlot::_minimum = -70;
+
+AudioPlot::AudioPlot (wxWindow* parent)
+ : wxPanel (parent)
+ , _gain (0)
+ , _smoothing (1)
+{
+ SetDoubleBuffered (true);
+
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ _channel_visible[i] = false;
+ }
+
+ for (int i = 0; i < AudioPoint::COUNT; ++i) {
+ _type_visible[i] = false;
+ }
+
+ _colours.push_back (wxColour ( 0, 0, 0));
+ _colours.push_back (wxColour (255, 0, 0));
+ _colours.push_back (wxColour ( 0, 255, 0));
+ _colours.push_back (wxColour (139, 0, 204));
+ _colours.push_back (wxColour ( 0, 0, 255));
+ _colours.push_back (wxColour (100, 100, 100));
+
+ Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (AudioPlot::paint), 0, this);
+
+ SetMinSize (wxSize (640, 512));
+}
+
+void
+AudioPlot::set_analysis (shared_ptr<AudioAnalysis> a)
+{
+ _analysis = a;
+
+ for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+ _channel_visible[i] = false;
+ }
+
+ for (int i = 0; i < AudioPoint::COUNT; ++i) {
+ _type_visible[i] = false;
+ }
+
+ Refresh ();
+}
+
+void
+AudioPlot::set_channel_visible (int c, bool v)
+{
+ _channel_visible[c] = v;
+ Refresh ();
+}
+
+void
+AudioPlot::set_type_visible (int t, bool v)
+{
+ _type_visible[t] = v;
+ Refresh ();
+}
+
+void
+AudioPlot::paint (wxPaintEvent &)
+{
+ wxPaintDC dc (this);
+
+ wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+ if (!gc) {
+ return;
+ }
+
+ if (!_analysis || _analysis->channels() == 0) {
+ gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+ gc->DrawText (_("Please wait; audio is being analysed..."), 32, 32);
+ return;
+ }
+
+ wxGraphicsPath grid = gc->CreatePath ();
+ gc->SetFont (gc->CreateFont (*wxSMALL_FONT));
+ wxDouble db_label_width;
+ wxDouble db_label_height;
+ wxDouble db_label_descent;
+ wxDouble db_label_leading;
+ gc->GetTextExtent (_("-80dB"), &db_label_width, &db_label_height, &db_label_descent, &db_label_leading);
+
+ db_label_width += 8;
+
+ int const data_width = GetSize().GetWidth() - db_label_width;
+ /* Assume all channels have the same number of points */
+ float const xs = data_width / float (_analysis->points (0));
+ int const height = GetSize().GetHeight ();
+ int const yo = 32;
+ float const ys = (height - yo) / -_minimum;
+
+ for (int i = _minimum; i <= 0; i += 10) {
+ int const y = (height - (i - _minimum) * ys) - yo;
+ grid.MoveToPoint (db_label_width - 4, y);
+ grid.AddLineToPoint (db_label_width + data_width, y);
+ gc->DrawText (std_to_wx (String::compose ("%1dB", i)), 0, y - (db_label_height / 2));
+ }
+
+ gc->SetPen (*wxLIGHT_GREY_PEN);
+ gc->StrokePath (grid);
+
+ gc->DrawText (_("Time"), data_width, height - yo + db_label_height / 2);
+
+ for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
+ if (!_channel_visible[c] || c >= _analysis->channels()) {
+ continue;
+ }
+
+ wxGraphicsPath path[AudioPoint::COUNT];
+
+ for (int i = 0; i < AudioPoint::COUNT; ++i) {
+ if (!_type_visible[i]) {
+ continue;
+ }
+
+ path[i] = gc->CreatePath ();
+
+ float const val = 20 * log10 (_analysis->get_point(c, 0)[i]);
+
+ path[i].MoveToPoint (
+ db_label_width,
+ height - (max (val, float (_minimum)) - _minimum + _gain) * ys - yo
+ );
+ }
+
+ list<float> smoothing[AudioPoint::COUNT];
+
+ for (int i = 0; i < _analysis->points(c); ++i) {
+ for (int j = 0; j < AudioPoint::COUNT; ++j) {
+ if (!_type_visible[j]) {
+ continue;
+ }
+
+ smoothing[j].push_back (_analysis->get_point(c, i)[j]);
+ if (int(smoothing[j].size()) > _smoothing) {
+ smoothing[j].pop_front ();
+ }
+
+ float const val = 20 * log10 (_analysis->smooth (smoothing[j], static_cast<AudioPoint::Type> (j)));
+
+ path[j].AddLineToPoint (
+ i * xs + db_label_width,
+ height - (max (val, float (_minimum)) - _minimum + _gain) * ys - yo
+ );
+ }
+ }
+
+ wxColour const col = _colours[c];
+
+ if (_type_visible[AudioPoint::RMS]) {
+ gc->SetPen (*wxThePenList->FindOrCreatePen (col));
+ gc->StrokePath (path[AudioPoint::RMS]);
+ }
+
+ if (_type_visible[AudioPoint::PEAK]) {
+ gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (col.Red(), col.Green(), col.Blue(), col.Alpha() / 2)));
+ gc->StrokePath (path[AudioPoint::PEAK]);
+ }
+ }
+
+ wxGraphicsPath axes = gc->CreatePath ();
+ axes.MoveToPoint (db_label_width, 0);
+ axes.AddLineToPoint (db_label_width, height - yo);
+ axes.AddLineToPoint (db_label_width + data_width, height - yo);
+ gc->SetPen (*wxBLACK_PEN);
+ gc->StrokePath (axes);
+
+ delete gc;
+}
+
+void
+AudioPlot::set_gain (float g)
+{
+ _gain = g;
+ Refresh ();
+}
+
+void
+AudioPlot::set_smoothing (int s)
+{
+ _smoothing = s;
+ Refresh ();
+}
diff --git a/src/wx/audio_plot.h b/src/wx/audio_plot.h
new file mode 100644
index 000000000..fe8862d54
--- /dev/null
+++ b/src/wx/audio_plot.h
@@ -0,0 +1,50 @@
+/*
+ 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 <vector>
+#include <boost/shared_ptr.hpp>
+#include <wx/wx.h>
+#include "util.h"
+#include "audio_analysis.h"
+
+class AudioPlot : public wxPanel
+{
+public:
+ AudioPlot (wxWindow *);
+
+ void set_analysis (boost::shared_ptr<AudioAnalysis>);
+ void set_channel_visible (int c, bool v);
+ void set_type_visible (int t, bool v);
+ void set_gain (float);
+ void set_smoothing (int);
+
+private:
+ void paint (wxPaintEvent &);
+
+ boost::shared_ptr<AudioAnalysis> _analysis;
+ bool _channel_visible[MAX_AUDIO_CHANNELS];
+ bool _type_visible[AudioPoint::COUNT];
+ /** gain to apply in dB */
+ float _gain;
+ int _smoothing;
+
+ std::vector<wxColour> _colours;
+
+ static const int _minimum;
+};
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index 68291a812..c9f83677c 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -45,6 +45,7 @@
#include "sound_processor.h"
#include "dci_metadata_dialog.h"
#include "scaler.h"
+#include "audio_dialog.h"
using std::string;
using std::cout;
@@ -62,6 +63,7 @@ FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
: wxPanel (parent)
, _film (f)
, _generally_sensitive (true)
+ , _audio_dialog (0)
{
wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
SetSizer (s);
@@ -204,6 +206,7 @@ FilmEditor::connect_to_widgets ()
_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);
@@ -303,6 +306,10 @@ FilmEditor::make_audio_panel ()
wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
_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);
+
{
video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain")));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
@@ -340,22 +347,8 @@ FilmEditor::make_audio_panel ()
grid->Add (_use_external_audio);
grid->AddSpacer (0);
- assert (MAX_AUDIO_CHANNELS == 6);
-
- /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
- enhancement channel (sub-woofer)./
- */
- wxString const channels[] = {
- _("Left"),
- _("Right"),
- _("Centre"),
- _("Lfe (sub)"),
- _("Left surround"),
- _("Right surround"),
- };
-
for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- add_label_to_sizer (grid, _audio_panel, channels[i]);
+ add_label_to_sizer (grid, _audio_panel, audio_channel_name (i));
_external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
grid->Add (_external_audio[i], 1, wxEXPAND);
}
@@ -756,6 +749,10 @@ FilmEditor::set_film (shared_ptr<Film> f)
} else {
FileChanged ("");
}
+
+ if (_audio_dialog) {
+ _audio_dialog->set_film (_film);
+ }
film_changed (Film::NAME);
film_changed (Film::USE_DCI_NAME);
@@ -817,6 +814,7 @@ FilmEditor::set_things_sensitive (bool 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);
@@ -1175,3 +1173,16 @@ FilmEditor::setup_dcp_name ()
_dcp_name->SetLabel (std_to_wx (s));
}
}
+
+void
+FilmEditor::show_audio_clicked (wxCommandEvent &)
+{
+ if (_audio_dialog) {
+ _audio_dialog->Destroy ();
+ _audio_dialog = 0;
+ }
+
+ _audio_dialog = new AudioDialog (this);
+ _audio_dialog->Show ();
+ _audio_dialog->set_film (_film);
+}
diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h
index 8cb90b481..2af747bb0 100644
--- a/src/wx/film_editor.h
+++ b/src/wx/film_editor.h
@@ -29,8 +29,8 @@
#include "lib/film.h"
class wxNotebook;
-
class Film;
+class AudioDialog;
/** @class FilmEditor
* @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -70,6 +70,7 @@ private:
void scaler_changed (wxCommandEvent &);
void audio_gain_changed (wxCommandEvent &);
void audio_gain_calculate_button_clicked (wxCommandEvent &);
+ void show_audio_clicked (wxCommandEvent &);
void audio_delay_changed (wxCommandEvent &);
void with_subtitles_toggled (wxCommandEvent &);
void subtitle_offset_changed (wxCommandEvent &);
@@ -145,6 +146,7 @@ private:
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;
@@ -177,4 +179,5 @@ private:
std::vector<Format const *> _formats;
bool _generally_sensitive;
+ AudioDialog* _audio_dialog;
};
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index 96656ce09..6d46b2c21 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -101,7 +101,14 @@ FilmViewer::film_changed (Film::Property p)
o.decode_audio = false;
o.decode_subtitles = true;
o.video_sync = false;
- _decoders = decoder_factory (_film, o);
+
+ try {
+ _decoders = decoder_factory (_film, o);
+ } catch (StringError& e) {
+ error_dialog (this, wxString::Format (_("Could not open content file (%s)"), e.what()));
+ return;
+ }
+
if (_decoders.video == 0) {
break;
}
diff --git a/src/wx/job_manager_view.cc b/src/wx/job_manager_view.cc
index 7537da287..7361f29a8 100644
--- a/src/wx/job_manager_view.cc
+++ b/src/wx/job_manager_view.cc
@@ -109,6 +109,7 @@ JobManagerView::update ()
if ((*i)->finished() && !_job_records[*i].finalised) {
_job_records[*i].gauge->SetValue (100);
checked_set (_job_records[*i].message, st);
+ (*i)->Finished ();
_job_records[*i].finalised = true;
if (!(*i)->error_details().empty ()) {
_job_records[*i].details->Enable (true);
diff --git a/src/wx/wscript b/src/wx/wscript
index d844b1f1b..3fa40f55a 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -13,6 +13,8 @@ def build(bld):
obj.uselib = 'WXWIDGETS'
obj.use = 'libdvdomatic'
obj.source = """
+ audio_dialog.cc
+ audio_plot.cc
config_dialog.cc
dci_metadata_dialog.cc
dir_picker_ctrl.cc