summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2015-05-20 16:29:25 +0100
committerCarl Hetherington <cth@carlh.net>2015-05-20 16:29:25 +0100
commit6f0a590bc3266f21ba577116219bd019e891d480 (patch)
tree273721d852a9b90b541c8fcefd10d209e6ef2ce2 /src/lib
parentada329f77032590bae1e18d05a87f94c82e14a55 (diff)
parentb433d33bcbfccf29171fe24c55fdee550a8c36aa (diff)
Merge branch '2.0' of git.carlh.net:git/dcpomatic into 2.0
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/analyse_audio_job.cc14
-rw-r--r--src/lib/analyse_audio_job.h3
-rw-r--r--src/lib/audio_analysis.cc84
-rw-r--r--src/lib/audio_analysis.h41
-rw-r--r--src/lib/audio_decoder.cc4
-rw-r--r--src/lib/content.cc5
-rw-r--r--src/lib/content.h3
-rw-r--r--src/lib/cross.cc4
-rw-r--r--src/lib/dcp_decoder.cc10
-rw-r--r--src/lib/dcp_decoder.h6
-rw-r--r--src/lib/dcp_subtitle_decoder.cc10
-rw-r--r--src/lib/dcp_subtitle_decoder.h5
-rw-r--r--src/lib/dcpomatic_time.h1
-rw-r--r--src/lib/decoder.h12
-rw-r--r--src/lib/ffmpeg_content.cc21
-rw-r--r--src/lib/ffmpeg_content.h2
-rw-r--r--src/lib/ffmpeg_decoder.cc98
-rw-r--r--src/lib/ffmpeg_decoder.h8
-rw-r--r--src/lib/ffmpeg_examiner.cc15
-rw-r--r--src/lib/ffmpeg_examiner.h4
-rw-r--r--src/lib/ffmpeg_subtitle_stream.cc52
-rw-r--r--src/lib/ffmpeg_subtitle_stream.h9
-rw-r--r--src/lib/film.cc9
-rw-r--r--src/lib/film.h3
-rw-r--r--src/lib/image_decoder.cc2
-rw-r--r--src/lib/image_decoder.h5
-rw-r--r--src/lib/job.cc13
-rw-r--r--src/lib/job.h3
-rw-r--r--src/lib/job_manager.cc9
-rw-r--r--src/lib/job_manager.h3
-rw-r--r--src/lib/kdm.cc2
-rw-r--r--src/lib/player.cc8
-rw-r--r--src/lib/render_subtitles.cc4
-rw-r--r--src/lib/server_finder.cc3
-rw-r--r--src/lib/server_finder.h3
-rw-r--r--src/lib/signal_manager.cc (renamed from src/lib/ui_signaller.cc)9
-rw-r--r--src/lib/signal_manager.h (renamed from src/lib/ui_signaller.h)54
-rw-r--r--src/lib/signaller.h131
-rw-r--r--src/lib/sndfile_decoder.cc2
-rw-r--r--src/lib/sndfile_decoder.h5
-rw-r--r--src/lib/subrip_decoder.cc10
-rw-r--r--src/lib/subrip_decoder.h5
-rw-r--r--src/lib/subtitle_decoder.cc33
-rw-r--r--src/lib/subtitle_decoder.h5
-rw-r--r--src/lib/types.h1
-rw-r--r--src/lib/update.cc3
-rw-r--r--src/lib/update.h3
-rw-r--r--src/lib/util.cc14
-rw-r--r--src/lib/util.h19
-rw-r--r--src/lib/video_decoder.cc6
-rw-r--r--src/lib/writer.cc15
-rw-r--r--src/lib/wscript24
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'