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/lib | |
| parent | ada329f77032590bae1e18d05a87f94c82e14a55 (diff) | |
| parent | b433d33bcbfccf29171fe24c55fdee550a8c36aa (diff) | |
Merge branch '2.0' of git.carlh.net:git/dcpomatic into 2.0
Diffstat (limited to 'src/lib')
52 files changed, 557 insertions, 260 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' |
