diff options
| author | Carl Hetherington <cth@carlh.net> | 2015-05-20 16:29:25 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2015-05-20 16:29:25 +0100 |
| commit | 6f0a590bc3266f21ba577116219bd019e891d480 (patch) | |
| tree | 273721d852a9b90b541c8fcefd10d209e6ef2ce2 /src | |
| parent | ada329f77032590bae1e18d05a87f94c82e14a55 (diff) | |
| parent | b433d33bcbfccf29171fe24c55fdee550a8c36aa (diff) | |
Merge branch '2.0' of git.carlh.net:git/dcpomatic into 2.0
Diffstat (limited to 'src')
74 files changed, 787 insertions, 348 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 079fe884e..cdf623876 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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 @@ -39,6 +39,8 @@ AnalyseAudioJob::AnalyseAudioJob (shared_ptr<const Film> f, shared_ptr<AudioCont , _content (c) , _done (0) , _samples_per_point (1) + , _overall_peak (0) + , _overall_peak_frame (0) { } @@ -81,6 +83,7 @@ AnalyseAudioJob::run () set_progress (t.seconds() / _film->length().seconds()); } + _analysis->set_peak (_overall_peak, DCPTime::from_frames (_overall_peak_frame, _film->audio_frame_rate ())); _analysis->write (content->audio_analysis_path ()); set_progress (1); @@ -101,6 +104,15 @@ AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b) _current[j][AudioPoint::RMS] += pow (s, 2); _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s)); + float const as = fabs (s); + + _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], as); + + if (as > _overall_peak) { + _overall_peak = as; + _overall_peak_frame = _done + i; + } + if ((_done % _samples_per_point) == 0) { _current[j][AudioPoint::RMS] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point); _analysis->add_point (j, _current[j]); diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 6f64dd272..0f9605eed 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -52,6 +52,9 @@ private: int64_t _samples_per_point; std::vector<AudioPoint> _current; + float _overall_peak; + AudioFrame _overall_peak_frame; + boost::shared_ptr<AudioAnalysis> _analysis; static const int _num_points; diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc index 19a0d876e..73422a9be 100644 --- a/src/lib/audio_analysis.cc +++ b/src/lib/audio_analysis.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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,14 +18,17 @@ */ #include "audio_analysis.h" -#include "dcpomatic_assert.h" #include "cross.h" +#include "util.h" +#include "raw_convert.h" +#include <libxml++/libxml++.h> #include <boost/filesystem.hpp> +#include <boost/foreach.hpp> #include <stdint.h> #include <cmath> -#include <cassert> #include <cstdio> #include <iostream> +#include <inttypes.h> using std::ostream; using std::istream; @@ -34,6 +37,7 @@ using std::vector; using std::cout; using std::max; using std::list; +using boost::shared_ptr; AudioPoint::AudioPoint () { @@ -42,14 +46,10 @@ AudioPoint::AudioPoint () } } -AudioPoint::AudioPoint (FILE* f) +AudioPoint::AudioPoint (cxml::ConstNodePtr node) { - for (int i = 0; i < COUNT; ++i) { - int n = fscanf (f, "%f", &_data[i]); - if (n != 1) { - _data[i] = 0; - } - } + _data[PEAK] = node->number_child<float> ("Peak"); + _data[RMS] = node->number_child<float> ("RMS"); } AudioPoint::AudioPoint (AudioPoint const & other) @@ -74,14 +74,12 @@ AudioPoint::operator= (AudioPoint const & other) } void -AudioPoint::write (FILE* f) const +AudioPoint::as_xml (xmlpp::Element* parent) const { - for (int i = 0; i < COUNT; ++i) { - fprintf (f, "%f\n", _data[i]); - } + parent->add_child ("Peak")->add_child_text (raw_convert<string> (_data[PEAK])); + parent->add_child ("RMS")->add_child_text (raw_convert<string> (_data[RMS])); } - AudioAnalysis::AudioAnalysis (int channels) { _data.resize (channels); @@ -89,33 +87,21 @@ AudioAnalysis::AudioAnalysis (int channels) AudioAnalysis::AudioAnalysis (boost::filesystem::path filename) { - FILE* f = fopen_boost (filename, "r"); - if (!f) { - throw OpenFileError (filename); - } + cxml::Document f ("AudioAnalysis"); + f.read_file (filename); - int channels = 0; - fscanf (f, "%d", &channels); - _data.resize (channels); + BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) { + vector<AudioPoint> channel; - for (int i = 0; i < channels; ++i) { - int points; - fscanf (f, "%d", &points); - if (feof (f)) { - fclose (f); - return; - } - - for (int j = 0; j < points; ++j) { - _data[i].push_back (AudioPoint (f)); - if (feof (f)) { - fclose (f); - return; - } + BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) { + channel.push_back (AudioPoint (j)); } + + _data.push_back (channel); } - fclose (f); + _peak = f.number_child<float> ("Peak"); + _peak_time = DCPTime (f.number_child<DCPTime::Type> ("PeakTime")); } void @@ -148,22 +134,20 @@ AudioAnalysis::points (int c) const void AudioAnalysis::write (boost::filesystem::path filename) { - boost::filesystem::path tmp = filename; - tmp.replace_extension (".tmp"); + shared_ptr<xmlpp::Document> doc (new xmlpp::Document); + xmlpp::Element* root = doc->create_root_node ("AudioAnalysis"); - FILE* f = fopen_boost (tmp, "w"); - if (!f) { - throw OpenFileError (tmp); + BOOST_FOREACH (vector<AudioPoint>& i, _data) { + xmlpp::Element* channel = root->add_child ("Channel"); + BOOST_FOREACH (AudioPoint& j, i) { + j.as_xml (channel->add_child ("Point")); + } } - fprintf (f, "%ld\n", _data.size ()); - for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) { - fprintf (f, "%ld\n", i->size ()); - for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) { - j->write (f); - } + if (_peak) { + root->add_child("Peak")->add_child_text (raw_convert<string> (_peak.get ())); + root->add_child("PeakTime")->add_child_text (raw_convert<string> (_peak_time.get().get ())); } - fclose (f); - boost::filesystem::rename (tmp, filename); + doc->write_to_file_formatted (filename.string ()); } diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h index 865d64781..9387ec896 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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,19 +17,16 @@ */ -/** @file src/lib/audio_analysis.h - * @brief AudioAnalysis and AudioPoint classes. - */ - #ifndef DCPOMATIC_AUDIO_ANALYSIS_H #define DCPOMATIC_AUDIO_ANALYSIS_H -#include <boost/filesystem.hpp> #include <vector> +#include <list> +#include <boost/filesystem.hpp> +#include <boost/optional.hpp> +#include <libcxml/cxml.h> +#include "types.h" -/** @class AudioPoint - * @brief A single point of an audio analysis for one portion of one channel. - */ class AudioPoint { public: @@ -40,11 +37,11 @@ public: }; AudioPoint (); - AudioPoint (FILE *); + AudioPoint (cxml::ConstNodePtr node); AudioPoint (AudioPoint const &); AudioPoint& operator= (AudioPoint const &); - void write (FILE *) const; + void as_xml (xmlpp::Element *) const; float& operator[] (int t) { return _data[t]; @@ -54,14 +51,6 @@ private: float _data[COUNT]; }; -/** @class AudioAnalysis - * @brief An analysis of the audio data in a piece of AudioContent. - * - * This is a set of AudioPoints for each channel. The AudioPoints - * each represent some measurement of the audio over a portion of the - * content. For example each AudioPoint may give the RMS level of - * a 1-minute portion of the audio. - */ class AudioAnalysis : public boost::noncopyable { public: @@ -69,15 +58,29 @@ public: AudioAnalysis (boost::filesystem::path); void add_point (int c, AudioPoint const & p); + void set_peak (float peak, DCPTime time) { + _peak = peak; + _peak_time = time; + } AudioPoint get_point (int c, int p) const; int points (int c) const; int channels () const; + boost::optional<float> peak () const { + return _peak; + } + + boost::optional<DCPTime> peak_time () const { + return _peak_time; + } + void write (boost::filesystem::path); private: std::vector<std::vector<AudioPoint> > _data; + boost::optional<float> _peak; + boost::optional<DCPTime> _peak_time; }; #endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 22376e3e3..f6133947a 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -80,10 +80,10 @@ AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate) */ if (accurate) { /* Keep stuffing data into _decoded_audio until we have enough data, or the subclass does not want to give us any more */ - while ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass ()) {} + while ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass (PASS_REASON_AUDIO)) {} decoded_offset = frame - _decoded_audio.frame; } else { - while (_decoded_audio.audio->frames() < length && !pass ()) {} + while (_decoded_audio.audio->frames() < length && !pass (PASS_REASON_AUDIO)) {} /* Use decoded_offset of 0, as we don't really care what frames we return */ } diff --git a/src/lib/content.cc b/src/lib/content.cc index fcc658717..b9e8367e1 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -24,7 +24,6 @@ #include "content.h" #include "util.h" #include "content_factory.h" -#include "ui_signaller.h" #include "exceptions.h" #include "film.h" #include "safe_stringstream.h" @@ -153,9 +152,7 @@ Content::examine (shared_ptr<Job> job) void Content::signal_changed (int p) { - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Changed), shared_from_this (), p, _change_signals_frequent)); - } + emit (boost::bind (boost::ref (Changed), shared_from_this (), p, _change_signals_frequent)); } void diff --git a/src/lib/content.h b/src/lib/content.h index c6cede5fa..2b966110b 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -25,6 +25,7 @@ #define DCPOMATIC_CONTENT_H #include "types.h" +#include "signaller.h" #include "dcpomatic_time.h" #include <libxml++/libxml++.h> #include <libcxml/cxml.h> @@ -53,7 +54,7 @@ public: /** @class Content * @brief A piece of content represented by one or more files on disk. */ -class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable +class Content : public boost::enable_shared_from_this<Content>, public Signaller, public boost::noncopyable { public: Content (boost::shared_ptr<const Film>); diff --git a/src/lib/cross.cc b/src/lib/cross.cc index 9894d885f..285fbe1ce 100644 --- a/src/lib/cross.cc +++ b/src/lib/cross.cc @@ -155,6 +155,10 @@ boost::filesystem::path shared_path () { #ifdef DCPOMATIC_LINUX + char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX"); + if (p) { + return p; + } return boost::filesystem::canonical (LINUX_SHARE_PREFIX); #endif #ifdef DCPOMATIC_WINDOWS diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index 51d16b43c..3bfbd7720 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -55,7 +55,7 @@ DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c) } bool -DCPDecoder::pass () +DCPDecoder::pass (PassReason) { if (_reel == _reels.end () || !_dcp_content->can_be_played ()) { return true; @@ -133,7 +133,13 @@ DCPDecoder::seek (ContentTime t, bool accurate) list<ContentTimePeriod> -DCPDecoder::subtitles_during (ContentTimePeriod, bool) const +DCPDecoder::image_subtitles_during (ContentTimePeriod, bool) const +{ + return list<ContentTimePeriod> (); +} + +list<ContentTimePeriod> +DCPDecoder::text_subtitles_during (ContentTimePeriod, bool) const { /* XXX */ return list<ContentTimePeriod> (); diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index 8afebff57..3a05325c7 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -38,9 +38,11 @@ public: DCPDecoder (boost::shared_ptr<const DCPContent>); private: + bool pass (PassReason); void seek (ContentTime t, bool accurate); - bool pass (); - std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const; ContentTime _next; std::list<boost::shared_ptr<dcp::Reel> > _reels; diff --git a/src/lib/dcp_subtitle_decoder.cc b/src/lib/dcp_subtitle_decoder.cc index e3c06378b..93a122590 100644 --- a/src/lib/dcp_subtitle_decoder.cc +++ b/src/lib/dcp_subtitle_decoder.cc @@ -46,7 +46,7 @@ DCPSubtitleDecoder::seek (ContentTime time, bool accurate) } bool -DCPSubtitleDecoder::pass () +DCPSubtitleDecoder::pass (PassReason) { if (_next == _subtitles.end ()) { return true; @@ -61,7 +61,13 @@ DCPSubtitleDecoder::pass () } list<ContentTimePeriod> -DCPSubtitleDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +DCPSubtitleDecoder::image_subtitles_during (ContentTimePeriod, bool) const +{ + return list<ContentTimePeriod> (); +} + +list<ContentTimePeriod> +DCPSubtitleDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const { /* XXX: inefficient */ diff --git a/src/lib/dcp_subtitle_decoder.h b/src/lib/dcp_subtitle_decoder.h index 2326b31ad..52e400416 100644 --- a/src/lib/dcp_subtitle_decoder.h +++ b/src/lib/dcp_subtitle_decoder.h @@ -28,11 +28,12 @@ public: DCPSubtitleDecoder (boost::shared_ptr<const DCPSubtitleContent>); protected: + bool pass (PassReason); void seek (ContentTime time, bool accurate); - bool pass (); private: - std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const; std::list<dcp::SubtitleString> _subtitles; std::list<dcp::SubtitleString>::const_iterator _next; diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h index dc9b0cd8a..ae8f25199 100644 --- a/src/lib/dcpomatic_time.h +++ b/src/lib/dcpomatic_time.h @@ -193,6 +193,7 @@ class ContentTimePeriod { public: ContentTimePeriod () {} + ContentTimePeriod (ContentTime f, ContentTime t) : from (f) , to (t) diff --git a/src/lib/decoder.h b/src/lib/decoder.h index c1b859865..0703a5426 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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 @@ -41,6 +41,7 @@ public: virtual ~Decoder () {} protected: + /** Seek so that the next pass() will yield the next thing * (video/sound frame, subtitle etc.) at or after the requested * time. Pass accurate = true to try harder to ensure that, at worst, @@ -50,7 +51,14 @@ protected: * it may seek to just the right spot. */ virtual void seek (ContentTime time, bool accurate) = 0; - virtual bool pass () = 0; + + enum PassReason { + PASS_REASON_VIDEO, + PASS_REASON_AUDIO, + PASS_REASON_SUBTITLE + }; + + virtual bool pass (PassReason reason) = 0; }; #endif diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 3a42b169f..a52b53b04 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2015 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 @@ -365,33 +365,22 @@ FFmpegContent::audio_analysis_path () const analyses for each stream. */ - boost::filesystem::path p = film->audio_analysis_dir (); - string name = digest(); + boost::filesystem::path p = AudioContent::audio_analysis_path (); if (audio_stream ()) { - name += "_" + audio_stream()->identifier (); + p = p.string() + "_" + audio_stream()->identifier (); } - p /= name; return p; } list<ContentTimePeriod> FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const { - list<ContentTimePeriod> d; - shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream (); if (!stream) { - return d; - } - - /* XXX: inefficient */ - for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) { - if ((starting && period.contains (i->from)) || (!starting && period.overlaps (*i))) { - d.push_back (*i); - } + return list<ContentTimePeriod> (); } - return d; + return stream->subtitles_during (period, starting); } bool diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 76ba43567..d6edb2bdb 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2015 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 diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index bd01b280b..35e15a331 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -132,7 +132,7 @@ FFmpegDecoder::flush () } bool -FFmpegDecoder::pass () +FFmpegDecoder::pass (PassReason reason) { int r = av_read_frame (_format_context, &_packet); @@ -153,12 +153,13 @@ FFmpegDecoder::pass () } int const si = _packet.stream_index; + shared_ptr<const FFmpegContent> fc = _ffmpeg_content; - if (si == _video_stream && !_ignore_video) { + if (si == _video_stream && !_ignore_video && reason != PASS_REASON_SUBTITLE) { decode_video_packet (); - } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) { + } else if (fc->audio_stream() && fc->audio_stream()->uses_index (_format_context, si) && reason != PASS_REASON_SUBTITLE) { decode_audio_packet (); - } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) { + } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index (_format_context, si)) { decode_subtitle_packet (); } @@ -301,6 +302,7 @@ FFmpegDecoder::seek (ContentTime time, bool accurate) { VideoDecoder::seek (time, accurate); AudioDecoder::seek (time, accurate); + SubtitleDecoder::seek (time, accurate); /* If we are doing an `accurate' seek, we need to use pre-roll, as we don't really know what the seek will give us. @@ -425,35 +427,69 @@ FFmpegDecoder::decode_subtitle_packet () if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) { return; } - - /* Sometimes we get an empty AVSubtitle, which is used by some codecs to - indicate that the previous subtitle should stop. - */ + if (sub.num_rects <= 0) { - image_subtitle (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ()); + /* Sometimes we get an empty AVSubtitle, which is used by some codecs to + indicate that the previous subtitle should stop. We can ignore it here. + */ return; } else if (sub.num_rects > 1) { throw DecodeError (_("multi-part subtitles not yet supported")); } - + /* Subtitle PTS (within the source, not taking into account any of the - source that we may have chopped off for the DCP) + source that we may have chopped off for the DCP). */ - ContentTimePeriod period = subtitle_period (sub) + _pts_offset; - + FFmpegSubtitlePeriod sub_period = subtitle_period (sub); + ContentTimePeriod period; + period.from = sub_period.from + _pts_offset; + if (sub_period.to) { + /* We already know the subtitle period `to' time */ + period.to = sub_period.to.get() + _pts_offset; + } else { + /* We have to look up the `to' time in the stream's records */ + period.to = ffmpeg_content()->subtitle_stream()->find_subtitle_to (sub_period.from); + } + AVSubtitleRect const * rect = sub.rects[0]; - if (rect->type != SUBTITLE_BITMAP) { - /* XXX */ - // throw DecodeError (_("non-bitmap subtitles not yet supported")); - return; + switch (rect->type) { + case SUBTITLE_NONE: + break; + case SUBTITLE_BITMAP: + decode_bitmap_subtitle (rect, period); + break; + case SUBTITLE_TEXT: + cout << "XXX: SUBTITLE_TEXT " << rect->text << "\n"; + break; + case SUBTITLE_ASS: + cout << "XXX: SUBTITLE_ASS " << rect->ass << "\n"; + break; } + + avsubtitle_free (&sub); +} + +list<ContentTimePeriod> +FFmpegDecoder::image_subtitles_during (ContentTimePeriod p, bool starting) const +{ + return _ffmpeg_content->subtitles_during (p, starting); +} +list<ContentTimePeriod> +FFmpegDecoder::text_subtitles_during (ContentTimePeriod, bool) const +{ + return list<ContentTimePeriod> (); +} + +void +FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period) +{ /* Note RGBA is expressed little-endian, so the first byte in the word is R, second G, third B, fourth A. */ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true)); - + /* Start of the first line in the subtitle */ uint8_t* sub_p = rect->pict.data[0]; /* sub_p looks up into a BGRA palette which is here @@ -462,7 +498,7 @@ FFmpegDecoder::decode_subtitle_packet () uint32_t const * palette = (uint32_t *) rect->pict.data[1]; /* Start of the output data */ uint32_t* out_p = (uint32_t *) image->data()[0]; - + for (int y = 0; y < rect->h; ++y) { uint8_t* sub_line_p = sub_p; uint32_t* out_line_p = out_p; @@ -473,25 +509,15 @@ FFmpegDecoder::decode_subtitle_packet () sub_p += rect->pict.linesize[0]; out_p += image->stride()[0] / sizeof (uint32_t); } - + dcp::Size const vs = _ffmpeg_content->video_size (); - - image_subtitle ( - period, - image, - dcpomatic::Rect<double> ( - static_cast<double> (rect->x) / vs.width, - static_cast<double> (rect->y) / vs.height, - static_cast<double> (rect->w) / vs.width, - static_cast<double> (rect->h) / vs.height - ) + dcpomatic::Rect<double> const scaled_rect ( + static_cast<double> (rect->x) / vs.width, + static_cast<double> (rect->y) / vs.height, + static_cast<double> (rect->w) / vs.width, + static_cast<double> (rect->h) / vs.height ); - avsubtitle_free (&sub); + image_subtitle (period, image, scaled_rect); } -list<ContentTimePeriod> -FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const -{ - return _ffmpeg_content->subtitles_during (p, starting); -} diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 0334a30e2..6f027ce1c 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -27,6 +27,7 @@ #include "audio_decoder.h" #include "subtitle_decoder.h" #include "ffmpeg.h" +#include "rect.h" extern "C" { #include <libavcodec/avcodec.h> } @@ -52,8 +53,8 @@ public: private: friend struct ::ffmpeg_pts_offset_test; + bool pass (PassReason reason); void seek (ContentTime time, bool); - bool pass (); void flush (); AVSampleFormat audio_sample_format () const; @@ -63,10 +64,13 @@ private: void decode_audio_packet (); void decode_subtitle_packet (); + void decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period); + void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size); - std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const; boost::shared_ptr<Log> _log; diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index 4409526dc..8afd4c164 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -150,13 +150,18 @@ FFmpegExaminer::subtitle_packet (AVCodecContext* context, shared_ptr<FFmpegSubti int frame_finished; AVSubtitle sub; if (avcodec_decode_subtitle2 (context, &sub, &frame_finished, &_packet) >= 0 && frame_finished) { - ContentTimePeriod const period = subtitle_period (sub); - if (sub.num_rects == 0 && !stream->periods.empty () && stream->periods.back().to > period.from) { - /* Finish the last subtitle */ - stream->periods.back().to = period.from; + FFmpegSubtitlePeriod const period = subtitle_period (sub); + if (sub.num_rects <= 0 && _last_subtitle_start) { + stream->add_subtitle (ContentTimePeriod (_last_subtitle_start.get (), period.from)); + _last_subtitle_start = optional<ContentTime> (); } else if (sub.num_rects == 1) { - stream->periods.push_back (period); + if (period.to) { + stream->add_subtitle (ContentTimePeriod (period.from, period.to.get ())); + } else { + _last_subtitle_start = period.from; + } } + avsubtitle_free (&sub); } } diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index b873222c1..34d4b1e0d 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -55,7 +55,7 @@ private: std::string audio_stream_name (AVStream* s) const; std::string subtitle_stream_name (AVStream* s) const; boost::optional<ContentTime> frame_time (AVStream* s) const; - + std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams; std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; boost::optional<ContentTime> _first_video; @@ -64,4 +64,6 @@ private: */ ContentTime _video_length; bool _need_video_length; + + boost::optional<ContentTime> _last_subtitle_start; }; diff --git a/src/lib/ffmpeg_subtitle_stream.cc b/src/lib/ffmpeg_subtitle_stream.cc index 3d8fd4e83..77a56e330 100644 --- a/src/lib/ffmpeg_subtitle_stream.cc +++ b/src/lib/ffmpeg_subtitle_stream.cc @@ -18,6 +18,13 @@ */ #include "ffmpeg_subtitle_stream.h" +#include "raw_convert.h" +#include <libxml++/libxml++.h> +#include <boost/foreach.hpp> + +using std::string; +using std::map; +using std::list; /** Construct a SubtitleStream from a value returned from to_string(). * @param t String returned from to_string(). @@ -26,11 +33,54 @@ FFmpegSubtitleStream::FFmpegSubtitleStream (cxml::ConstNodePtr node) : FFmpegStream (node) { - + BOOST_FOREACH (cxml::NodePtr i, node->node_children ("Period")) { + add_subtitle ( + ContentTimePeriod ( + ContentTime (node->number_child<ContentTime::Type> ("From")), + ContentTime (node->number_child<ContentTime::Type> ("To")) + ) + ); + } } void FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const { FFmpegStream::as_xml (root); + + for (map<ContentTime, ContentTime>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + xmlpp::Node* node = root->add_child ("Subtitle"); + node->add_child("From")->add_child_text (raw_convert<string> (i->first.get ())); + node->add_child("To")->add_child_text (raw_convert<string> (i->second.get ())); + } +} + +void +FFmpegSubtitleStream::add_subtitle (ContentTimePeriod period) +{ + DCPOMATIC_ASSERT (_subtitles.find (period.from) == _subtitles.end ()); + _subtitles[period.from] = period.to; +} + +list<ContentTimePeriod> +FFmpegSubtitleStream::subtitles_during (ContentTimePeriod period, bool starting) const +{ + list<ContentTimePeriod> d; + + /* XXX: inefficient */ + for (map<ContentTime, ContentTime>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + if ((starting && period.contains (i->first)) || (!starting && period.overlaps (ContentTimePeriod (i->first, i->second)))) { + d.push_back (ContentTimePeriod (i->first, i->second)); + } + } + + return d; +} + +ContentTime +FFmpegSubtitleStream::find_subtitle_to (ContentTime from) const +{ + map<ContentTime, ContentTime>::const_iterator i = _subtitles.find (from); + DCPOMATIC_ASSERT (i != _subtitles.end ()); + return i->second; } diff --git a/src/lib/ffmpeg_subtitle_stream.h b/src/lib/ffmpeg_subtitle_stream.h index b16b825e7..3ed931b8c 100644 --- a/src/lib/ffmpeg_subtitle_stream.h +++ b/src/lib/ffmpeg_subtitle_stream.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2015 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 @@ -31,6 +31,11 @@ public: void as_xml (xmlpp::Node *) const; - std::vector<ContentTimePeriod> periods; + void add_subtitle (ContentTimePeriod period); + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod period, bool starting) const; + ContentTime find_subtitle_to (ContentTime from) const; + +private: + std::map<ContentTime, ContentTime> _subtitles; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index 80755a4cb..35773c797 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -32,7 +32,6 @@ #include "exceptions.h" #include "examine_content_job.h" #include "config.h" -#include "ui_signaller.h" #include "playlist.h" #include "player.h" #include "dcp_content_type.h" @@ -790,9 +789,7 @@ Film::signal_changed (Property p) break; } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Changed), p)); - } + emit (boost::bind (boost::ref (Changed), p)); } void @@ -995,9 +992,7 @@ Film::playlist_content_changed (boost::weak_ptr<Content> c, int p) signal_changed (NAME); } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); - } + emit (boost::bind (boost::ref (ContentChanged), c, p)); } void diff --git a/src/lib/film.h b/src/lib/film.h index 3cd370a0d..f61062be0 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -29,6 +29,7 @@ #include "types.h" #include "isdcf_metadata.h" #include "frame_rate_change.h" +#include "signaller.h" #include "ratio.h" #include <dcp/key.h> #include <dcp/encrypted_kdm.h> @@ -55,7 +56,7 @@ struct isdcf_name_test; * * The content of a Film is held in a Playlist (created and managed by the Film). */ -class Film : public boost::enable_shared_from_this<Film>, public boost::noncopyable +class Film : public boost::enable_shared_from_this<Film>, public Signaller, public boost::noncopyable { public: Film (boost::filesystem::path, bool log = true); diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index 78201fc23..250c8f845 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -43,7 +43,7 @@ ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c) } bool -ImageDecoder::pass () +ImageDecoder::pass (PassReason) { if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) { return true; diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 242f69477..ec90051da 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -34,10 +34,9 @@ public: return _image_content; } - void seek (ContentTime, bool); - private: - bool pass (); + bool pass (PassReason); + void seek (ContentTime, bool); boost::shared_ptr<const ImageContent> _image_content; boost::shared_ptr<ImageProxy> _image; diff --git a/src/lib/job.cc b/src/lib/job.cc index eadafbf73..c4d93ddc1 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -27,7 +27,6 @@ #include "job.h" #include "util.h" #include "cross.h" -#include "ui_signaller.h" #include "exceptions.h" #include "film.h" #include "log.h" @@ -203,8 +202,8 @@ Job::set_state (State s) } } - if (finished && ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Finished))); + if (finished) { + emit (boost::bind (boost::ref (Finished))); } } @@ -239,9 +238,7 @@ Job::set_progress (float p, bool force) _pause_changed.wait (lm2); } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Progress))); - } + emit (boost::bind (boost::ref (Progress))); } /** @return fractional progress of the current sub-job, if known */ @@ -301,9 +298,7 @@ Job::set_progress_unknown () _progress.reset (); lm.unlock (); - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Progress))); - } + emit (boost::bind (boost::ref (Progress))); } /** @return Human-readable status of this job */ diff --git a/src/lib/job.h b/src/lib/job.h index 7c6707880..8fe87747c 100644 --- a/src/lib/job.h +++ b/src/lib/job.h @@ -24,6 +24,7 @@ #ifndef DCPOMATIC_JOB_H #define DCPOMATIC_JOB_H +#include "signaller.h" #include <boost/thread/mutex.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/signals2.hpp> @@ -35,7 +36,7 @@ class Film; /** @class Job * @brief A parent class to represent long-running tasks which are run in their own thread. */ -class Job : public boost::enable_shared_from_this<Job>, public boost::noncopyable +class Job : public boost::enable_shared_from_this<Job>, public Signaller, public boost::noncopyable { public: Job (boost::shared_ptr<const Film>); diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc index 2b727b0aa..b5b64a77e 100644 --- a/src/lib/job_manager.cc +++ b/src/lib/job_manager.cc @@ -26,7 +26,6 @@ #include "job_manager.h" #include "job.h" #include "cross.h" -#include "ui_signaller.h" using std::string; using std::list; @@ -64,9 +63,7 @@ JobManager::add (shared_ptr<Job> j) _jobs.push_back (j); } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (JobAdded), weak_ptr<Job> (j))); - } + emit (boost::bind (boost::ref (JobAdded), weak_ptr<Job> (j))); return j; } @@ -138,9 +135,7 @@ JobManager::scheduler () if (active_jobs != _last_active_jobs) { _last_active_jobs = active_jobs; - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (ActiveJobsChanged), active_jobs)); - } + emit (boost::bind (boost::ref (ActiveJobsChanged), active_jobs)); } dcpomatic_sleep (1); diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h index 9d8620cbb..b946c1a98 100644 --- a/src/lib/job_manager.h +++ b/src/lib/job_manager.h @@ -21,6 +21,7 @@ * @brief A simple scheduler for jobs. */ +#include "signaller.h" #include <boost/thread/mutex.hpp> #include <boost/thread.hpp> #include <boost/signals2.hpp> @@ -32,7 +33,7 @@ extern void wait_for_jobs (); /** @class JobManager * @brief A simple scheduler for jobs. */ -class JobManager : public boost::noncopyable +class JobManager : public Signaller, public boost::noncopyable { public: diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc index 3f88bbd9d..8949736f8 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -253,6 +253,8 @@ email_kdms ( if (!Config::instance()->kdm_bcc().empty ()) { quickmail_add_bcc (mail, Config::instance()->kdm_bcc().c_str ()); } + + quickmail_add_header (mail, "Content-Type: text/plain; charset=UTF-8"); string body = Config::instance()->kdm_email().c_str(); boost::algorithm::replace_all (body, "$CPL_NAME", film->dcp_name ()); diff --git a/src/lib/player.cc b/src/lib/player.cc index 436ae3fe8..640253c6d 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -376,9 +376,11 @@ Player::get_video (DCPTime time, bool accurate) list<PositionImage> c = transform_image_subtitles (ps.image); copy (c.begin(), c.end(), back_inserter (sub_images)); - /* Text subtitles (rendered to images) */ - sub_images.push_back (render_subtitles (ps.text, _video_container_size)); - + /* Text subtitles (rendered to an image) */ + if (!ps.text.empty ()) { + sub_images.push_back (render_subtitles (ps.text, _video_container_size)); + } + if (!sub_images.empty ()) { for (list<shared_ptr<PlayerVideo> >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) { (*i)->set_subtitle (merge (sub_images)); diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc index bc89fd3f8..9620eacbf 100644 --- a/src/lib/render_subtitles.cc +++ b/src/lib/render_subtitles.cc @@ -50,10 +50,6 @@ calculate_position (dcp::VAlign v_align, double v_position, int target_height, i PositionImage render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target) { - if (subtitles.empty ()) { - return PositionImage (); - } - /* Estimate height that the subtitle image needs to be */ optional<int> top; optional<int> bottom; diff --git a/src/lib/server_finder.cc b/src/lib/server_finder.cc index f347132e4..726437ea5 100644 --- a/src/lib/server_finder.cc +++ b/src/lib/server_finder.cc @@ -22,7 +22,6 @@ #include "util.h" #include "config.h" #include "cross.h" -#include "ui_signaller.h" #include "dcpomatic_socket.h" #include "raw_convert.h" #include <libcxml/cxml.h> @@ -173,7 +172,7 @@ ServerFinder::handle_accept (boost::system::error_code ec, shared_ptr<Socket> so boost::mutex::scoped_lock lm (_mutex); _servers.push_back (sd); } - ui_signaller->emit (boost::bind (boost::ref (ServerFound), sd)); + emit (boost::bind (boost::ref (ServerFound), sd)); } start_accept (); diff --git a/src/lib/server_finder.h b/src/lib/server_finder.h index 3fab6864a..dc62f998d 100644 --- a/src/lib/server_finder.h +++ b/src/lib/server_finder.h @@ -18,9 +18,10 @@ */ #include "server.h" +#include "signaller.h" #include <boost/signals2.hpp> -class ServerFinder : public ExceptionStore +class ServerFinder : public Signaller, public ExceptionStore { public: boost::signals2::connection connect (boost::function<void (ServerDescription)>); diff --git a/src/lib/ui_signaller.cc b/src/lib/signal_manager.cc index 4cb34da51..7c2b3e11a 100644 --- a/src/lib/ui_signaller.cc +++ b/src/lib/signal_manager.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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,8 +17,7 @@ */ -#include "ui_signaller.h" - -/** Global UISignaller instance */ -UISignaller* ui_signaller = 0; +#include "signal_manager.h" +/** Global SignalManager instance */ +SignalManager* signal_manager = 0; diff --git a/src/lib/ui_signaller.h b/src/lib/signal_manager.h index ee4d230d4..ae4306e30 100644 --- a/src/lib/ui_signaller.h +++ b/src/lib/signal_manager.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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,43 +17,28 @@ */ -#ifndef DCPOMATIC_UI_SIGNALLER_H -#define DCPOMATIC_UI_SIGNALLER_H +#ifndef DCPOMATIC_SIGNAL_MANAGER_H +#define DCPOMATIC_SIGNAL_MANAGER_H #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/thread.hpp> +class Signaller; + /** A class to allow signals to be emitted from non-UI threads and handled * by a UI thread. */ -class UISignaller : public boost::noncopyable +class SignalManager : public boost::noncopyable { public: - /** Create a UISignaller. Must be called from the UI thread */ - UISignaller () + /** Create a SignalManager. Must be called from the UI thread */ + SignalManager () : _work (_service) { _ui_thread = boost::this_thread::get_id (); } - /** Emit a signal from any thread whose handlers will be called in the UI - * thread. Use something like: - * - * ui_signaller->emit (boost::bind (boost::ref (SomeSignal), parameter)); - */ - template <typename T> - void emit (T f) { - if (boost::this_thread::get_id() == _ui_thread) { - /* already in the UI thread */ - f (); - } else { - /* non-UI thread; post to the service and wake up the UI */ - _service.post (f); - wake_ui (); - } - } - /* Do something next time the UI is idle */ template <typename T> void when_idle (T f) { @@ -68,11 +53,30 @@ public: /** This should wake the UI and make it call ui_idle() */ virtual void wake_ui () { - /* This is only a sensible implementation when there is no GUI... */ + /* This is only a sensible implementation when there is no GUI */ ui_idle (); } private: + /** Emit a signal from any thread whose handlers will be called in the UI + * thread. Use something like: + * + * ui_signaller->emit (boost::bind (boost::ref (SomeSignal), parameter)); + */ + template <typename T> + void emit (T f) { + if (boost::this_thread::get_id() == _ui_thread) { + /* already in the UI thread */ + f (); + } else { + /* non-UI thread; post to the service and wake up the UI */ + _service.post (f); + wake_ui (); + } + } + + friend class Signaller; + /** A io_service which is used as the conduit for messages */ boost::asio::io_service _service; /** Object required to keep io_service from stopping when it has nothing to do */ @@ -81,6 +85,6 @@ private: boost::thread::id _ui_thread; }; -extern UISignaller* ui_signaller; +extern SignalManager* signal_manager; #endif diff --git a/src/lib/signaller.h b/src/lib/signaller.h new file mode 100644 index 000000000..4ef9b38b3 --- /dev/null +++ b/src/lib/signaller.h @@ -0,0 +1,131 @@ +/* + Copyright (C) 2015 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_SIGNALLER_H +#define DCPOMATIC_SIGNALLER_H + +#include "signal_manager.h" +#include <boost/thread/mutex.hpp> +#include <boost/signals2.hpp> + +class WrapperBase +{ +public: + WrapperBase () + : _valid (true) + , _finished (false) + {} + + virtual ~WrapperBase () {} + + /* Can be called from any thread */ + void invalidate () + { + boost::mutex::scoped_lock lm (_mutex); + _valid = false; + } + + bool finished () const { + boost::mutex::scoped_lock lm (_mutex); + return _finished; + } + +protected: + /* Protect _valid and _finished */ + mutable boost::mutex _mutex; + bool _valid; + bool _finished; +}; + +/** Helper class to manage lifetime of signals, specifically to address + * the problem where an object containing a signal is deleted before + * its signal is emitted. + */ +template <class T> +class Wrapper : public WrapperBase +{ +public: + Wrapper (T signal) + : _signal (signal) + { + + } + + /* Called by the UI thread only */ + void signal () + { + boost::mutex::scoped_lock lm (_mutex); + if (_valid) { + _signal (); + } + _finished = true; + } + +private: + T _signal; +}; + +/** Parent for any class which needs to raise cross-thread signals (from non-UI + * to UI). Subclasses should call, e.g. emit (boost::bind (boost::ref (MySignal), foo, bar)); + */ +class Signaller +{ +public: + /* Can be called from any thread */ + virtual ~Signaller () { + boost::mutex::scoped_lock lm (_mutex); + for (std::list<WrapperBase*>::iterator i = _wrappers.begin(); i != _wrappers.end(); ++i) { + (*i)->invalidate (); + } + } + + /* Can be called from any thread */ + template <class T> + void emit (T signal) + { + Wrapper<T>* w = new Wrapper<T> (signal); + if (signal_manager) { + signal_manager->emit (boost::bind (&Wrapper<T>::signal, w)); + } + + boost::mutex::scoped_lock lm (_mutex); + + /* Clean up finished Wrappers */ + std::list<WrapperBase*>::iterator i = _wrappers.begin (); + while (i != _wrappers.end ()) { + std::list<WrapperBase*>::iterator tmp = i; + ++tmp; + if ((*i)->finished ()) { + delete *i; + _wrappers.erase (i); + } + i = tmp; + } + + /* Add the new one */ + _wrappers.push_back (w); + } + +private: + /* Protect _wrappers */ + boost::mutex _mutex; + std::list<WrapperBase*> _wrappers; +}; + +#endif diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index 602014d58..09059a8b0 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -65,7 +65,7 @@ SndfileDecoder::~SndfileDecoder () } bool -SndfileDecoder::pass () +SndfileDecoder::pass (PassReason) { if (_remaining == 0) { return true; diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 5ebe1da7b..68c8633a0 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -30,14 +30,13 @@ public: SndfileDecoder (boost::shared_ptr<const SndfileContent> c); ~SndfileDecoder (); - void seek (ContentTime, bool); - int audio_channels () const; ContentTime audio_length () const; int audio_frame_rate () const; private: - bool pass (); + bool pass (PassReason); + void seek (ContentTime, bool); boost::shared_ptr<const SndfileContent> _sndfile_content; SNDFILE* _sndfile; diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc index 552a96b8f..6ed2e5254 100644 --- a/src/lib/subrip_decoder.cc +++ b/src/lib/subrip_decoder.cc @@ -48,7 +48,7 @@ SubRipDecoder::seek (ContentTime time, bool accurate) } bool -SubRipDecoder::pass () +SubRipDecoder::pass (PassReason) { if (_next >= _subtitles.size ()) { return true; @@ -85,7 +85,13 @@ SubRipDecoder::pass () } list<ContentTimePeriod> -SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +SubRipDecoder::image_subtitles_during (ContentTimePeriod, bool) const +{ + return list<ContentTimePeriod> (); +} + +list<ContentTimePeriod> +SubRipDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const { /* XXX: inefficient */ diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h index ad9d04e40..264ca8899 100644 --- a/src/lib/subrip_decoder.h +++ b/src/lib/subrip_decoder.h @@ -32,10 +32,11 @@ public: protected: void seek (ContentTime time, bool accurate); - bool pass (); + bool pass (PassReason); private: - std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const; + std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const; size_t _next; }; diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index 9b2aa8ab0..2efe9afb6 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2015 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 @@ -33,7 +33,8 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const SubtitleContent> c) } /** Called by subclasses when an image subtitle is ready. - * Image may be 0 to say that there is no current subtitle. + * @param period Period of the subtitle. + * @param image Subtitle image. * @param rect Area expressed as a fraction of the video frame that this subtitle * is for (e.g. a width of 0.5 means the width of the subtitle is half the width * of the video frame) @@ -50,12 +51,11 @@ SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s) _decoded_text_subtitles.push_back (ContentTextSubtitle (s)); } +/** @param sp Full periods of subtitles that are showing or starting during the specified period */ template <class T> list<T> -SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool starting) +SubtitleDecoder::get (list<T> const & subs, list<ContentTimePeriod> const & sp, ContentTimePeriod period, bool starting) { - /* Get the full periods of the subtitles that are showing or starting during the specified period */ - list<ContentTimePeriod> sp = subtitles_during (period, starting); if (sp.empty ()) { /* Nothing in this period */ return list<T> (); @@ -70,7 +70,7 @@ SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool start * (a) give us what we want, or * (b) hit the end of the decoder. */ - while (!pass() && (subs.empty() || (subs.back().period().to < sp.back().to))) {} + while (!pass(PASS_REASON_SUBTITLE) && (subs.empty() || (subs.back().period().to < sp.back().to))) {} /* Now look for what we wanted in the data we have collected */ /* XXX: inefficient */ @@ -82,19 +82,36 @@ SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool start } } + /* Discard anything in _decoded_image_subtitles that is outside 5 seconds either side of period */ + + list<ContentImageSubtitle>::iterator i = _decoded_image_subtitles.begin(); + while (i != _decoded_image_subtitles.end()) { + list<ContentImageSubtitle>::iterator tmp = i; + ++tmp; + + if ( + i->period().to < (period.from - ContentTime::from_seconds (5)) || + i->period().from > (period.to + ContentTime::from_seconds (5)) + ) { + _decoded_image_subtitles.erase (i); + } + + i = tmp; + } + return out; } list<ContentTextSubtitle> SubtitleDecoder::get_text_subtitles (ContentTimePeriod period, bool starting) { - return get<ContentTextSubtitle> (_decoded_text_subtitles, period, starting); + return get<ContentTextSubtitle> (_decoded_text_subtitles, text_subtitles_during (period, starting), period, starting); } list<ContentImageSubtitle> SubtitleDecoder::get_image_subtitles (ContentTimePeriod period, bool starting) { - return get<ContentImageSubtitle> (_decoded_image_subtitles, period, starting); + return get<ContentImageSubtitle> (_decoded_image_subtitles, image_subtitles_during (period, starting), period, starting); } void diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index d7faaa014..8ba74404f 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -49,12 +49,13 @@ protected: private: template <class T> - std::list<T> get (std::list<T> const & subs, ContentTimePeriod period, bool starting); + std::list<T> get (std::list<T> const & subs, std::list<ContentTimePeriod> const & sp, ContentTimePeriod period, bool starting); /** @param starting true if we want only subtitles that start during the period, otherwise * we want subtitles that overlap the period. */ - virtual std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod period, bool starting) const = 0; + virtual std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod period, bool starting) const = 0; + virtual std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod period, bool starting) const = 0; boost::shared_ptr<const SubtitleContent> _subtitle_content; }; diff --git a/src/lib/types.h b/src/lib/types.h index f3877d0d5..e7017a295 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -22,6 +22,7 @@ #include "dcpomatic_time.h" #include "position.h" +#include "rect.h" #include <dcp/util.h> #include <boost/shared_ptr.hpp> #include <vector> diff --git a/src/lib/update.cc b/src/lib/update.cc index a05df8ef3..f433ff991 100644 --- a/src/lib/update.cc +++ b/src/lib/update.cc @@ -19,7 +19,6 @@ #include "update.h" #include "version.h" -#include "ui_signaller.h" #include "safe_stringstream.h" #include "config.h" #include "util.h" @@ -168,7 +167,7 @@ UpdateChecker::set_state (State s) _emits++; } - ui_signaller->emit (boost::bind (boost::ref (StateChanged))); + emit (boost::bind (boost::ref (StateChanged))); } UpdateChecker * diff --git a/src/lib/update.h b/src/lib/update.h index 5bb9e9501..461217a37 100644 --- a/src/lib/update.h +++ b/src/lib/update.h @@ -21,6 +21,7 @@ * @brief UpdateChecker class. */ +#include "signaller.h" #include <curl/curl.h> #include <boost/signals2.hpp> #include <boost/thread/mutex.hpp> @@ -30,7 +31,7 @@ struct update_checker_test; /** Class to check for the existance of an update for DCP-o-matic on a remote server */ -class UpdateChecker : public boost::noncopyable +class UpdateChecker : public Signaller, public boost::noncopyable { public: UpdateChecker (); diff --git a/src/lib/util.cc b/src/lib/util.cc index bffbe90d4..99d9ba2c4 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -564,18 +564,21 @@ wrapped_av_malloc (size_t s) } return p; } - -ContentTimePeriod + +FFmpegSubtitlePeriod subtitle_period (AVSubtitle const & sub) { ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE); - ContentTimePeriod period ( + if (sub.end_display_time == static_cast<uint32_t> (-1)) { + /* End time is not known */ + return FFmpegSubtitlePeriod (packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3)); + } + + return FFmpegSubtitlePeriod ( packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3), packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3) ); - - return period; } map<string, string> @@ -667,3 +670,4 @@ write_frame_info (FILE* file, int frame, Eyes eyes, dcp::FrameInfo info) fwrite (&info.size, sizeof (info.size), 1, file); fwrite (info.hash.c_str(), 1, info.hash.size(), file); } + diff --git a/src/lib/util.h b/src/lib/util.h index c1f7a78c7..44bd7dced 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -74,7 +74,24 @@ extern int dcp_audio_frame_rate (int); extern int stride_round_up (int, int const *, int); extern int round_to (float n, int r); extern void* wrapped_av_malloc (size_t); -extern ContentTimePeriod subtitle_period (AVSubtitle const &); + +class FFmpegSubtitlePeriod +{ +public: + FFmpegSubtitlePeriod (ContentTime f) + : from (f) + {} + + FFmpegSubtitlePeriod (ContentTime f, ContentTime t) + : from (f) + , to (t) + {} + + ContentTime from; + boost::optional<ContentTime> to; +}; + +extern FFmpegSubtitlePeriod subtitle_period (AVSubtitle const &); extern void set_backtrace_file (boost::filesystem::path); extern dcp::FrameInfo read_frame_info (FILE* file, int frame, Eyes eyes); extern void write_frame_info (FILE* file, int frame, Eyes eyes, dcp::FrameInfo info); diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index b7cf1641b..31dc3cdc2 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -96,7 +96,7 @@ VideoDecoder::get_video (VideoFrame frame, bool accurate) break; } - if (pass ()) { + if (pass (PASS_REASON_VIDEO)) { /* The decoder has nothing more for us */ break; } @@ -113,7 +113,7 @@ VideoDecoder::get_video (VideoFrame frame, bool accurate) dec = decoded_video (frame); } else { /* Any frame will do: use the first one that comes out of pass() */ - while (_decoded_video.empty() && !pass ()) {} + while (_decoded_video.empty() && !pass (PASS_REASON_VIDEO)) {} if (!_decoded_video.empty ()) { dec.push_back (_decoded_video.front ()); } @@ -237,7 +237,7 @@ VideoDecoder::video (shared_ptr<const ImageProxy> image, VideoFrame frame) if (_ignore_video) { return; } - + /* We may receive the same frame index twice for 3D, and we need to know when that happens. */ diff --git a/src/lib/writer.cc b/src/lib/writer.cc index 31c265e2f..1a11a482b 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -366,21 +366,24 @@ try } DCPOMATIC_ASSERT (i != _queue.rend()); - QueueItem qi = *i; - ++_pushed_to_disk; - lock.unlock (); + /* i is valid here, even though we don't hold a lock on the mutex, + since list iterators are unaffected by insertion and only this + thread could erase the last item in the list. + */ + LOG_GENERAL ( "Writer full (awaiting %1 [last eye was %2]); pushes %3 to disk", _last_written_frame + 1, - _last_written_eyes, qi.frame + _last_written_eyes, i->frame ); - qi.encoded->write (_film, qi.frame, qi.eyes); + i->encoded->write (_film, i->frame, i->eyes); + lock.lock (); - qi.encoded.reset (); + i->encoded.reset (); --_queued_full_in_memory; } diff --git a/src/lib/wscript b/src/lib/wscript index 24aa7c134..5956c73d6 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -1,3 +1,21 @@ +# +# Copyright (C) 2012-2015 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. +# + import os import i18n @@ -91,7 +109,7 @@ sources = """ transcode_job.cc transcoder.cc types.cc - ui_signaller.cc + signal_manager.cc update.cc upmixer_a.cc util.cc @@ -102,7 +120,7 @@ sources = """ """ def build(bld): - if bld.env.BUILD_STATIC: + if bld.env.STATIC_DCPOMATIC: obj = bld(features = 'cxx cxxstlib') else: obj = bld(features = 'cxx cxxshlib') @@ -123,7 +141,7 @@ def build(bld): if bld.env.TARGET_WINDOWS: obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI MSWSOCK BOOST_LOCALE' - if bld.env.BUILD_STATIC: + if bld.env.STATIC_DCPOMATIC: obj.uselib += ' XMLPP' obj.target = 'dcpomatic2' diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index e59220785..904e39fda 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -21,7 +21,7 @@ #include "lib/config.h" #include "lib/util.h" #include "lib/version.h" -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" #include "lib/log.h" #include "lib/job_manager.h" #include "lib/transcode_job.h" @@ -39,7 +39,7 @@ #include "wx/wx_util.h" #include "wx/new_film_dialog.h" #include "wx/properties_dialog.h" -#include "wx/wx_ui_signaller.h" +#include "wx/wx_signal_manager.h" #include "wx/about_dialog.h" #include "wx/kdm_dialog.h" #include "wx/servers_list_dialog.h" @@ -841,7 +841,7 @@ private: } } - ui_signaller = new wxUISignaller (this); + signal_manager = new wxSignalManager (this); Bind (wxEVT_IDLE, boost::bind (&App::idle, this)); Bind (wxEVT_TIMER, boost::bind (&App::check, this)); @@ -909,7 +909,7 @@ private: void idle () { - ui_signaller->ui_idle (); + signal_manager->ui_idle (); } void check () diff --git a/src/tools/dcpomatic_batch.cc b/src/tools/dcpomatic_batch.cc index da8a61414..ae2f3a2c5 100644 --- a/src/tools/dcpomatic_batch.cc +++ b/src/tools/dcpomatic_batch.cc @@ -29,7 +29,7 @@ #include "lib/job_manager.h" #include "wx/wx_util.h" #include "wx/about_dialog.h" -#include "wx/wx_ui_signaller.h" +#include "wx/wx_signal_manager.h" #include "wx/job_manager_view.h" using std::exception; @@ -225,7 +225,7 @@ class App : public wxApp f->Maximize (); f->Show (); - ui_signaller = new wxUISignaller (this); + signal_manager = new wxSignalManager (this); this->Bind (wxEVT_IDLE, boost::bind (&App::idle, this)); shared_ptr<Film> film; @@ -244,7 +244,7 @@ class App : public wxApp void idle () { - ui_signaller->ui_idle (); + signal_manager->ui_idle (); } void OnInitCmdLine (wxCmdLineParser& parser) diff --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc index 4facdd4d1..0307cac9c 100644 --- a/src/tools/dcpomatic_cli.cc +++ b/src/tools/dcpomatic_cli.cc @@ -30,7 +30,7 @@ #include "lib/cross.h" #include "lib/config.h" #include "lib/log.h" -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" #include "lib/server_finder.h" #include "lib/json_server.h" @@ -119,7 +119,7 @@ main (int argc, char* argv[]) film_dir = argv[optind]; dcpomatic_setup (); - ui_signaller = new UISignaller (); + signal_manager = new SignalManager (); if (no_remote) { ServerFinder::instance()->disable (); diff --git a/src/tools/dcpomatic_create.cc b/src/tools/dcpomatic_create.cc index 304f4f697..d121eb0cc 100644 --- a/src/tools/dcpomatic_create.cc +++ b/src/tools/dcpomatic_create.cc @@ -28,7 +28,7 @@ #include "lib/util.h" #include "lib/content_factory.h" #include "lib/job_manager.h" -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" #include "lib/job.h" #include "lib/dcp_content_type.h" #include "lib/ratio.h" @@ -59,11 +59,11 @@ help (string n) << " -o, --output <dir> output directory\n"; } -class SimpleUISignaller : public UISignaller +class SimpleSignalManager : public SignalManager { public: /* Do nothing in this method so that UI events happen in our thread - when we call UISignaller::ui_idle(). + when we call SignalManager::ui_idle(). */ void wake_ui () {} }; @@ -161,7 +161,7 @@ main (int argc, char* argv[]) exit (EXIT_FAILURE); } - ui_signaller = new SimpleUISignaller (); + signal_manager = new SimpleSignalManager (); try { shared_ptr<Film> film (new Film (output, false)); @@ -184,7 +184,7 @@ main (int argc, char* argv[]) JobManager* jm = JobManager::instance (); while (jm->work_to_do ()) {} - while (ui_signaller->ui_idle() > 0) {} + while (signal_manager->ui_idle() > 0) {} ContentList content = film->content (); for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { diff --git a/src/tools/wscript b/src/tools/wscript index 175cebc96..ffd77c6cb 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -1,3 +1,21 @@ +# +# Copyright (C) 2012-2015 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. +# + import os import glob from waflib import Logs @@ -9,9 +27,8 @@ def configure(conf): conf.env.append_value('LINKFLAGS', ['-mconsole']) def build(bld): - - uselib = 'BOOST_THREAD BOOST_DATETIME BOOST_FILESYSTEM OPENJPEG DCP CXML SNDFILE ZIP XMLPP SSH ' - uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS SUB CURL GLIB CAIROMM PANGOMM MAGICK ' + uselib = 'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP XMLSEC CXML XMLPP AVFORMAT AVFILTER AVCODEC ' + uselib += 'AVUTIL SWSCALE POSTPROC CURL BOOST_FILESYSTEM SSH WXWIDGETS ZIP CAIROMM PANGOMM SUB MAGICK SNDFILE ' if bld.env.TARGET_WINDOWS: uselib += 'WINSOCK2' diff --git a/src/wscript b/src/wscript index f3a6a8c7b..8ec39963f 100644 --- a/src/wscript +++ b/src/wscript @@ -1,3 +1,21 @@ +# +# Copyright (C) 2012-2015 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. +# + def configure(conf): conf.recurse('tools') if not conf.env.DISABLE_GUI: diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc index 1d41fc185..fcae9c30f 100644 --- a/src/wx/audio_dialog.cc +++ b/src/wx/audio_dialog.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + Copyright (C) 2013-2015 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 @@ -29,31 +29,43 @@ using boost::shared_ptr; using boost::bind; using boost::optional; -AudioDialog::AudioDialog (wxWindow* parent) +AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film) : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) + , _film (film) , _plot (0) { + wxFont subheading_font (*wxNORMAL_FONT); + subheading_font.SetWeight (wxFONTWEIGHT_BOLD); + wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL); + + wxBoxSizer* left = new wxBoxSizer (wxVERTICAL); _plot = new AudioPlot (this); - sizer->Add (_plot, 1, wxALL | wxEXPAND, 12); + left->Add (_plot, 1, wxALL | wxEXPAND, 12); + _peak_time = new wxStaticText (this, wxID_ANY, wxT ("")); + left->Add (_peak_time, 0, wxALL, 12); + + sizer->Add (left, 1, wxALL, 12); - wxBoxSizer* side = new wxBoxSizer (wxVERTICAL); + wxBoxSizer* right = new wxBoxSizer (wxVERTICAL); { wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels")); - side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16); + m->SetFont (subheading_font); + right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 16); } for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) { _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i))); - side->Add (_channel_checkbox[i], 1, wxEXPAND | wxALL, 3); + right->Add (_channel_checkbox[i], 0, wxEXPAND | wxALL, 3); _channel_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::channel_clicked, this, _1)); } { wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type")); - side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16); + m->SetFont (subheading_font); + right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16); } wxString const types[] = { @@ -63,20 +75,21 @@ AudioDialog::AudioDialog (wxWindow* parent) for (int i = 0; i < AudioPoint::COUNT; ++i) { _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]); - side->Add (_type_checkbox[i], 1, wxEXPAND | wxALL, 3); + right->Add (_type_checkbox[i], 0, wxEXPAND | wxALL, 3); _type_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::type_clicked, this, _1)); } { wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing")); - side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16); + m->SetFont (subheading_font); + right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16); } _smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing); _smoothing->Bind (wxEVT_SCROLL_THUMBTRACK, boost::bind (&AudioDialog::smoothing_changed, this)); - side->Add (_smoothing, 1, wxEXPAND); + right->Add (_smoothing, 0, wxEXPAND); - sizer->Add (side, 0, wxALL, 12); + sizer->Add (right, 0, wxALL, 12); SetSizer (sizer); sizer->Layout (); @@ -107,14 +120,21 @@ AudioDialog::try_to_load_analysis () if (!boost::filesystem::exists (_content->audio_analysis_path())) { _plot->set_analysis (shared_ptr<AudioAnalysis> ()); + _analysis.reset (); _analysis_finished_connection = _content->analyse_audio (bind (&AudioDialog::analysis_finished, this)); return; } + + try { + _analysis.reset (new AudioAnalysis (_content->audio_analysis_path ())); + } catch (xmlpp::exception& e) { + /* Probably an old-style analysis file: recreate it */ + _analysis_finished_connection = _content->analyse_audio (bind (&AudioDialog::analysis_finished, this)); + return; + } - shared_ptr<AudioAnalysis> a; - - a.reset (new AudioAnalysis (_content->audio_analysis_path ())); - _plot->set_analysis (a); + _plot->set_analysis (_analysis); + setup_peak_time (); /* Set up some defaults if no check boxes are checked */ @@ -139,6 +159,8 @@ AudioDialog::try_to_load_analysis () _plot->set_type_visible (i, true); } } + + Refresh (); } void @@ -173,6 +195,7 @@ AudioDialog::content_changed (int p) { if (p == AudioContentProperty::AUDIO_GAIN) { _plot->set_gain (_content->audio_gain ()); + setup_peak_time (); } else if (p == AudioContentProperty::AUDIO_MAPPING) { try_to_load_analysis (); } @@ -196,3 +219,32 @@ AudioDialog::smoothing_changed () { _plot->set_smoothing (_smoothing->GetValue ()); } + +void +AudioDialog::setup_peak_time () +{ + if (!_analysis || !_analysis->peak ()) { + return; + } + + shared_ptr<Film> film = _film.lock (); + if (!film) { + return; + } + + float peak_dB = 20 * log10 (_analysis->peak().get()) + _content->audio_gain(); + + _peak_time->SetLabel ( + wxString::Format ( + _("Peak is %.2fdB at %s"), + peak_dB, + time_to_timecode (_analysis->peak_time().get(), film->video_frame_rate ()).data () + ) + ); + + if (peak_dB > -3) { + _peak_time->SetForegroundColour (wxColour (255, 0, 0)); + } else { + _peak_time->SetForegroundColour (wxColour (0, 0, 0)); + } +} diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h index b27785292..aef8ea944 100644 --- a/src/wx/audio_dialog.h +++ b/src/wx/audio_dialog.h @@ -29,7 +29,7 @@ class Film; class AudioDialog : public wxDialog { public: - AudioDialog (wxWindow *); + AudioDialog (wxWindow *, boost::shared_ptr<Film> film); void set_content (boost::shared_ptr<AudioContent>); @@ -40,9 +40,13 @@ private: void smoothing_changed (); void try_to_load_analysis (); void analysis_finished (); + void setup_peak_time (); boost::shared_ptr<AudioContent> _content; + boost::shared_ptr<AudioAnalysis> _analysis; + boost::weak_ptr<Film> _film; AudioPlot* _plot; + wxStaticText* _peak_time; wxCheckBox* _channel_checkbox[MAX_DCP_AUDIO_CHANNELS]; wxCheckBox* _type_checkbox[AudioPoint::COUNT]; wxSlider* _smoothing; diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc index 2a41aeb2d..4d783ca9d 100644 --- a/src/wx/audio_panel.cc +++ b/src/wx/audio_panel.cc @@ -217,7 +217,7 @@ AudioPanel::show_clicked () return; } - _audio_dialog = new AudioDialog (this); + _audio_dialog = new AudioDialog (this, _parent->film ()); _audio_dialog->Show (); _audio_dialog->set_content (ac.front ()); } diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc index 82872ad84..ccfe5711c 100644 --- a/src/wx/dcp_panel.cc +++ b/src/wx/dcp_panel.cc @@ -344,6 +344,7 @@ DCPPanel::film_changed (int p) break; case Film::INTEROP: checked_set (_standard, _film->interop() ? 1 : 0); + setup_dcp_name (); break; default: break; @@ -636,7 +637,7 @@ DCPPanel::make_audio_panel () int r = 0; add_label_to_grid_bag_sizer (grid, panel, _("Channels"), true, wxGBPosition (r, 0)); _audio_channels = new wxChoice (panel, wxID_ANY); - for (int i = 2; i <= 16; i += 2) { + for (int i = 2; i <= 12; i += 2) { _audio_channels->Append (wxString::Format ("%d", i)); } grid->Add (_audio_channels, wxGBPosition (r, 1)); diff --git a/src/wx/dolby_certificate_dialog.cc b/src/wx/dolby_certificate_dialog.cc index 5e094844d..ad43f6479 100644 --- a/src/wx/dolby_certificate_dialog.cc +++ b/src/wx/dolby_certificate_dialog.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2014-2015 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,7 +21,7 @@ #include <curl/curl.h> #include "lib/compose.hpp" #include "lib/internet.h" -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" #include "dolby_certificate_dialog.h" #include "wx_util.h" @@ -80,7 +80,7 @@ DolbyCertificateDialog::setup_countries () /* See DoremiCertificateDialog for discussion about this daft delay */ wxMilliSleep (200); #endif - ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_setup_countries, this)); + signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_setup_countries, this)); } void @@ -103,7 +103,7 @@ DolbyCertificateDialog::country_selected () #ifdef DCPOMATIC_OSX wxMilliSleep (200); #endif - ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_country_selected, this)); + signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_country_selected, this)); } void @@ -126,7 +126,7 @@ DolbyCertificateDialog::cinema_selected () #ifdef DCPOMATIC_OSX wxMilliSleep (200); #endif - ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_cinema_selected, this)); + signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_cinema_selected, this)); } void @@ -154,13 +154,14 @@ DolbyCertificateDialog::serial_selected () void DolbyCertificateDialog::download () { + downloaded (false); _message->SetLabel (_("Downloading certificate")); #ifdef DCPOMATIC_OSX wxMilliSleep (200); #endif - ui_signaller->when_idle (boost::bind (&DolbyCertificateDialog::finish_download, this)); + signal_manager->when_idle (boost::bind (&DolbyCertificateDialog::finish_download, this)); } void @@ -189,5 +190,6 @@ DolbyCertificateDialog::finish_download () _message->SetLabel (std_to_wx (error.get ())); } else { _message->SetLabel (_("Certificate downloaded")); + downloaded (true); } } diff --git a/src/wx/doremi_certificate_dialog.cc b/src/wx/doremi_certificate_dialog.cc index 4b5d58b37..578a7a72d 100644 --- a/src/wx/doremi_certificate_dialog.cc +++ b/src/wx/doremi_certificate_dialog.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2014-2015 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,7 +21,7 @@ #include <zip.h> #include "lib/compose.hpp" #include "lib/util.h" -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" #include "lib/internet.h" #include "doremi_certificate_dialog.h" #include "wx_util.h" @@ -51,6 +51,7 @@ DoremiCertificateDialog::download () return; } + downloaded (false); _message->SetLabel (_("Downloading certificate")); #ifdef DCPOMATIC_OSX @@ -58,7 +59,7 @@ DoremiCertificateDialog::download () wxMilliSleep (200); #endif - ui_signaller->when_idle (boost::bind (&DoremiCertificateDialog::finish_download, this, serial)); + signal_manager->when_idle (boost::bind (&DoremiCertificateDialog::finish_download, this, serial)); } void @@ -101,6 +102,7 @@ DoremiCertificateDialog::finish_download (string serial) error_dialog (this, std_to_wx (error.get ())); } else { _message->SetLabel (_("Certificate downloaded")); + downloaded (true); } } diff --git a/src/wx/download_certificate_dialog.cc b/src/wx/download_certificate_dialog.cc index a8a712334..a0c41fd76 100644 --- a/src/wx/download_certificate_dialog.cc +++ b/src/wx/download_certificate_dialog.cc @@ -17,9 +17,9 @@ */ -#include <boost/bind.hpp> -#include "download_certificate_dialog.h" #include "wx_util.h" +#include "download_certificate_dialog.h" +#include <boost/bind.hpp> using boost::function; @@ -50,4 +50,16 @@ DownloadCertificateDialog::add_common_widgets () _download->Enable (false); layout (); + + wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this)); + ok->Enable (false); } + +void +DownloadCertificateDialog::downloaded (bool done) +{ + wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this)); + ok->Enable (done); +} + + diff --git a/src/wx/download_certificate_dialog.h b/src/wx/download_certificate_dialog.h index 804c0c762..40e11de45 100644 --- a/src/wx/download_certificate_dialog.h +++ b/src/wx/download_certificate_dialog.h @@ -32,6 +32,7 @@ public: protected: void add_common_widgets (); + void downloaded (bool done); boost::function<void (boost::filesystem::path)> _load; wxStaticText* _message; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 311ec734c..0938d52a4 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -354,16 +354,7 @@ FilmViewer::set_position_text () double const fps = _film->video_frame_rate (); /* Count frame number from 1 ... not sure if this is the best idea */ _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (_position.seconds() * fps)) + 1)); - - double w = _position.seconds (); - int const h = (w / 3600); - w -= h * 3600; - int const m = (w / 60); - w -= m * 60; - int const s = floor (w); - w -= s; - int const f = rint (w * fps); - _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f)); + _timecode->SetLabel (time_to_timecode (_position, fps)); } void diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc index 3e6301482..8f4f8622d 100644 --- a/src/wx/kdm_dialog.cc +++ b/src/wx/kdm_dialog.cc @@ -273,7 +273,7 @@ KDMDialog::setup_sensitivity () _edit_screen->Enable (ss); _remove_screen->Enable (ss); - wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK)); + wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this)); if (ok) { ok->Enable ((selected_cinemas().size() > 0 || selected_screens().size() > 0) && sd); } diff --git a/src/wx/key_dialog.cc b/src/wx/key_dialog.cc index d7c809609..70229c7a9 100644 --- a/src/wx/key_dialog.cc +++ b/src/wx/key_dialog.cc @@ -62,7 +62,7 @@ KeyDialog::key () const void KeyDialog::key_changed () { - wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK)); + wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this)); ok->Enable (_key->GetValue().Length() == 32); } diff --git a/src/wx/wscript b/src/wx/wscript index 7b5904b4b..370f59c62 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -1,4 +1,24 @@ +# +# Copyright (C) 2012-2015 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. +# + import os +import subprocess +import shlex import glob from waflib import Logs import i18n @@ -60,38 +80,44 @@ sources = """ update_dialog.cc video_panel.cc wx_util.cc - wx_ui_signaller.cc + wx_signal_manager.cc """ def configure(conf): - args = '--cppflags --cxxflags' - if not conf.env.BUILD_STATIC: - args += ' --libs std,richtext' - - conf.check_cfg(msg='Checking for wxWidgets', package='', path=conf.options.wx_config, args=args, - uselib_store='WXWIDGETS', mandatory=True) + conf.check_cfg(msg='Checking for wxWidgets', + package='', + path='wx-config', + args='--cppflags --cxxflags --libs std,richtext', + uselib_store='WXWIDGETS', + mandatory=True) - if conf.env.BUILD_STATIC: + if conf.options.static_wxwidgets: # wx-config returns its static libraries as full paths, without -l prefixes, which confuses - # check_cfg(), so just hard-code it all. - conf.env.STLIB_WXWIDGETS = ['wx_gtk2u_richtext-3.0', 'wx_gtk2u_xrc-3.0', 'wx_gtk2u_qa-3.0', 'wx_baseu_net-3.0', 'wx_gtk2u_html-3.0', - 'wx_gtk2u_adv-3.0', 'wx_gtk2u_core-3.0', 'wx_baseu_xml-3.0', 'wx_baseu-3.0'] - conf.env.LIB_WXWIDGETS = ['tiff', 'SM', 'dl', 'jpeg', 'png', 'X11', 'expat'] - if conf.env.TARGET_DEBIAN and conf.env.DEBIAN_UNSTABLE: - conf.env.LIB_WXWIDGETS.append('Xext') - conf.env.LIB_WXWIDGETS.append('X11') - - if conf.env.TARGET_CENTOS_7: - conf.env.LIB_WXWIDGETS.append('Xxf86vm') + # check_cfg(). It puts the static libraries into LINKFLAGS_WXWIDGETS, so fish them out. + stlibs = [] + new_linkflags = [] + stlib_paths = [] + for f in conf.env.LINKFLAGS_WXWIDGETS: + if f.startswith('/'): + d = os.path.dirname(f) + if not d in stlib_paths: + stlib_paths.append(d) + stlibs.append(os.path.basename(f)[3:-2]) + else: + new_linkflags.append(f) + + conf.env.STLIB_WXWIDGETS = stlibs + conf.env.LINKFLAGS_WXWIDGETS = new_linkflags + conf.env.STLIBPATH_WXWIDGETS = stlib_paths conf.in_msg = 1 - wx_version = conf.check_cfg(package='', path=conf.options.wx_config, args='--version').strip() + wx_version = conf.check_cfg(package='', path='wx-config', args='--version').strip() conf.im_msg = 0 if not wx_version.startswith('3.0.'): conf.fatal('wxwidgets version 3.0.x is required; %s found' % wx_version) def build(bld): - if bld.env.BUILD_STATIC: + if bld.env.STATIC_DCPOMATIC: obj = bld(features = 'cxx cxxstlib') else: obj = bld(features = 'cxx cxxshlib') diff --git a/src/wx/wx_ui_signaller.cc b/src/wx/wx_signal_manager.cc index 8fc6670d6..3d8b9992a 100644 --- a/src/wx/wx_ui_signaller.cc +++ b/src/wx/wx_signal_manager.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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,16 +18,16 @@ */ #include <wx/wx.h> -#include "wx_ui_signaller.h" +#include "wx_signal_manager.h" -wxUISignaller::wxUISignaller (wxEvtHandler* h) +wxSignalManager::wxSignalManager (wxEvtHandler* h) : _handler (h) { } void -wxUISignaller::wake_ui () +wxSignalManager::wake_ui () { wxCommandEvent event (-1, -1); _handler->AddPendingEvent (event); diff --git a/src/wx/wx_ui_signaller.h b/src/wx/wx_signal_manager.h index 63f2049cd..ad18e6880 100644 --- a/src/wx/wx_ui_signaller.h +++ b/src/wx/wx_signal_manager.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 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,18 @@ */ -#include "lib/ui_signaller.h" +#include "lib/signal_manager.h" class wxEvtHandler; -/** @class wxUISignaller - * @brief UISignaller for the wxWidgets event loop +/** @class wxSignalManager + * @brief SignalManager for the wxWidgets event loop */ -class wxUISignaller : public UISignaller +class wxSignalManager : public SignalManager { public: - wxUISignaller (wxEvtHandler *); + wxSignalManager (wxEvtHandler *); void wake_ui (); private: diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index e9c07c91f..9c13cfbcb 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -330,3 +330,17 @@ context_translation (wxString s) return t; } + +wxString +time_to_timecode (DCPTime t, float fps) +{ + double w = t.seconds (); + int const h = (w / 3600); + w -= h * 3600; + int const m = (w / 60); + w -= m * 60; + int const s = floor (w); + w -= s; + int const f = rint (w * fps); + return wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f); +} diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h index dfa0fca5e..f2ab2d8c5 100644 --- a/src/wx/wx_util.h +++ b/src/wx/wx_util.h @@ -24,6 +24,7 @@ #ifndef DCPOMATIC_WX_UTIL_H #define DCPOMATIC_WX_UTIL_H +#include "lib/dcpomatic_time.h" #include <wx/wx.h> #include <wx/gbsizer.h> #include <boost/function.hpp> @@ -65,6 +66,7 @@ extern wxString std_to_wx (std::string); extern void dcpomatic_setup_i18n (); extern wxString context_translation (wxString); extern std::string string_client_data (wxClientData* o); +extern wxString time_to_timecode (DCPTime t, float fps); extern void checked_set (wxFilePickerCtrl* widget, std::string value); extern void checked_set (wxSpinCtrl* widget, int value); |
