+2014-03-07 Carl Hetherington <cth@carlh.net>
+
+ * Add subtitle view.
+
+ 2014-03-17 Carl Hetherington <cth@carlh.net>
+
+ * Improve appearance of config dialog on OS X.
+
+ 2014-03-15 Carl Hetherington <cth@carlh.net>
+
+ * Improve appearance of new film and KDM dialogs on OS X.
+
+ * Fix KDM dialog to predictably set up its initial range to
+ a week from now.
+
+ * Remove support for FFmpeg post-processing filters as they apparently
+ do not support > 8bpp. I don't think they are worth the pain of
+ quantizing and then telling the user what has happened.
+
+ 2014-03-12 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.66.1 released.
+
+ 2014-03-12 Carl Hetherington <cth@carlh.net>
+
+ * Hopefully fix i18n on OS X (#324).
+
+ 2014-03-10 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.66.0 released.
+
+ 2014-03-09 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.65.2 released.
+
+ 2014-03-09 Carl Hetherington <cth@carlh.net>
+
+ * Restore old behaviour of "no-stretch" mode with crop.
+
+ * Fix display of no-scale display mode in the player.
+
2014-03-08 Carl Hetherington <cth@carlh.net>
+ * Version 1.65.1 released.
+
+ 2014-03-08 Carl Hetherington <cth@carlh.net>
+
+ * Fix incorrect audio analyses on multiple-stream content.
+
* Support for unsigned 8-bit audio (hmm!).
2014-03-06 Carl Hetherington <cth@carlh.net>
int const AudioContentProperty::AUDIO_DELAY = 204;
int const AudioContentProperty::AUDIO_MAPPING = 205;
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
: Content (f, s)
, _audio_gain (0)
, _audio_delay (Config::instance()->default_audio_delay ())
return boost::filesystem::path ();
}
- return film->audio_analysis_path (dynamic_pointer_cast<const AudioContent> (shared_from_this ()));
+ boost::filesystem::path p = film->audio_analysis_dir ();
+ p /= digest ();
+ return p;
}
string
AudioContent::technical_summary () const
{
- return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
+ return String::compose (
+ "audio: channels %1, length %2, raw rate %3, out rate %4",
+ audio_channels(),
+ audio_length().seconds(),
+ content_audio_frame_rate(),
+ output_audio_frame_rate()
+ );
}
public:
typedef int64_t Frame;
- AudioContent (boost::shared_ptr<const Film>, Time);
+ AudioContent (boost::shared_ptr<const Film>, DCPTime);
AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
std::string technical_summary () const;
virtual int audio_channels () const = 0;
- virtual AudioContent::Frame audio_length () const = 0;
+ virtual ContentTime audio_length () const = 0;
virtual int content_audio_frame_rate () const = 0;
virtual int output_audio_frame_rate () const = 0;
virtual AudioMapping audio_mapping () const = 0;
virtual void set_audio_mapping (AudioMapping) = 0;
+ virtual boost::filesystem::path audio_analysis_path () const;
boost::signals2::connection analyse_audio (boost::function<void()>);
- boost::filesystem::path audio_analysis_path () const;
void set_audio_gain (float);
void set_audio_delay (int);
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/metadata.h>
+#include <dcp/metadata.h>
#include "dci_metadata.h"
#include "colour_conversion.h"
#include "server.h"
void set_use_any_servers (bool u) {
_use_any_servers = u;
+ write ();
}
bool use_any_servers () const {
/** @param s New list of servers */
void set_servers (std::vector<std::string> s) {
_servers = s;
+ write ();
}
/** @return Host names / IP addresses of J2K encoding servers that should definitely be used */
return _default_dcp_content_type;
}
- libdcp::XMLMetadata dcp_metadata () const {
+ dcp::XMLMetadata dcp_metadata () const {
return _dcp_metadata;
}
/** @param n New number of local encoding threads */
void set_num_local_encoding_threads (int n) {
_num_local_encoding_threads = n;
+ write ();
}
void set_default_directory (boost::filesystem::path d) {
_default_directory = d;
+ write ();
}
/** @param p New server port */
void set_server_port_base (int p) {
_server_port_base = p;
+ write ();
}
/** @param i IP address of a TMS that we can copy DCPs to */
void set_tms_ip (std::string i) {
_tms_ip = i;
+ write ();
}
/** @param p Path on a TMS that we should write DCPs to */
void set_tms_path (std::string p) {
_tms_path = p;
+ write ();
}
/** @param u User name to log into the TMS with */
void set_tms_user (std::string u) {
_tms_user = u;
+ write ();
}
/** @param p Password to log into the TMS with */
void set_tms_password (std::string p) {
_tms_password = p;
+ write ();
}
void add_cinema (boost::shared_ptr<Cinema> c) {
void set_allowed_dcp_frame_rates (std::list<int> const & r) {
_allowed_dcp_frame_rates = r;
+ write ();
}
void set_default_dci_metadata (DCIMetadata d) {
_default_dci_metadata = d;
+ write ();
}
void set_language (std::string l) {
_language = l;
+ write ();
}
void unset_language () {
_language = boost::none;
+ write ();
}
void set_default_still_length (int s) {
_default_still_length = s;
+ write ();
}
void set_default_container (Ratio const * c) {
_default_container = c;
+ write ();
}
void set_default_dcp_content_type (DCPContentType const * t) {
_default_dcp_content_type = t;
+ write ();
}
- void set_dcp_metadata (libdcp::XMLMetadata m) {
+ void set_dcp_metadata (dcp::XMLMetadata m) {
_dcp_metadata = m;
+ write ();
}
void set_default_j2k_bandwidth (int b) {
_default_j2k_bandwidth = b;
+ write ();
}
void set_default_audio_delay (int d) {
_default_audio_delay = d;
+ write ();
}
void set_colour_conversions (std::vector<PresetColourConversion> const & c) {
_colour_conversions = c;
+ write ();
}
void set_mail_server (std::string s) {
_mail_server = s;
+ write ();
}
void set_mail_user (std::string u) {
_mail_user = u;
+ write ();
}
void set_mail_password (std::string p) {
_mail_password = p;
+ write ();
}
void set_kdm_from (std::string f) {
_kdm_from = f;
+ write ();
}
void set_kdm_email (std::string e) {
_kdm_email = e;
+ write ();
}
void set_check_for_updates (bool c) {
_check_for_updates = c;
+ write ();
}
void set_check_for_test_updates (bool c) {
_check_for_test_updates = c;
+ write ();
}
void write () const;
int _default_still_length;
Ratio const * _default_container;
DCPContentType const * _default_dcp_content_type;
- libdcp::XMLMetadata _dcp_metadata;
+ dcp::XMLMetadata _dcp_metadata;
int _default_j2k_bandwidth;
int _default_audio_delay;
std::vector<PresetColourConversion> _colour_conversions;
}
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
: _film (f)
, _position (p)
, _trim_start (0)
_paths.push_back ((*i)->content ());
}
_digest = node->string_child ("Digest");
- _position = node->number_child<Time> ("Position");
- _trim_start = node->number_child<Time> ("TrimStart");
- _trim_end = node->number_child<Time> ("TrimEnd");
+ _position = DCPTime (node->number_child<double> ("Position"));
+ _trim_start = DCPTime (node->number_child<double> ("TrimStart"));
+ _trim_end = DCPTime (node->number_child<double> ("TrimEnd"));
}
Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
, _change_signals_frequent (false)
{
for (size_t i = 0; i < c.size(); ++i) {
- if (i > 0 && c[i]->trim_start ()) {
+ if (i > 0 && c[i]->trim_start() > DCPTime()) {
throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
}
- if (i < (c.size() - 1) && c[i]->trim_end ()) {
+ if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) {
throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
}
node->add_child("Path")->add_child_text (i->string ());
}
node->add_child("Digest")->add_child_text (_digest);
- node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
- node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
- node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end));
+ node->add_child("Position")->add_child_text (lexical_cast<string> (_position.get ()));
+ node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start.get ()));
+ node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end.get ()));
}
void
}
void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
{
{
boost::mutex::scoped_lock lm (_mutex);
}
void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
}
void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
xmlpp::Document doc;
xmlpp::Node* node = doc.create_root_node ("Content");
as_xml (node);
- return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::current_state_version);
+
+ /* notes is unused here (we assume) */
+ list<string> notes;
+ return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::current_state_version, notes);
}
string
Content::technical_summary () const
{
- return String::compose ("%1 %2 %3", path_summary(), digest(), position());
+ return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
}
-Time
+DCPTime
Content::length_after_trim () const
{
return full_length() - trim_start() - trim_end();
}
-/** @param t A time relative to the start of this content (not the position).
- * @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
- return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
/** @return string which includes everything about how this content affects
* its playlist.
*/
stringstream s;
s << Content::digest()
- << "_" << position()
- << "_" << trim_start()
- << "_" << trim_end();
+ << "_" << position().get()
+ << "_" << trim_start().get()
+ << "_" << trim_end().get();
return s.str ();
}
#include "ffmpeg_content.h"
#include "image_content.h"
#include "sndfile_content.h"
+#include "subrip_content.h"
#include "util.h"
using std::string;
+ using std::list;
using boost::shared_ptr;
shared_ptr<Content>
- content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version)
+ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, list<string>& notes)
{
string const type = node->string_child ("Type");
boost::shared_ptr<Content> content;
if (type == "FFmpeg") {
- content.reset (new FFmpegContent (film, node, version));
+ content.reset (new FFmpegContent (film, node, version, notes));
} else if (type == "Image") {
content.reset (new ImageContent (film, node, version));
} else if (type == "Sndfile") {
content.reset (new SndfileContent (film, node, version));
+ } else if (type == "SubRip") {
+ content.reset (new SubRipContent (film, node, version));
}
return content;
content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
{
shared_ptr<Content> content;
+
+ string ext = path.extension().string ();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
if (valid_image_file (path)) {
content.reset (new ImageContent (film, path));
} else if (SndfileContent::valid_file (path)) {
content.reset (new SndfileContent (film, path));
+ } else if (ext == ".srt") {
+ content.reset (new SubRipContent (film, path));
} else {
content.reset (new FFmpegContent (film, path));
}
}
- FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
+ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
: Content (f, node)
, VideoContent (f, node, version)
, AudioContent (f, node)
c = node->node_children ("Filter");
for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
- _filters.push_back (Filter::from_id ((*i)->content ()));
+ Filter const * f = Filter::from_id ((*i)->content ());
+ if (f) {
+ _filters.push_back (f);
+ } else {
+ notes.push_back (String::compose (_("DCP-o-matic no longer supports the `%1' filter, so it has been turned off."), (*i)->content()));
+ }
}
_first_video = node->optional_number_child<double> ("FirstVideo");
}
if (_first_video) {
- node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+ node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get().get()));
}
}
Content::examine (job);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
+ take_from_video_examiner (examiner);
+
+ ContentTime video_length = examiner->video_length ();
- VideoContent::Frame video_length = 0;
- video_length = examiner->video_length ();
- film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ())));
{
boost::mutex::scoped_lock lm (_mutex);
_first_video = examiner->first_video ();
}
- take_from_video_examiner (examiner);
-
signal_changed (ContentProperty::LENGTH);
signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
ss = _subtitle_stream->technical_summary ();
}
- pair<string, string> filt = Filter::ffmpeg_strings (_filters);
+ string filt = Filter::ffmpeg_string (_filters);
return Content::technical_summary() + " - "
+ VideoContent::technical_summary() + " - "
+ AudioContent::technical_summary() + " - "
+ String::compose (
- "ffmpeg: audio %1, subtitle %2, filters %3 %4", as, ss, filt.first, filt.second
+ "ffmpeg: audio %1, subtitle %2, filters %3", as, ss, filt
);
}
string
FFmpegContent::information () const
{
- if (video_length() == 0 || video_frame_rate() == 0) {
+ if (video_length() == ContentTime (0) || video_frame_rate() == 0) {
return "";
}
stringstream s;
- s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length().frames (video_frame_rate()), video_frame_rate()) << "\n";
s << VideoContent::information ();
return s.str ();
signal_changed (FFmpegContentProperty::AUDIO_STREAM);
}
-AudioContent::Frame
+ContentTime
FFmpegContent::audio_length () const
{
- int const cafr = content_audio_frame_rate ();
- int const vfr = video_frame_rate ();
- VideoContent::Frame const vl = video_length ();
-
- boost::mutex::scoped_lock lm (_mutex);
- if (!_audio_stream) {
- return 0;
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ if (!_audio_stream) {
+ return ContentTime ();
+ }
}
-
- return video_frames_to_audio_frames (vl, cafr, vfr);
+
+ return video_length();
}
int
/* Resample to a DCI-approved sample rate */
double t = dcp_audio_frame_rate (content_audio_frame_rate ());
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
/* Compensate if the DCP is being run at a different frame rate
to the source; that is, if the video is run such that it will
look different in the DCP compared to the source (slower or faster).
- skip/repeat doesn't come into effect here.
*/
if (frc.change_speed) {
- t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+ t /= frc.speed_up;
}
return rint (t);
root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
if (first_audio) {
- root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get ()));
+ root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get().get()));
}
mapping.as_xml (root->add_child("Mapping"));
}
FFmpegStream::as_xml (root);
}
-Time
+DCPTime
FFmpegContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
- return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
+ return DCPTime (video_length(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
}
AudioMapping
return s.str ();
}
+ boost::filesystem::path
+ FFmpegContent::audio_analysis_path () const
+ {
+ shared_ptr<const Film> film = _film.lock ();
+ if (!film) {
+ return boost::filesystem::path ();
+ }
+
+ /* We need to include the stream ID in this path so that we get different
+ analyses for each stream.
+ */
+
+ boost::filesystem::path p = film->audio_analysis_dir ();
+ string name = digest ();
+ if (audio_stream ()) {
+ name += "_" + audio_stream()->identifier ();
+ }
+ p /= name;
+ return p;
+ }
int frame_rate;
int channels;
AudioMapping mapping;
- boost::optional<double> first_audio;
+ boost::optional<ContentTime> first_audio;
private:
friend class ffmpeg_pts_offset_test;
{
public:
FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version);
+ FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version, std::list<std::string> &);
FFmpegContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
boost::shared_ptr<FFmpegContent> shared_from_this () {
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
/* AudioContent */
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ ContentTime audio_length () const;
int content_audio_frame_rate () const;
int output_audio_frame_rate () const;
AudioMapping audio_mapping () const;
void set_audio_mapping (AudioMapping);
+ boost::filesystem::path audio_analysis_path () const;
void set_filters (std::vector<Filter const *> const &);
void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
- boost::optional<double> first_video () const {
+ boost::optional<ContentTime> first_video () const {
boost::mutex::scoped_lock lm (_mutex);
return _first_video;
}
boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
boost::shared_ptr<FFmpegAudioStream> _audio_stream;
- boost::optional<double> _first_video;
+ boost::optional<ContentTime> _first_video;
/** Video filters that should be used when generating DCPs */
std::vector<Filter const *> _filters;
};
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
-#include "film.h"
#include "filter.h"
#include "exceptions.h"
#include "image.h"
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-using libdcp::Size;
+using dcp::Size;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
- : Decoder (f)
- , VideoDecoder (f, c)
- , AudioDecoder (f, c)
- , SubtitleDecoder (f)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log, bool video, bool audio, bool subtitles)
+ : VideoDecoder (c)
+ , AudioDecoder (c)
, FFmpeg (c)
+ , _log (log)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
, _decode_video (video)
, _decode_audio (audio)
+ , _decode_subtitles (subtitles)
, _pts_offset (0)
- , _just_sought (false)
{
setup_subtitle ();
Then we remove big initial gaps in PTS and we allow our
insertion of black frames to work.
- We will do:
- audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
- video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+ We will do pts_to_use = pts_from_ffmpeg + pts_offset;
*/
bool const have_video = video && c->first_video();
- bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+ bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio;
/* First, make one of them start at 0 */
/* Now adjust both so that the video pts starts on a frame */
if (have_video && have_audio) {
- double first_video = c->first_video().get() + _pts_offset;
- double const old_first_video = first_video;
-
- /* Round the first video up to a frame boundary */
- if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
- first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
- }
-
- _pts_offset += first_video - old_first_video;
+ ContentTime first_video = c->first_video().get() + _pts_offset;
+ ContentTime const old_first_video = first_video;
+ _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
}
}
if (_ffmpeg_content->audio_stream() && _decode_audio) {
decode_audio_packet ();
+ AudioDecoder::flush ();
}
-
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length ();
- _audio_position = _ffmpeg_content->audio_length ();
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
/* Maybe we should fail here, but for now we'll just finish off instead */
char buf[256];
av_strerror (r, buf, sizeof(buf));
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
+ _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
}
flush ();
- return;
+ return true;
}
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
int const si = _packet.stream_index;
if (si == _video_stream && _decode_video) {
decode_video_packet ();
} else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
decode_audio_packet ();
- } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
+ } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && _decode_subtitles) {
decode_subtitle_packet ();
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+ optional<ContentTime> last_video;
+ optional<ContentTime> last_audio;
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
- */
- int initial = frame;
+ while (!finished (last_video, last_audio, frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ /* We should flush our decoders here, possibly yielding a few more frames,
+ but the consequence of having to do that is too hideous to contemplate.
+ Instead we give up and say that you can't seek too close to the end
+ of a file.
+ */
+ return frames_read;
+ }
+
+ ++frames_read;
+
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
- if (accurate) {
- initial -= 5;
+ if (_packet.stream_index == _video_stream) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int finished = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+ }
+
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
+ AVPacket copy_packet = _packet;
+ while (copy_packet.size > 0) {
+
+ int finished;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- if (initial < 0) {
- initial = 0;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+ return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+ ContentTime const u = t - _pts_offset;
+ int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ if (_ffmpeg_content->audio_stream ()) {
+ s = min (
+ s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+ );
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+ /* Ridiculous empirical hack */
+ s--;
+ if (s < 0) {
+ s = 0;
+ }
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ av_seek_frame (_format_context, _video_stream, s, 0);
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+}
- /* This !accurate is piling hack upon hack; setting _just_sought to true
- even with accurate == true defeats our attempt to align the start
- of the video and audio. Here we disable that defeat when accurate == true
- i.e. when we are making a DCP rather than just previewing one.
- Ewww. This should be gone in 2.0.
- */
- if (!accurate) {
- _just_sought = true;
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ Decoder::seek (time, accurate);
+ if (_decode_audio) {
+ AudioDecoder::seek (time, accurate);
}
- _video_position = frame;
-
- if (frame == 0 || !accurate) {
- /* We're already there, or we're as close as we need to be */
- return;
+ /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+ a number plucked from the air) earlier than we want to end up. The loop below
+ will hopefully then step through to where we want to be.
+ */
+
+ ContentTime pre_roll = accurate ? ContentTime::from_seconds (0.2) : ContentTime (0);
+ ContentTime initial_seek = time - pre_roll;
+ if (initial_seek < ContentTime (0)) {
+ initial_seek = ContentTime (0);
}
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
+ /* Initial seek time in the video stream's timebase */
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
- _video_position = rint (
- (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate()
- );
+ seek_and_flush (initial_seek);
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ if (!accurate) {
+ /* That'll do */
+ return;
+ }
+
+ int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
+
+ seek_and_flush (initial_seek);
+ if (N > 0) {
+ minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
}
}
int frame_finished;
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet);
+
if (decode_result < 0) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+ _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
return;
}
if (frame_finished) {
-
- if (_audio_position == 0) {
- /* Where we are in the source, in seconds */
- double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
- if (pts > 0) {
- /* Emit some silence */
- shared_ptr<AudioBuffers> silence (
- new AudioBuffers (
- _ffmpeg_content->audio_channels(),
- pts * _ffmpeg_content->content_audio_frame_rate()
- )
- );
-
- silence->make_silent ();
- audio (silence, _audio_position);
- }
- }
+ ContentTime const ct = ContentTime::from_seconds (
+ av_frame_get_best_effort_timestamp (_frame) *
+ av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+ + _pts_offset;
int const data_size = av_samples_get_buffer_size (
0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
-
- audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+
+ audio (deinterleave_audio (_frame->data, data_size), ct);
}
copy_packet.data += decode_result;
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+ while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
++i;
}
if (i == _filter_graphs.end ()) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
_filter_graphs.push_back (graph);
-
- film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+ _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
} else {
graph = *i;
}
list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame);
- string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
-
for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) {
shared_ptr<Image> image = i->first;
- if (!post_process.empty ()) {
- image = image->post_process (post_process, true);
- }
if (i->second != AV_NOPTS_VALUE) {
-
- double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
- if (_just_sought) {
- /* We just did a seek, so disable any attempts to correct for where we
- are / should be.
- */
- _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
- _just_sought = false;
- }
-
- double const next = _video_position / _ffmpeg_content->video_frame_rate();
- double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
- double delta = pts - next;
-
- while (delta > one_frame) {
- /* This PTS is more than one frame forward in time of where we think we should be; emit
- a black frame.
- */
-
- /* XXX: I think this should be a copy of the last frame... */
- boost::shared_ptr<Image> black (
- new Image (
- static_cast<AVPixelFormat> (_frame->format),
- libdcp::Size (video_codec_context()->width, video_codec_context()->height),
- true
- )
- );
-
- black->make_black ();
- video (image, false, _video_position);
- delta -= one_frame;
- }
-
- if (delta > -one_frame) {
- /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (image, false, _video_position);
- }
-
+ video (image, false, ContentTime::from_seconds (i->second * av_q2d (_format_context->streams[_video_stream]->time_base)) + _pts_offset);
} else {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log ("Dropping frame without PTS");
+ _log->log ("Dropping frame without PTS");
}
}
}
}
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
-
void
FFmpegDecoder::decode_subtitle_packet ()
{
indicate that the previous subtitle should stop.
*/
if (sub.num_rects <= 0) {
- subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+ image_subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), ContentTime (), ContentTime ());
return;
} else if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
}
- /* Subtitle PTS in seconds (within the source, not taking into account any of the
+ /* Subtitle PTS (within the source, not taking into account any of the
source that we may have chopped off for the DCP)
*/
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
-
+ ContentTime packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
+
/* hence start time for this sub */
- Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
- Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+ ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3);
+ ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3);
AVSubtitleRect const * rect = sub.rects[0];
/* 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, libdcp::Size (rect->w, rect->h), true));
+ 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];
out_p += image->stride()[0] / sizeof (uint32_t);
}
- libdcp::Size const vs = _ffmpeg_content->video_size ();
+ dcp::Size const vs = _ffmpeg_content->video_size ();
- subtitle (
+ image_subtitle (
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
#include <boost/date_time.hpp>
#include <libxml++/libxml++.h>
#include <libcxml/cxml.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/cpl.h>
-#include <libdcp/signer.h>
-#include <libdcp/util.h>
-#include <libdcp/kdm.h>
+#include <dcp/signer_chain.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
+#include <dcp/util.h>
+#include <dcp/kdm.h>
#include "film.h"
#include "job.h"
#include "util.h"
using boost::ends_with;
using boost::starts_with;
using boost::optional;
-using libdcp::Size;
-using libdcp::Signer;
+using dcp::Size;
+using dcp::Signer;
/* 5 -> 6
* AudioMapping XML changed.
}
boost::filesystem::path
- Film::audio_analysis_path (shared_ptr<const AudioContent> c) const
+ Film::audio_analysis_dir () const
{
- boost::filesystem::path p = dir ("analysis");
- p /= c->digest();
- return p;
+ return dir ("analysis");
}
/** Add suitable Jobs to the JobManager to create a DCP for this Film */
_dirty = false;
}
- /** Read state from our metadata file */
- void
+ /** Read state from our metadata file.
+ * @return Notes about things that the user should know about, or empty.
+ */
+ list<string>
Film::read_metadata ()
{
LocaleGuard lg;
_sequence_video = f.bool_child ("SequenceVideo");
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
- _key = libdcp::Key (f.string_child ("Key"));
+ _key = dcp::Key (f.string_child ("Key"));
- _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version);
+
+ list<string> notes;
+ /* This method is the only one that can return notes (so far) */
+ _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
_dirty = false;
+ return notes;
}
/** Given a directory name, return its full path within the Film's directory.
return file (p);
}
-/** @return List of subdirectories (not full paths) containing DCPs that can be successfully libdcp::DCP::read() */
+/** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */
list<boost::filesystem::path>
Film::dcps () const
{
) {
try {
- libdcp::DCP dcp (*i);
+ dcp::DCP dcp (*i);
dcp.read ();
out.push_back (i->path().leaf ());
} catch (...) {
_playlist->move_later (c);
}
-Time
+DCPTime
Film::length () const
{
return _playlist->length ();
return _playlist->has_subtitles ();
}
-OutputVideoFrame
+int
Film::best_video_frame_rate () const
{
return _playlist->best_dcp_frame_rate ();
}
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
signal_changed (CONTENT);
}
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
-{
- return divide_with_round (t * audio_frame_rate (), TIME_HZ);
-}
-
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
-{
- return divide_with_round (t * video_frame_rate (), TIME_HZ);
-}
-
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
-{
- return divide_with_round (f * TIME_HZ, audio_frame_rate ());
-}
-
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
-{
- return divide_with_round (f * TIME_HZ, video_frame_rate ());
-}
-
-OutputAudioFrame
+int
Film::audio_frame_rate () const
{
/* XXX */
signal_changed (SEQUENCE_VIDEO);
}
-libdcp::Size
+ /** @return Size of the largest possible image in whatever resolution we are using */
+dcp::Size
Film::full_frame () const
{
switch (_resolution) {
case RESOLUTION_2K:
- return libdcp::Size (2048, 1080);
+ return dcp::Size (2048, 1080);
case RESOLUTION_4K:
- return libdcp::Size (4096, 2160);
+ return dcp::Size (4096, 2160);
}
assert (false);
- return libdcp::Size ();
+ return dcp::Size ();
}
-libdcp::Size
+ /** @return Size of the frame */
-libdcp::KDM
++dcp::Size
+ Film::frame_size () const
+ {
+ return fit_ratio_within (container()->ratio(), full_frame ());
+ }
+
+dcp::KDM
Film::make_kdm (
- shared_ptr<libdcp::Certificate> target,
+ shared_ptr<dcp::Certificate> target,
boost::filesystem::path dcp_dir,
boost::posix_time::ptime from,
boost::posix_time::ptime until
{
shared_ptr<const Signer> signer = make_signer ();
- libdcp::DCP dcp (dir (dcp_dir.string ()));
+ dcp::DCP dcp (dir (dcp_dir.string ()));
try {
dcp.read ();
time_t now = time (0);
struct tm* tm = localtime (&now);
- string const issue_date = libdcp::tm_to_string (tm);
+ string const issue_date = dcp::tm_to_string (tm);
dcp.cpls().front()->set_mxf_keys (key ());
- return libdcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
+ return dcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
}
-list<libdcp::KDM>
+list<dcp::KDM>
Film::make_kdms (
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
boost::posix_time::ptime until
) const
{
- list<libdcp::KDM> kdms;
+ list<dcp::KDM> kdms;
for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until));
uint64_t
Film::required_disk_space () const
{
- return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ;
+ return uint64_t (j2k_bandwidth() / 8) * length().seconds();
}
/** This method checks the disk that the Film is on and tries to decide whether or not
#include <boost/signals2.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/key.h>
-#include <libdcp/kdm.h>
+#include <dcp/key.h>
+#include <dcp/kdm.h>
#include "util.h"
#include "types.h"
#include "dci_metadata.h"
boost::filesystem::path info_path (int, Eyes) const;
boost::filesystem::path internal_video_mxf_dir () const;
boost::filesystem::path internal_video_mxf_filename () const;
- boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
+ boost::filesystem::path audio_analysis_dir () const;
boost::filesystem::path video_mxf_filename () const;
boost::filesystem::path audio_mxf_filename () const;
boost::filesystem::path file (boost::filesystem::path f) const;
boost::filesystem::path dir (boost::filesystem::path d) const;
- void read_metadata ();
+ std::list<std::string> read_metadata ();
void write_metadata () const;
boost::shared_ptr<xmlpp::Document> metadata () const;
return _dirty;
}
- libdcp::Size full_frame () const;
- libdcp::Size frame_size () const;
+ dcp::Size full_frame () const;
++ dcp::Size frame_size () const;
std::list<boost::filesystem::path> dcps () const;
boost::shared_ptr<Player> make_player () const;
boost::shared_ptr<Playlist> playlist () const;
- OutputAudioFrame audio_frame_rate () const;
-
- OutputAudioFrame time_to_audio_frames (Time) const;
- OutputVideoFrame time_to_video_frames (Time) const;
- Time video_frames_to_time (OutputVideoFrame) const;
- Time audio_frames_to_time (OutputAudioFrame) const;
+ int audio_frame_rate () const;
uint64_t required_disk_space () const;
bool should_be_enough_disk_space (double &, double &) const;
/* Proxies for some Playlist methods */
ContentList content () const;
- Time length () const;
+ DCPTime length () const;
bool has_subtitles () const;
- OutputVideoFrame best_video_frame_rate () const;
+ int best_video_frame_rate () const;
+ FrameRateChange active_frame_rate_change (DCPTime) const;
- libdcp::KDM
+ dcp::KDM
make_kdm (
- boost::shared_ptr<libdcp::Certificate> target,
+ boost::shared_ptr<dcp::Certificate> target,
boost::filesystem::path dcp,
boost::posix_time::ptime from,
boost::posix_time::ptime until
) const;
- std::list<libdcp::KDM> make_kdms (
+ std::list<dcp::KDM> make_kdms (
std::list<boost::shared_ptr<Screen> >,
boost::filesystem::path dcp,
boost::posix_time::ptime from,
boost::posix_time::ptime until
) const;
- libdcp::Key key () const {
+ dcp::Key key () const {
return _key;
}
bool _three_d;
bool _sequence_video;
bool _interop;
- libdcp::Key _key;
+ dcp::Key _key;
int _state_version;
using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
/** Construct a FilterGraph for the settings in a piece of content.
* @param content Content.
* @param s Size of the images to process.
* @param p Pixel format of the images to process.
*/
-FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
{
_frame = av_frame_alloc ();
- string filters = Filter::ffmpeg_strings (content->filters()).first;
+ string filters = Filter::ffmpeg_string (content->filters());
if (filters.empty ()) {
filters = "copy";
}
throw DecodeError (N_("could not configure filter graph."));
}
- /* XXX: leaking `inputs' / `outputs' ? */
+ avfilter_inout_free (&inputs);
+ avfilter_inout_free (&outputs);
}
FilterGraph::~FilterGraph ()
* @return true if this chain can process images with `s' and `p', otherwise false.
*/
bool
-FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (dcp::Size s, AVPixelFormat p) const
{
return (_size == s && _pixel_format == p);
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
+#include "timer.h"
#include "i18n.h"
using std::cout;
using std::cerr;
using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
int
Image::line_factor (int n) const
/** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */
shared_ptr<Image>
-Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
{
assert (scaler);
/* Empirical testing suggests that sws_scale() will crash if
out->make_black ();
/* Size of the image after any crop */
- libdcp::Size const cropped_size = crop.apply (size ());
+ dcp::Size const cropped_size = crop.apply (size ());
/* Scale context for a scale from cropped_size to inter_size */
struct SwsContext* scale_context = sws_getContext (
- cropped_size.width, cropped_size.height, pixel_format(),
- inter_size.width, inter_size.height, out_format,
- scaler->ffmpeg_id (), 0, 0, 0
+ cropped_size.width, cropped_size.height, pixel_format(),
+ inter_size.width, inter_size.height, out_format,
+ scaler->ffmpeg_id (), 0, 0, 0
);
if (!scale_context) {
}
shared_ptr<Image>
-Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
{
assert (scaler);
/* Empirical testing suggests that sws_scale() will crash if
return scaled;
}
- /** Run a FFmpeg post-process on this image and return the processed version.
- * @param pp Flags for the required set of post processes.
- * @return Post-processed image.
- */
- shared_ptr<Image>
- Image::post_process (string pp, bool aligned) const
- {
- shared_ptr<Image> out (new Image (pixel_format(), size (), aligned));
-
- int pp_format = 0;
- switch (pixel_format()) {
- case PIX_FMT_YUV420P:
- pp_format = PP_FORMAT_420;
- break;
- case PIX_FMT_YUV422P10LE:
- case PIX_FMT_YUV422P:
- case PIX_FMT_UYVY422:
- pp_format = PP_FORMAT_422;
- break;
- case PIX_FMT_YUV444P:
- case PIX_FMT_YUV444P9BE:
- case PIX_FMT_YUV444P9LE:
- case PIX_FMT_YUV444P10BE:
- case PIX_FMT_YUV444P10LE:
- pp_format = PP_FORMAT_444;
- default:
- throw PixelFormatError ("post_process", pixel_format());
- }
-
- pp_mode* mode = pp_get_mode_by_name_and_quality (pp.c_str (), PP_QUALITY_MAX);
- pp_context* context = pp_get_context (size().width, size().height, pp_format | PP_CPU_CAPS_MMX2);
-
- pp_postprocess (
- (const uint8_t **) data(), stride(),
- out->data(), out->stride(),
- size().width, size().height,
- 0, 0, mode, context, 0
- );
-
- pp_free_mode (mode);
- pp_free_context (context);
-
- return out;
- }
-
shared_ptr<Image>
Image::crop (Crop crop, bool aligned) const
{
- libdcp::Size cropped_size = crop.apply (size ());
+ dcp::Size cropped_size = crop.apply (size ());
shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
for (int c = 0; c < components(); ++c) {
void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* Only implemented for RGBA onto RGB24 so far */
- assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+ int this_bpp = 0;
+ int other_bpp = 0;
+
+ if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 4;
+ other_bpp = 4;
+ } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 3;
+ other_bpp = 4;
+ } else {
+ assert (false);
+ }
int start_tx = position.x;
int start_ox = 0;
}
for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
- uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+ uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
- tp += 3;
- op += 4;
+ tp += this_bpp;
+ op += other_bpp;
}
}
}
* @param p Pixel format.
* @param s Size in pixels.
*/
-Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned)
- : libdcp::Image (s)
+Image::Image (AVPixelFormat p, dcp::Size s, bool aligned)
+ : dcp::Image (s)
, _pixel_format (p)
, _aligned (aligned)
{
}
Image::Image (Image const & other)
- : libdcp::Image (other)
+ : dcp::Image (other)
, _pixel_format (other._pixel_format)
, _aligned (other._aligned)
{
}
Image::Image (AVFrame* frame)
- : libdcp::Image (libdcp::Size (frame->width, frame->height))
+ : dcp::Image (dcp::Size (frame->width, frame->height))
, _pixel_format (static_cast<AVPixelFormat> (frame->format))
, _aligned (true)
{
}
Image::Image (shared_ptr<const Image> other, bool aligned)
- : libdcp::Image (other)
+ : dcp::Image (other)
, _pixel_format (other->_pixel_format)
, _aligned (aligned)
{
void
Image::swap (Image & other)
{
- libdcp::Image::swap (other);
+ dcp::Image::swap (other);
std::swap (_pixel_format, other._pixel_format);
return _stride;
}
-libdcp::Size
+dcp::Size
Image::size () const
{
return _size;
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
}
-#include <libdcp/image.h>
+#include <dcp/image.h>
#include "util.h"
#include "position.h"
class Scaler;
-class Image : public libdcp::Image
+class Image : public dcp::Image
{
public:
- Image (AVPixelFormat, libdcp::Size, bool);
+ Image (AVPixelFormat, dcp::Size, bool);
Image (AVFrame *);
Image (Image const &);
Image (boost::shared_ptr<const Image>, bool);
uint8_t ** data () const;
int * line_size () const;
int * stride () const;
- libdcp::Size size () const;
+ dcp::Size size () const;
bool aligned () const;
int components () const;
int line_factor (int) const;
int lines (int) const;
- boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
- boost::shared_ptr<Image> post_process (std::string, bool aligned) const;
boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
- boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
void make_black ();
void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
*/
#include <stdint.h>
+#include <algorithm>
#include "player.h"
#include "film.h"
#include "ffmpeg_decoder.h"
+#include "audio_buffers.h"
#include "ffmpeg_content.h"
#include "image_decoder.h"
#include "image_content.h"
#include "sndfile_decoder.h"
#include "sndfile_content.h"
#include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
#include "playlist.h"
#include "job.h"
#include "image.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
using std::list;
using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
class Piece
{
public:
- Piece (shared_ptr<Content> c)
- : content (c)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
- {}
-
- Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+ Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
: content (c)
, decoder (d)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
+ , frc (f)
{}
- /** Set this piece to repeat a video frame a given number of times */
- void set_repeat (IncomingVideo video, int num)
- {
- repeat_video = video;
- repeat_to_do = num;
- repeat_done = 0;
- }
-
- void reset_repeat ()
- {
- repeat_video.image.reset ();
- repeat_to_do = 0;
- repeat_done = 0;
- }
-
- bool repeating () const
- {
- return repeat_done != repeat_to_do;
- }
-
- void repeat (Player* player)
- {
- player->process_video (
- repeat_video.weak_piece,
- repeat_video.image,
- repeat_video.eyes,
- repeat_done > 0,
- repeat_video.frame,
- (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
- );
-
- ++repeat_done;
- }
-
shared_ptr<Content> content;
shared_ptr<Decoder> decoder;
- /** Time of the last video we emitted relative to the start of the DCP */
- Time video_position;
- /** Time of the last audio we emitted relative to the start of the DCP */
- Time audio_position;
-
- IncomingVideo repeat_video;
- int repeat_to_do;
- int repeat_done;
+ FrameRateChange frc;
};
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
, _have_valid_pieces (false)
, _video_position (0)
, _audio_position (0)
- , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
+ , _audio_merger (f->audio_channels(), f->audio_frame_rate ())
, _last_emit_was_black (false)
+ , _just_did_inaccurate_seek (false)
+ , _approximate_size (false)
{
_playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
_film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
- set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
+ set_video_container_size (_film->frame_size ());
}
void
setup_pieces ();
}
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
+ /* Interrogate all our pieces to find the one with the earliest decoded data */
+
+ shared_ptr<Piece> earliest_piece;
+ shared_ptr<Decoded> earliest_decoded;
+ DCPTime earliest_time = DCPTime::max ();
+ DCPTime earliest_audio = DCPTime::max ();
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
+
+ bool done = false;
+ shared_ptr<Decoded> dec;
+ while (!done) {
+ dec = (*i)->decoder->peek ();
+ if (!dec) {
+ /* Decoder has nothing else to give us */
+ break;
+ }
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
+ dec->set_dcp_times ((*i)->frc, offset);
+ DCPTime const t = dec->dcp_time - offset;
+ if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
+ /* In the end-trimmed part; decoder has nothing else to give us */
+ dec.reset ();
+ done = true;
+ } else if (t >= (*i)->content->trim_start ()) {
+ /* Within the un-trimmed part; everything's ok */
+ done = true;
+ } else {
+ /* Within the start-trimmed part; get something else */
+ (*i)->decoder->consume ();
}
}
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
- }
+ if (!dec) {
+ continue;
}
- }
- if (!earliest) {
+ if (dec->dcp_time < earliest_time) {
+ earliest_piece = *i;
+ earliest_decoded = dec;
+ earliest_time = dec->dcp_time;
+ }
+
+ if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
+ earliest_audio = dec->dcp_time;
+ }
+ }
+
+ if (!earliest_piece) {
flush ();
return true;
}
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
- } else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
- } else {
- earliest->decoder->pass ();
- }
- }
- break;
-
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
- } else {
- earliest->decoder->pass ();
-
- if (earliest->decoder->done()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
- assert (ac);
- shared_ptr<Resampler> re = resampler (ac, false);
- if (re) {
- shared_ptr<const AudioBuffers> b = re->flush ();
- if (b->frames ()) {
- process_audio (earliest, b, ac->audio_length ());
- }
- }
- }
+ if (earliest_audio != DCPTime::max ()) {
+ if (earliest_audio.get() < 0) {
+ earliest_audio = DCPTime ();
}
- break;
+ TimedAudioBuffers tb = _audio_merger.pull (earliest_audio);
+ Audio (tb.audio, tb.time);
+ /* This assumes that the audio-frames-to-time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _audio_position += DCPTime::from_frames (tb.audio->frames(), _film->audio_frame_rate ());
}
- if (_audio) {
- boost::optional<Time> audio_done_up_to;
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
+ /* Emit the earliest thing */
+
+ shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+ shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+ shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
+ shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
+
+ /* Will be set to false if we shouldn't consume the peeked DecodedThing */
+ bool consume = true;
+
+ if (dv && _video) {
+
+ if (_just_did_inaccurate_seek) {
+
+ /* Just emit; no subtlety */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+
+ } else if (dv->dcp_time > _video_position) {
+
+ /* Too far ahead */
+
+ list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+ while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+ ++i;
}
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
- if (ad && ad->has_audio ()) {
- audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
+ if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+ /* We're outside all video content */
+ emit_black ();
+ _statistics.video.black++;
+ } else {
+ /* We're inside some video; repeat the frame */
+ _last_incoming_video.video->dcp_time = _video_position;
+ emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
+ step_video_position (_last_incoming_video.video);
+ _statistics.video.repeat++;
}
+
+ consume = false;
+
+ } else if (dv->dcp_time == _video_position) {
+ /* We're ok */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+ _statistics.video.good++;
+ } else {
+ /* Too far behind: skip */
+ _statistics.video.skip++;
}
- if (audio_done_up_to) {
- TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ _just_did_inaccurate_seek = false;
+
+ } else if (da && _audio) {
+
+ if (da->dcp_time > _audio_position) {
+ /* Too far ahead */
+ emit_silence (da->dcp_time - _audio_position);
+ consume = false;
+ _statistics.audio.silence += (da->dcp_time - _audio_position);
+ } else if (da->dcp_time == _audio_position) {
+ /* We're ok */
+ emit_audio (earliest_piece, da);
+ _statistics.audio.good += da->data->frames();
+ } else {
+ /* Too far behind: skip */
+ _statistics.audio.skip += da->data->frames();
}
- }
+ } else if (dis && _video) {
+ _image_subtitle.piece = earliest_piece;
+ _image_subtitle.subtitle = dis;
+ update_subtitle_from_image ();
+ } else if (dts && _video) {
+ _text_subtitle.piece = earliest_piece;
+ _text_subtitle.subtitle = dts;
+ update_subtitle_from_text ();
+ }
+
+ if (consume) {
+ earliest_piece->decoder->consume ();
+ }
+
return false;
}
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
{
/* Keep a note of what came in so that we can repeat it if required */
_last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.image = image;
- _last_incoming_video.eyes = eyes;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
+ _last_incoming_video.video = video;
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
assert (content);
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
- if (frc.skip && (frame % 2) == 1) {
- return;
- }
+ FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
- return;
+ dcp::Size image_size = content->scale().size (content, _video_container_size);
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
}
- Time const time = content->position() + relative_time + extra - content->trim_start ();
- libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
-
shared_ptr<PlayerImage> pi (
new PlayerImage (
- image,
+ video->image,
content->crop(),
image_size,
_video_container_size,
)
);
- if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+ if (
+ _film->with_subtitles () &&
+ _out_subtitle.image &&
+ video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
+ ) {
Position<int> const container_offset (
(_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
+ (_video_container_size.height - image_size.height) / 2
);
pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
_last_video = piece->content;
#endif
- Video (pi, eyes, content->colour_conversion(), same, time);
-
+ Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
+
_last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+}
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+void
+Player::step_video_position (shared_ptr<DecodedVideo> video)
+{
+ /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
+ if (video->eyes != EYES_LEFT) {
+ /* This assumes that the video-frames-to-time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _video_position += DCPTime::from_frames (1, _film->video_frame_rate ());
}
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
{
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
/* Gain */
if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
gain->apply_gain (content->audio_gain ());
- audio = gain;
- }
-
- /* Resample */
- if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
+ audio->data = gain;
}
-
- Time const relative_time = _film->audio_frames_to_time (frame);
- if (content->trimmed (relative_time)) {
- return;
- }
-
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-
/* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
dcp_mapped->make_silent ();
-
AudioMapping map = content->audio_mapping ();
for (int i = 0; i < map.content_channels(); ++i) {
for (int j = 0; j < _film->audio_channels(); ++j) {
- if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
+ if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
dcp_mapped->accumulate_channel (
- audio.get(),
+ audio->data.get(),
i,
- static_cast<libdcp::Channel> (j),
- map.get (i, static_cast<libdcp::Channel> (j))
+ static_cast<dcp::Channel> (j),
+ map.get (i, static_cast<dcp::Channel> (j))
);
}
}
}
- audio = dcp_mapped;
+ audio->data = dcp_mapped;
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
+ /* Delay */
+ audio->dcp_time += DCPTime::from_seconds (content->audio_delay() / 1000.0);
+ if (audio->dcp_time < DCPTime (0)) {
+ int const frames = - audio->dcp_time.frames (_film->audio_frame_rate());
+ if (frames >= audio->data->frames ()) {
return;
}
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+ trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
- audio = trimmed;
- time = 0;
+ audio->data = trimmed;
+ audio->dcp_time = DCPTime ();
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ _audio_merger.push (audio->data, audio->dcp_time);
}
void
Player::flush ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+ TimedAudioBuffers tb = _audio_merger.flush ();
if (_audio && tb.audio) {
Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ _audio_position += DCPTime::from_frames (tb.audio->frames (), _film->audio_frame_rate ());
}
while (_video && _video_position < _audio_position) {
}
while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ emit_silence (_video_position - _audio_position);
}
-
}
/** Seek so that the next pass() will yield (approximately) the requested frame.
* @return true on error
*/
void
-Player::seek (Time t, bool accurate)
+Player::seek (DCPTime t, bool accurate)
{
if (!_have_valid_pieces) {
setup_pieces ();
}
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
- if (!vc) {
- continue;
- }
-
/* s is the offset of t from the start position of this content */
- Time s = t - vc->position ();
- s = max (static_cast<Time> (0), s);
- s = min (vc->length_after_trim(), s);
+ DCPTime s = t - (*i)->content->position ();
+ s = max (static_cast<DCPTime> (0), s);
+ s = min ((*i)->content->length_after_trim(), s);
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
+ /* Convert this to the content time */
+ ContentTime ct (s + (*i)->content->trim_start(), (*i)->frc);
/* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
-
- (*i)->reset_repeat ();
+ (*i)->decoder->seek (ct, accurate);
}
- _video_position = _audio_position = t;
+ _video_position = t.round_up (_film->video_frame_rate());
+ _audio_position = t.round_up (_film->audio_frame_rate());
- /* XXX: don't seek audio because we don't need to... */
+ _audio_merger.clear (_audio_position);
+
+ if (!accurate) {
+ /* We just did an inaccurate seek, so it's likely that the next thing seen
+ out of pass() will be a fair distance from _{video,audio}_position. Setting
+ this flag stops pass() from trying to fix that: we assume that if it
+ was an inaccurate seek then the caller does not care too much about
+ inserting black/silence to keep the time tidy.
+ */
+ _just_did_inaccurate_seek = true;
+ }
}
void
Player::setup_pieces ()
{
list<shared_ptr<Piece> > old_pieces = _pieces;
-
_pieces.clear ();
ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
if (!(*i)->paths_valid ()) {
continue;
}
+
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
+
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ DCPTime best_overlap_t;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+ } else {
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
+ /* FFmpeg */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
- fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
- piece->decoder = fd;
+ decoder.reset (new FFmpegDecoder (fc, _film->log(), _video, _audio, _film->with_subtitles ()));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
-
+
+ /* ImageContent */
shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
if (ic) {
- bool reusing = false;
-
/* See if we can re-use an old ImageDecoder */
for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
if (imd && imd->content() == ic) {
- piece = *j;
- reusing = true;
+ decoder = imd;
}
}
- if (!reusing) {
- shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- piece->decoder = id;
+ if (!decoder) {
+ decoder.reset (new ImageDecoder (ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
+ /* SndfileContent */
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
- shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+ decoder.reset (new SndfileDecoder (sc));
+ frc = best_overlap_frc;
+ }
- piece->decoder = sd;
+ /* SubRipContent */
+ shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+ if (rc) {
+ decoder.reset (new SubRipDecoder (rc));
+ frc = best_overlap_frc;
}
- _pieces.push_back (piece);
+ ContentTime st ((*i)->trim_start(), frc.get ());
+ decoder->seek (st, true);
+
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
_have_valid_pieces = true;
+
+ /* The Piece for the _last_incoming_video will no longer be valid */
+ _last_incoming_video.video.reset ();
+
+ _video_position = DCPTime ();
+ _audio_position = DCPTime ();
}
void
property == SubtitleContentProperty::SUBTITLE_SCALE
) {
- update_subtitle ();
+ update_subtitle_from_image ();
+ update_subtitle_from_text ();
Changed (frequent);
} else if (
}
void
-Player::set_video_container_size (libdcp::Size s)
+Player::set_video_container_size (dcp::Size s)
{
_video_container_size = s;
);
}
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
-
- if (!create) {
- return shared_ptr<Resampler> ();
- }
-
- _film->log()->log (
- String::compose (
- "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
- )
- );
-
- shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
- _resamplers[c] = r;
- return r;
-}
-
void
Player::emit_black ()
{
#endif
Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
- _video_position += _film->video_frames_to_time (1);
+ _video_position += DCPTime::from_frames (1, _film->video_frame_rate ());
_last_emit_was_black = true;
}
void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
{
- if (most == 0) {
+ if (most == DCPTime ()) {
return;
}
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+ DCPTime t = min (most, DCPTime::from_seconds (0.5));
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t.frames (_film->audio_frame_rate())));
silence->make_silent ();
Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
+
+ _audio_position += t;
}
void
}
void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+Player::update_subtitle_from_image ()
{
- _in_subtitle.piece = weak_piece;
- _in_subtitle.image = image;
- _in_subtitle.rect = rect;
- _in_subtitle.from = from;
- _in_subtitle.to = to;
-
- update_subtitle ();
-}
-
-void
-Player::update_subtitle ()
-{
- shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+ shared_ptr<Piece> piece = _image_subtitle.piece.lock ();
if (!piece) {
return;
}
- if (!_in_subtitle.image) {
+ if (!_image_subtitle.subtitle->image) {
_out_subtitle.image.reset ();
return;
}
shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
assert (sc);
- dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
- libdcp::Size scaled_size;
+ dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect;
+ dcp::Size scaled_size;
in_rect.x += sc->subtitle_x_offset ();
in_rect.y += sc->subtitle_y_offset ();
_out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
_out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
- _out_subtitle.image = _in_subtitle.image->scale (
+ _out_subtitle.image = _image_subtitle.subtitle->image->scale (
scaled_size,
Scaler::from_id ("bicubic"),
- _in_subtitle.image->pixel_format (),
+ _image_subtitle.subtitle->image->pixel_format (),
true
);
-
- /* XXX: hack */
- Time from = _in_subtitle.from;
- Time to = _in_subtitle.to;
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
- if (vc) {
- from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
- to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
- }
- _out_subtitle.from = from + piece->content->position ();
- _out_subtitle.to = to + piece->content->position ();
+ _out_subtitle.from = _image_subtitle.subtitle->dcp_time + piece->content->position ();
+ _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to + piece->content->position ();
}
/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
bool
Player::repeat_last_video ()
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
+ if (!_last_incoming_video.video || !_have_valid_pieces) {
return false;
}
- process_video (
+ emit_video (
_last_incoming_video.weak_piece,
- _last_incoming_video.image,
- _last_incoming_video.eyes,
- _last_incoming_video.same,
- _last_incoming_video.frame,
- _last_incoming_video.extra
+ _last_incoming_video.video
);
return true;
}
+void
+Player::update_subtitle_from_text ()
+{
+ if (_text_subtitle.subtitle->subs.empty ()) {
+ _out_subtitle.image.reset ();
+ return;
+ }
+
+ render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position);
+}
+
+void
+Player::set_approximate_size ()
+{
+ _approximate_size = true;
+}
+
PlayerImage::PlayerImage (
shared_ptr<const Image> in,
Crop crop,
- libdcp::Size inter_size,
- libdcp::Size out_size,
+ dcp::Size inter_size,
+ dcp::Size out_size,
Scaler const * scaler
)
: _in (in)
}
shared_ptr<Image>
-PlayerImage::image ()
+PlayerImage::image (AVPixelFormat format, bool aligned)
{
- shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
-
+ shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
if (_subtitle_image) {
return out;
}
+
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()));
+}
+
+PlayerStatistics const &
+Player::statistics () const
+{
+ return _statistics;
+}
_sequencing_video = true;
ContentList cl = _content;
- Time next = 0;
+ DCPTime next;
for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
if (!dynamic_pointer_cast<VideoContent> (*i)) {
continue;
}
(*i)->set_position (next);
- next = (*i)->end() + 1;
+ next = (*i)->end() + DCPTime::delta ();
}
/* This won't change order, so it does not need a sort */
/** @param node <Playlist> node */
void
- Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
+ Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
{
list<cxml::NodePtr> c = node->node_children ("Content");
for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
- _content.push_back (content_factory (film, *i, version));
+ _content.push_back (content_factory (film, *i, version, notes));
}
sort (_content.begin(), _content.end(), ContentSorter ());
return best->dcp;
}
-Time
+DCPTime
Playlist::length () const
{
- Time len = 0;
+ DCPTime len;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
- len = max (len, (*i)->end() + 1);
+ len = max (len, (*i)->end() + DCPTime::delta ());
}
return len;
}
}
-Time
+DCPTime
Playlist::video_end () const
{
- Time end = 0;
+ DCPTime end;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
if (dynamic_pointer_cast<const VideoContent> (*i)) {
end = max (end, (*i)->end ());
return end;
}
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (!vc) {
+ break;
+ }
+
+ if (vc->position() >= t && t < vc->end()) {
+ return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+ }
+ }
+
+ return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
void
Playlist::set_sequence_video (bool s)
{
void
Playlist::repeat (ContentList c, int n)
{
- pair<Time, Time> range (TIME_MAX, 0);
+ pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
range.first = min (range.first, (*i)->position ());
range.second = max (range.second, (*i)->position ());
range.second = max (range.second, (*i)->end ());
}
- Time pos = range.second;
+ DCPTime pos = range.second;
for (int i = 0; i < n; ++i) {
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
shared_ptr<Content> copy = (*i)->clone ();
return;
}
- Time const p = (*previous)->position ();
+ DCPTime const p = (*previous)->position ();
(*previous)->set_position (p + c->length_after_trim ());
c->set_position (p);
sort (_content.begin(), _content.end(), ContentSorter ());
return;
}
- Time const p = (*next)->position ();
+ DCPTime const p = (*next)->position ();
(*next)->set_position (c->position ());
c->set_position (p + c->length_after_trim ());
sort (_content.begin(), _content.end(), ContentSorter ());
#include <boost/enable_shared_from_this.hpp>
#include "ffmpeg_content.h"
#include "audio_mapping.h"
+#include "util.h"
class Content;
class FFmpegContent;
~Playlist ();
void as_xml (xmlpp::Node *);
- void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+ void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int, std::list<std::string> &);
void add (boost::shared_ptr<Content>);
void remove (boost::shared_ptr<Content>);
std::string video_identifier () const;
- Time length () const;
+ DCPTime length () const;
int best_dcp_frame_rate () const;
- Time video_end () const;
+ DCPTime video_end () const;
+ FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
void set_sequence_video (bool);
void maybe_sequence_video ();
#include <iomanip>
#include <libcxml/cxml.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include "video_content.h"
#include "video_examiner.h"
#include "compose.hpp"
setup_default_colour_conversion ();
}
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
: Content (f, node)
{
- _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+ _video_length = ContentTime (node->number_child<int64_t> ("VideoLength"));
_video_size.width = node->number_child<int> ("VideoWidth");
_video_size.height = node->number_child<int> ("VideoHeight");
_video_frame_rate = node->number_child<float> ("VideoFrameRate");
VideoContent::as_xml (xmlpp::Node* node) const
{
boost::mutex::scoped_lock lm (_mutex);
- node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
+ node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length.get ()));
node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
void
VideoContent::setup_default_colour_conversion ()
{
- _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
+ _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
}
void
VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
{
/* These examiner calls could call other content methods which take a lock on the mutex */
- libdcp::Size const vs = d->video_size ();
+ dcp::Size const vs = d->video_size ();
float const vfr = d->video_frame_rate ();
{
string
VideoContent::technical_summary () const
{
- return String::compose ("video: length %1, size %2x%3, rate %4", video_length(), video_size().width, video_size().height, video_frame_rate());
+ return String::compose (
+ "video: length %1, size %2x%3, rate %4",
+ video_length().seconds(),
+ video_size().width,
+ video_size().height,
+ video_frame_rate()
+ );
}
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_3d_split () const
{
- libdcp::Size const s = video_size ();
+ dcp::Size const s = video_size ();
switch (video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
return s;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
- return libdcp::Size (s.width / 2, s.height);
+ return dcp::Size (s.width / 2, s.height);
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
- return libdcp::Size (s.width, s.height / 2);
+ return dcp::Size (s.width, s.height / 2);
}
assert (false);
}
/** @return Video size after 3D split and crop */
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_crop () const
{
return crop().apply (video_size_after_3d_split ());
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding time with respect to the content.
*/
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+ContentTime
+VideoContent::dcp_time_to_content_time (DCPTime t) const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
- /* Here we are converting from time (in the DCP) to a frame number in the content.
- Hence we need to use the DCP's frame rate and the double/skip correction, not
- the source's rate.
- */
- return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
}
VideoContentScale::VideoContentScale (Ratio const * r)
return _("No scale");
}
-libdcp::Size
-VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
+ /** @param display_container Size of the container that we are displaying this content in.
+ * @param film_container The size of the film's image.
+ */
- VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size container) const
+dcp::Size
++VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
{
if (_ratio) {
- return fit_ratio_within (_ratio->ratio (), container);
+ return fit_ratio_within (_ratio->ratio (), display_container);
}
- /* Force scale if the container is smaller than the content's image */
- if (_scale || container.width < c->video_size().width || container.height < c->video_size().height) {
- return fit_ratio_within (c->video_size().ratio (), container);
+ libdcp::Size const ac = c->video_size_after_crop ();
+
+ /* Force scale if the film_container is smaller than the content's image */
+ if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
+ return fit_ratio_within (ac.ratio (), display_container);
}
- return c->video_size ();
+ /* Scale the image so that it will be in the right place in film_container, even if display_container is a
+ different size.
+ */
+ return libdcp::Size (
+ c->video_size().width * float(display_container.width) / film_container.width,
+ c->video_size().height * float(display_container.height) / film_container.height
+ );
}
void
VideoContentScale (bool);
VideoContentScale (boost::shared_ptr<cxml::Node>);
- dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size) const;
- libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const;
++ dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const;
std::string id () const;
std::string name () const;
void as_xml (xmlpp::Node *) const;
}
private:
+ /** a ratio to stretch the content to, or 0 for no stretch */
Ratio const * _ratio;
+ /** true if we want to scale the content */
bool _scale;
static std::vector<VideoContentScale> _scales;
typedef int Frame;
VideoContent (boost::shared_ptr<const Film>);
- VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+ VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime);
VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
virtual std::string information () const;
virtual std::string identifier () const;
- VideoContent::Frame video_length () const {
+ ContentTime video_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_length;
}
- libdcp::Size video_size () const {
+ dcp::Size video_size () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_size;
}
return _colour_conversion;
}
- libdcp::Size video_size_after_3d_split () const;
- libdcp::Size video_size_after_crop () const;
+ dcp::Size video_size_after_3d_split () const;
+ dcp::Size video_size_after_crop () const;
- VideoContent::Frame time_to_content_video_frames (Time) const;
+ ContentTime dcp_time_to_content_time (DCPTime) const;
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
- VideoContent::Frame _video_length;
+ ContentTime _video_length;
float _video_frame_rate;
private:
void setup_default_colour_conversion ();
- libdcp::Size _video_size;
+ dcp::Size _video_size;
VideoFrameType _video_frame_type;
Crop _crop;
VideoContentScale _scale;
#include <fstream>
#include <cerrno>
-#include <libdcp/mono_picture_asset.h>
-#include <libdcp/stereo_picture_asset.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/reel.h>
-#include <libdcp/dcp.h>
-#include <libdcp/cpl.h>
+#include <dcp/mono_picture_mxf.h>
+#include <dcp/stereo_picture_mxf.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_mxf_writer.h>
+#include <dcp/reel.h>
+#include <dcp/reel_mono_picture_asset.h>
+#include <dcp/reel_stereo_picture_asset.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/dcp.h>
+#include <dcp/cpl.h>
#include "writer.h"
#include "compose.hpp"
#include "film.h"
#include "config.h"
#include "job.h"
#include "cross.h"
+#include "audio_buffers.h"
#include "i18n.h"
+ /* OS X strikes again */
+ #undef set_key
+
using std::make_pair;
using std::pair;
using std::string;
using std::stringstream;
using boost::shared_ptr;
using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
*/
if (_film->three_d ()) {
- _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
} else {
- _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
}
- _picture_mxf->set_size (fit_ratio_within (_film->container()->ratio(), _film->full_frame ()));
- _picture_asset->set_edit_rate (_film->video_frame_rate ());
- _picture_asset->set_size (_film->frame_size ());
- _picture_asset->set_interop (_film->interop ());
++ _picture_mxf->set_size (_film->frame_size ());
if (_film->encrypted ()) {
- _picture_asset->set_key (_film->key ());
+ _picture_mxf->set_key (_film->key ());
}
- _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+ _picture_mxf_writer = _picture_mxf->start_write (
+ _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
+ _film->interop() ? dcp::INTEROP : dcp::SMPTE,
+ _first_nonexistant_frame > 0
+ );
- _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
- _sound_asset->set_edit_rate (_film->video_frame_rate ());
- _sound_asset->set_channels (_film->audio_channels ());
- _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
- _sound_asset->set_interop (_film->interop ());
+ _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
if (_film->encrypted ()) {
- _sound_asset->set_key (_film->key ());
+ _sound_mxf->set_key (_film->key ());
}
- /* Write the sound asset into the film directory so that we leave the creation
+ /* Write the sound MXF into the film directory so that we leave the creation
of the DCP directory until the last minute.
*/
- _sound_asset_writer = _sound_asset->start_write ();
+ _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
_thread = new boost::thread (boost::bind (&Writer::thread, this));
}
FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r");
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
QueueItem qi;
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- _sound_asset_writer->write (audio->data(), audio->frames());
+ _sound_mxf_writer->write (audio->data(), audio->frames());
}
/** This must be called from Writer::thread() with an appropriate lock held */
qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
}
- libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
+ dcp::FrameInfo fin = _picture_mxf_writer->write (qi.encoded->data(), qi.encoded->size());
qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
_last_written[qi.eyes] = qi.encoded;
++_full_written;
}
case QueueItem::FAKE:
_film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame));
- _picture_asset_writer->fake_write (qi.size);
+ _picture_mxf_writer->fake_write (qi.size);
_last_written[qi.eyes].reset ();
++_fake_written;
break;
case QueueItem::REPEAT:
{
_film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame));
- libdcp::FrameInfo fin = _picture_asset_writer->write (
+ dcp::FrameInfo fin = _picture_mxf_writer->write (
_last_written[qi.eyes]->data(),
_last_written[qi.eyes]->size()
);
_last_written_frame = qi.frame;
_last_written_eyes = qi.eyes;
- if (_film->length()) {
- shared_ptr<Job> job = _job.lock ();
- assert (job);
- int total = _film->time_to_video_frames (_film->length ());
- if (_film->three_d ()) {
- /* _full_written and so on are incremented for each eye, so we need to double the total
- frames to get the correct progress.
- */
- total *= 2;
- }
+ shared_ptr<Job> job = _job.lock ();
+ assert (job);
+ int64_t total = _film->length().frames (_film->video_frame_rate ());
+ if (_film->three_d ()) {
+ /* _full_written and so on are incremented for each eye, so we need to double the total
+ frames to get the correct progress.
+ */
+ total *= 2;
+ }
+ if (total) {
job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
}
}
terminate_thread (true);
- _picture_asset_writer->finalize ();
- _sound_asset_writer->finalize ();
+ _picture_mxf_writer->finalize ();
+ _sound_mxf_writer->finalize ();
- int const frames = _last_written_frame + 1;
-
- _picture_asset->set_duration (frames);
-
/* Hard-link the video MXF into the DCP */
boost::filesystem::path video_from;
video_from /= _film->internal_video_mxf_dir();
_film->log()->log ("Hard-link failed; fell back to copying");
}
- /* And update the asset */
-
- _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
- _picture_asset->set_file_name (_film->video_mxf_filename ());
-
/* Move the audio MXF into the DCP */
boost::filesystem::path audio_to;
);
}
- _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
- _sound_asset->set_duration (frames);
-
- libdcp::DCP dcp (_film->dir (_film->dcp_name()));
+ dcp::DCP dcp (_film->dir (_film->dcp_name()));
- shared_ptr<libdcp::CPL> cpl (
- new libdcp::CPL (
- _film->dir (_film->dcp_name()),
+ shared_ptr<dcp::CPL> cpl (
+ new dcp::CPL (
_film->dcp_name(),
- _film->dcp_content_type()->libdcp_kind (),
- frames,
- _film->video_frame_rate ()
+ _film->dcp_content_type()->libdcp_kind ()
)
);
- dcp.add_cpl (cpl);
+ dcp.add (cpl);
+
+ shared_ptr<dcp::Reel> reel (new dcp::Reel ());
+
+ shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf);
+ if (mono) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0)));
+ }
- cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
- _picture_asset,
- _sound_asset,
- shared_ptr<libdcp::SubtitleAsset> ()
- )
- ));
+ shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
+ if (stereo) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
+ }
+
+ reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+
+ cpl->add (reel);
shared_ptr<Job> job = _job.lock ();
assert (job);
job->sub (_("Computing image digest"));
- _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
job->sub (_("Computing audio digest"));
- _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
- libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
meta.set_issue_date_now ();
- dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
+ dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ());
_film->log()->log (
String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk)
return false;
}
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
if (info.size == 0) {
_film->log()->log (String::compose ("Existing frame %1 has no info file", f));
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
#include <wx/stdpaths.h>
- #include <wx/notebook.h>
+ #include <wx/preferences.h>
+ #include <wx/filepicker.h>
+ #include <wx/spinctrl.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include "lib/config.h"
#include "lib/ratio.h"
#include "lib/scaler.h"
#include "lib/colour_conversion.h"
#include "config_dialog.h"
#include "wx_util.h"
+ #include "editable_list.h"
#include "filter_dialog.h"
#include "dir_picker_ctrl.h"
#include "dci_metadata_dialog.h"
using std::vector;
using std::string;
using std::list;
+ using std::cout;
using boost::bind;
using boost::shared_ptr;
using boost::lexical_cast;
- ConfigDialog::ConfigDialog (wxWindow* parent)
- : wxDialog (parent, wxID_ANY, _("DCP-o-matic Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ class Page
{
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _notebook = new wxNotebook (this, wxID_ANY);
- s->Add (_notebook, 1);
-
- make_misc_panel ();
- _notebook->AddPage (_misc_panel, _("Miscellaneous"), true);
- make_defaults_panel ();
- _notebook->AddPage (_defaults_panel, _("Defaults"), false);
- make_servers_panel ();
- _notebook->AddPage (_servers_panel, _("Encoding servers"), false);
- make_colour_conversions_panel ();
- _notebook->AddPage (_colour_conversions_panel, _("Colour conversions"), false);
- make_metadata_panel ();
- _notebook->AddPage (_metadata_panel, _("Metadata"), false);
- make_tms_panel ();
- _notebook->AddPage (_tms_panel, _("TMS"), false);
- make_kdm_email_panel ();
- _notebook->AddPage (_kdm_email_panel, _("KDM email"), false);
-
- wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (s, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
-
- wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
- if (buttons) {
- overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
- }
-
- SetSizer (overall_sizer);
- overall_sizer->Layout ();
- overall_sizer->SetSizeHints (this);
- }
+ public:
+ Page (wxSize panel_size, int border)
+ : _panel_size (panel_size)
+ , _border (border)
+ {}
+
+ protected:
+ wxSize _panel_size;
+ int _border;
+ };
- void
- ConfigDialog::make_misc_panel ()
+ class GeneralPage : public wxStockPreferencesPage, public Page
{
- _misc_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _misc_panel->SetSizer (s);
-
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
- table->AddGrowableCol (1, 1);
- s->Add (table, 1, wxALL | wxEXPAND, 8);
-
- _set_language = new wxCheckBox (_misc_panel, wxID_ANY, _("Set language"));
- table->Add (_set_language, 1);
- _language = new wxChoice (_misc_panel, wxID_ANY);
- _language->Append (wxT ("English"));
- _language->Append (wxT ("Français"));
- _language->Append (wxT ("Italiano"));
- _language->Append (wxT ("Español"));
- _language->Append (wxT ("Svenska"));
- _language->Append (wxT ("Deutsch"));
- table->Add (_language);
-
- wxStaticText* restart = add_label_to_sizer (table, _misc_panel, _("(restart DCP-o-matic to see language changes)"), false);
- wxFont font = restart->GetFont();
- font.SetStyle (wxFONTSTYLE_ITALIC);
- font.SetPointSize (font.GetPointSize() - 1);
- restart->SetFont (font);
- table->AddSpacer (0);
-
- add_label_to_sizer (table, _misc_panel, _("Threads to use for encoding on this host"), true);
- _num_local_encoding_threads = new wxSpinCtrl (_misc_panel);
- table->Add (_num_local_encoding_threads, 1);
-
- add_label_to_sizer (table, _misc_panel, _("Outgoing mail server"), true);
- _mail_server = new wxTextCtrl (_misc_panel, wxID_ANY);
- table->Add (_mail_server, 1, wxEXPAND | wxALL);
-
- add_label_to_sizer (table, _misc_panel, _("Mail user name"), true);
- _mail_user = new wxTextCtrl (_misc_panel, wxID_ANY);
- table->Add (_mail_user, 1, wxEXPAND | wxALL);
-
- add_label_to_sizer (table, _misc_panel, _("Mail password"), true);
- _mail_password = new wxTextCtrl (_misc_panel, wxID_ANY);
- table->Add (_mail_password, 1, wxEXPAND | wxALL);
-
- wxStaticText* plain = add_label_to_sizer (table, _misc_panel, _("(password will be stored on disk in plaintext)"), false);
- plain->SetFont (font);
- table->AddSpacer (0);
-
- add_label_to_sizer (table, _misc_panel, _("From address for KDM emails"), true);
- _kdm_from = new wxTextCtrl (_misc_panel, wxID_ANY);
- table->Add (_kdm_from, 1, wxEXPAND | wxALL);
+ public:
+ GeneralPage (wxSize panel_size, int border)
+ : wxStockPreferencesPage (Kind_General)
+ , Page (panel_size, border)
+ {}
- _check_for_updates = new wxCheckBox (_misc_panel, wxID_ANY, _("Check for updates on startup"));
- table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
- table->AddSpacer (0);
+ wxWindow* CreateWindow (wxWindow* parent)
+ {
+ wxPanel* panel = new wxPanel (parent);
+ wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+ panel->SetSizer (s);
+
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ table->AddGrowableCol (1, 1);
+ s->Add (table, 1, wxALL | wxEXPAND, _border);
+
+ _set_language = new wxCheckBox (panel, wxID_ANY, _("Set language"));
+ table->Add (_set_language, 1);
+ _language = new wxChoice (panel, wxID_ANY);
+ _language->Append (wxT ("English"));
+ _language->Append (wxT ("Français"));
+ _language->Append (wxT ("Italiano"));
+ _language->Append (wxT ("Español"));
+ _language->Append (wxT ("Svenska"));
+ _language->Append (wxT ("Deutsch"));
+ table->Add (_language);
+
+ wxStaticText* restart = add_label_to_sizer (table, panel, _("(restart DCP-o-matic to see language changes)"), false);
+ wxFont font = restart->GetFont();
+ font.SetStyle (wxFONTSTYLE_ITALIC);
+ font.SetPointSize (font.GetPointSize() - 1);
+ restart->SetFont (font);
+ table->AddSpacer (0);
+
+ add_label_to_sizer (table, panel, _("Threads to use for encoding on this host"), true);
+ _num_local_encoding_threads = new wxSpinCtrl (panel);
+ table->Add (_num_local_encoding_threads, 1);
+
+ add_label_to_sizer (table, panel, _("Outgoing mail server"), true);
+ _mail_server = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_mail_server, 1, wxEXPAND | wxALL);
+
+ add_label_to_sizer (table, panel, _("Mail user name"), true);
+ _mail_user = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_mail_user, 1, wxEXPAND | wxALL);
+
+ add_label_to_sizer (table, panel, _("Mail password"), true);
+ _mail_password = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_mail_password, 1, wxEXPAND | wxALL);
+
+ wxStaticText* plain = add_label_to_sizer (table, panel, _("(password will be stored on disk in plaintext)"), false);
+ plain->SetFont (font);
+ table->AddSpacer (0);
+
+ add_label_to_sizer (table, panel, _("From address for KDM emails"), true);
+ _kdm_from = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_kdm_from, 1, wxEXPAND | wxALL);
+
+ _check_for_updates = new wxCheckBox (panel, wxID_ANY, _("Check for updates on startup"));
+ table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
+ table->AddSpacer (0);
+
+ _check_for_test_updates = new wxCheckBox (panel, wxID_ANY, _("Check for testing updates as well as stable ones"));
+ table->Add (_check_for_test_updates, 1, wxEXPAND | wxALL);
+ table->AddSpacer (0);
+
+ Config* config = Config::instance ();
+
+ _set_language->SetValue (config->language ());
+
+ if (config->language().get_value_or ("") == "fr") {
+ _language->SetSelection (1);
+ } else if (config->language().get_value_or ("") == "it") {
+ _language->SetSelection (2);
+ } else if (config->language().get_value_or ("") == "es") {
+ _language->SetSelection (3);
+ } else if (config->language().get_value_or ("") == "sv") {
+ _language->SetSelection (4);
+ } else if (config->language().get_value_or ("") == "de") {
+ _language->SetSelection (5);
+ } else {
+ _language->SetSelection (0);
+ }
+
+ setup_language_sensitivity ();
+
+ _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::set_language_changed, this));
+ _language->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&GeneralPage::language_changed, this));
+
+ _num_local_encoding_threads->SetRange (1, 128);
+ _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
+ _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
+
+ _mail_server->SetValue (std_to_wx (config->mail_server ()));
+ _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::mail_server_changed, this));
+ _mail_user->SetValue (std_to_wx (config->mail_user ()));
+ _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::mail_user_changed, this));
+ _mail_password->SetValue (std_to_wx (config->mail_password ()));
+ _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::mail_password_changed, this));
+ _kdm_from->SetValue (std_to_wx (config->kdm_from ()));
+ _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::kdm_from_changed, this));
+ _check_for_updates->SetValue (config->check_for_updates ());
+ _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
+ _check_for_test_updates->SetValue (config->check_for_test_updates ());
+ _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
+
+ return panel;
+ }
- _check_for_test_updates = new wxCheckBox (_misc_panel, wxID_ANY, _("Check for testing updates as well as stable ones"));
- table->Add (_check_for_test_updates, 1, wxEXPAND | wxALL);
- table->AddSpacer (0);
-
- Config* config = Config::instance ();
+ private:
+ void setup_language_sensitivity ()
+ {
+ _language->Enable (_set_language->GetValue ());
+ }
- _set_language->SetValue (config->language ());
+ void set_language_changed ()
+ {
+ setup_language_sensitivity ();
+ if (_set_language->GetValue ()) {
+ language_changed ();
+ } else {
+ Config::instance()->unset_language ();
+ }
+ }
- if (config->language().get_value_or ("") == "fr") {
- _language->SetSelection (1);
- } else if (config->language().get_value_or ("") == "it") {
- _language->SetSelection (2);
- } else if (config->language().get_value_or ("") == "es") {
- _language->SetSelection (3);
- } else if (config->language().get_value_or ("") == "sv") {
- _language->SetSelection (4);
- } else if (config->language().get_value_or ("") == "de") {
- _language->SetSelection (5);
- } else {
- _language->SetSelection (0);
- }
-
- setup_language_sensitivity ();
-
- _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ConfigDialog::set_language_changed, this));
- _language->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ConfigDialog::language_changed, this));
-
- _num_local_encoding_threads->SetRange (1, 128);
- _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
- _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::num_local_encoding_threads_changed, this));
-
- _mail_server->SetValue (std_to_wx (config->mail_server ()));
- _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::mail_server_changed, this));
- _mail_user->SetValue (std_to_wx (config->mail_user ()));
- _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::mail_user_changed, this));
- _mail_password->SetValue (std_to_wx (config->mail_password ()));
- _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::mail_password_changed, this));
- _kdm_from->SetValue (std_to_wx (config->kdm_from ()));
- _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::kdm_from_changed, this));
- _check_for_updates->SetValue (config->check_for_updates ());
- _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ConfigDialog::check_for_updates_changed, this));
- _check_for_test_updates->SetValue (config->check_for_test_updates ());
- _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ConfigDialog::check_for_test_updates_changed, this));
- }
+ void language_changed ()
+ {
+ switch (_language->GetSelection ()) {
+ case 0:
+ Config::instance()->set_language ("en");
+ break;
+ case 1:
+ Config::instance()->set_language ("fr");
+ break;
+ case 2:
+ Config::instance()->set_language ("it");
+ break;
+ case 3:
+ Config::instance()->set_language ("es");
+ break;
+ case 4:
+ Config::instance()->set_language ("sv");
+ break;
+ case 5:
+ Config::instance()->set_language ("de");
+ break;
+ }
+ }
+
+ void mail_server_changed ()
+ {
+ Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
+ }
+
+ void mail_user_changed ()
+ {
+ Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
+ }
+
+ void mail_password_changed ()
+ {
+ Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
+ }
+
+ void kdm_from_changed ()
+ {
+ Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
+ }
- void
- ConfigDialog::make_defaults_panel ()
- {
- _defaults_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _defaults_panel->SetSizer (s);
+ void check_for_updates_changed ()
+ {
+ Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
+ }
+
+ void check_for_test_updates_changed ()
+ {
+ Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
+ }
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
- table->AddGrowableCol (1, 1);
- s->Add (table, 1, wxALL | wxEXPAND, 8);
+ void num_local_encoding_threads_changed ()
+ {
+ Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
+ }
+
+ wxCheckBox* _set_language;
+ wxChoice* _language;
+ wxSpinCtrl* _num_local_encoding_threads;
+ wxTextCtrl* _mail_server;
+ wxTextCtrl* _mail_user;
+ wxTextCtrl* _mail_password;
+ wxTextCtrl* _kdm_from;
+ wxCheckBox* _check_for_updates;
+ wxCheckBox* _check_for_test_updates;
+ };
+
+ class DefaultsPage : public wxPreferencesPage, public Page
+ {
+ public:
+ DefaultsPage (wxSize panel_size, int border)
+ : Page (panel_size, border)
+ {}
+
+ wxString GetName () const
+ {
+ return _("Defaults");
+ }
+ #ifdef DCPOMATIC_OSX
+ wxBitmap GetLargeIcon () const
{
- add_label_to_sizer (table, _defaults_panel, _("Default duration of still images"), true);
- wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _default_still_length = new wxSpinCtrl (_defaults_panel);
- s->Add (_default_still_length);
- add_label_to_sizer (s, _defaults_panel, _("s"), false);
- table->Add (s, 1);
+ return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
}
+ #endif
- add_label_to_sizer (table, _defaults_panel, _("Default directory for new films"), true);
+ wxWindow* CreateWindow (wxWindow* parent)
+ {
+ wxPanel* panel = new wxPanel (parent);
+ wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+ panel->SetSizer (s);
+
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ table->AddGrowableCol (1, 1);
+ s->Add (table, 1, wxALL | wxEXPAND, _border);
+
+ {
+ add_label_to_sizer (table, panel, _("Default duration of still images"), true);
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _still_length = new wxSpinCtrl (panel);
+ s->Add (_still_length);
+ add_label_to_sizer (s, panel, _("s"), false);
+ table->Add (s, 1);
+ }
+
+ add_label_to_sizer (table, panel, _("Default directory for new films"), true);
#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
- _default_directory = new DirPickerCtrl (_defaults_panel);
+ _directory = new DirPickerCtrl (panel);
#else
- _default_directory = new wxDirPickerCtrl (_defaults_panel, wxDD_DIR_MUST_EXIST);
+ _directory = new wxDirPickerCtrl (panel, wxDD_DIR_MUST_EXIST);
#endif
- table->Add (_default_directory, 1, wxEXPAND);
-
- add_label_to_sizer (table, _defaults_panel, _("Default DCI name details"), true);
- _default_dci_metadata_button = new wxButton (_defaults_panel, wxID_ANY, _("Edit..."));
- table->Add (_default_dci_metadata_button);
+ table->Add (_directory, 1, wxEXPAND);
+
+ add_label_to_sizer (table, panel, _("Default DCI name details"), true);
+ _dci_metadata_button = new wxButton (panel, wxID_ANY, _("Edit..."));
+ table->Add (_dci_metadata_button);
+
+ add_label_to_sizer (table, panel, _("Default container"), true);
+ _container = new wxChoice (panel, wxID_ANY);
+ table->Add (_container);
+
+ add_label_to_sizer (table, panel, _("Default content type"), true);
+ _dcp_content_type = new wxChoice (panel, wxID_ANY);
+ table->Add (_dcp_content_type);
+
+ {
+ add_label_to_sizer (table, panel, _("Default JPEG2000 bandwidth"), true);
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _j2k_bandwidth = new wxSpinCtrl (panel);
+ s->Add (_j2k_bandwidth);
+ add_label_to_sizer (s, panel, _("Mbit/s"), false);
+ table->Add (s, 1);
+ }
+
+ {
+ add_label_to_sizer (table, panel, _("Default audio delay"), true);
+ wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ _audio_delay = new wxSpinCtrl (panel);
+ s->Add (_audio_delay);
+ add_label_to_sizer (s, panel, _("ms"), false);
+ table->Add (s, 1);
+ }
- add_label_to_sizer (table, _defaults_panel, _("Default container"), true);
- _default_container = new wxChoice (_defaults_panel, wxID_ANY);
- table->Add (_default_container);
+ add_label_to_sizer (table, panel, _("Default issuer"), true);
+ _issuer = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_issuer, 1, wxEXPAND);
+
+ add_label_to_sizer (table, panel, _("Default creator"), true);
+ _creator = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_creator, 1, wxEXPAND);
+
+ Config* config = Config::instance ();
+
+ _still_length->SetRange (1, 3600);
+ _still_length->SetValue (config->default_still_length ());
+ _still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::still_length_changed, this));
+
+ _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
+ _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
+
+ _dci_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_dci_metadata_clicked, this, parent));
+
+ vector<Ratio const *> ratio = Ratio::all ();
+ int n = 0;
+ for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
+ _container->Append (std_to_wx ((*i)->nickname ()));
+ if (*i == config->default_container ()) {
+ _container->SetSelection (n);
+ }
+ ++n;
+ }
+
+ _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
+
+ vector<DCPContentType const *> const ct = DCPContentType::all ();
+ n = 0;
+ for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
+ _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
+ if (*i == config->default_dcp_content_type ()) {
+ _dcp_content_type->SetSelection (n);
+ }
+ ++n;
+ }
+
+ _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
+
+ _j2k_bandwidth->SetRange (50, 250);
+ _j2k_bandwidth->SetValue (config->default_j2k_bandwidth() / 1000000);
+ _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
+
+ _audio_delay->SetRange (-1000, 1000);
+ _audio_delay->SetValue (config->default_audio_delay ());
+ _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
+
+ _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
+ _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::issuer_changed, this));
+ _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
+ _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::creator_changed, this));
+
+ return panel;
+ }
- add_label_to_sizer (table, _defaults_panel, _("Default content type"), true);
- _default_dcp_content_type = new wxChoice (_defaults_panel, wxID_ANY);
- table->Add (_default_dcp_content_type);
+ private:
+ void j2k_bandwidth_changed ()
+ {
+ Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
+ }
+
+ void audio_delay_changed ()
+ {
+ Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
+ }
+ void directory_changed ()
{
- add_label_to_sizer (table, _defaults_panel, _("Default JPEG2000 bandwidth"), true);
- wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _default_j2k_bandwidth = new wxSpinCtrl (_defaults_panel);
- s->Add (_default_j2k_bandwidth);
- add_label_to_sizer (s, _defaults_panel, _("Mbit/s"), false);
- table->Add (s, 1);
+ Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
}
+ void edit_dci_metadata_clicked (wxWindow* parent)
{
- add_label_to_sizer (table, _defaults_panel, _("Default audio delay"), true);
- wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _default_audio_delay = new wxSpinCtrl (_defaults_panel);
- s->Add (_default_audio_delay);
- add_label_to_sizer (s, _defaults_panel, _("ms"), false);
- table->Add (s, 1);
+ DCIMetadataDialog* d = new DCIMetadataDialog (parent, Config::instance()->default_dci_metadata ());
+ d->ShowModal ();
+ Config::instance()->set_default_dci_metadata (d->dci_metadata ());
+ d->Destroy ();
}
- Config* config = Config::instance ();
+ void still_length_changed ()
+ {
+ Config::instance()->set_default_still_length (_still_length->GetValue ());
+ }
- _default_still_length->SetRange (1, 3600);
- _default_still_length->SetValue (config->default_still_length ());
- _default_still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::default_still_length_changed, this));
-
- _default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
- _default_directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&ConfigDialog::default_directory_changed, this));
-
- _default_dci_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ConfigDialog::edit_default_dci_metadata_clicked, this));
-
- vector<Ratio const *> ratio = Ratio::all ();
- int n = 0;
- for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
- _default_container->Append (std_to_wx ((*i)->nickname ()));
- if (*i == config->default_container ()) {
- _default_container->SetSelection (n);
- }
- ++n;
+ void container_changed ()
+ {
+ vector<Ratio const *> ratio = Ratio::all ();
+ Config::instance()->set_default_container (ratio[_container->GetSelection()]);
}
-
- _default_container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ConfigDialog::default_container_changed, this));
- vector<DCPContentType const *> const ct = DCPContentType::all ();
- n = 0;
- for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
- _default_dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
- if (*i == config->default_dcp_content_type ()) {
- _default_dcp_content_type->SetSelection (n);
- }
- ++n;
+ void dcp_content_type_changed ()
+ {
+ vector<DCPContentType const *> ct = DCPContentType::all ();
+ Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
}
- _default_dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ConfigDialog::default_dcp_content_type_changed, this));
-
- _default_j2k_bandwidth->SetRange (50, 250);
- _default_j2k_bandwidth->SetValue (config->default_j2k_bandwidth() / 1000000);
- _default_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::default_j2k_bandwidth_changed, this));
-
- _default_audio_delay->SetRange (-1000, 1000);
- _default_audio_delay->SetValue (config->default_audio_delay ());
- _default_audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ConfigDialog::default_audio_delay_changed, this));
- }
-
- void
- ConfigDialog::make_tms_panel ()
- {
- _tms_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _tms_panel->SetSizer (s);
-
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
- table->AddGrowableCol (1, 1);
- s->Add (table, 1, wxALL | wxEXPAND, 8);
-
- add_label_to_sizer (table, _tms_panel, _("IP address"), true);
- _tms_ip = new wxTextCtrl (_tms_panel, wxID_ANY);
- table->Add (_tms_ip, 1, wxEXPAND);
-
- add_label_to_sizer (table, _tms_panel, _("Target path"), true);
- _tms_path = new wxTextCtrl (_tms_panel, wxID_ANY);
- table->Add (_tms_path, 1, wxEXPAND);
-
- add_label_to_sizer (table, _tms_panel, _("User name"), true);
- _tms_user = new wxTextCtrl (_tms_panel, wxID_ANY);
- table->Add (_tms_user, 1, wxEXPAND);
-
- add_label_to_sizer (table, _tms_panel, _("Password"), true);
- _tms_password = new wxTextCtrl (_tms_panel, wxID_ANY);
- table->Add (_tms_password, 1, wxEXPAND);
-
- Config* config = Config::instance ();
+ void issuer_changed ()
+ {
+ libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+ m.issuer = wx_to_std (_issuer->GetValue ());
+ Config::instance()->set_dcp_metadata (m);
+ }
- _tms_ip->SetValue (std_to_wx (config->tms_ip ()));
- _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_ip_changed, this));
- _tms_path->SetValue (std_to_wx (config->tms_path ()));
- _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_path_changed, this));
- _tms_user->SetValue (std_to_wx (config->tms_user ()));
- _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_user_changed, this));
- _tms_password->SetValue (std_to_wx (config->tms_password ()));
- _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::tms_password_changed, this));
- }
-
- void
- ConfigDialog::make_metadata_panel ()
- {
- _metadata_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _metadata_panel->SetSizer (s);
-
- wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
- table->AddGrowableCol (1, 1);
- s->Add (table, 1, wxALL | wxEXPAND, 8);
-
- add_label_to_sizer (table, _metadata_panel, _("Issuer"), true);
- _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
- table->Add (_issuer, 1, wxEXPAND);
-
- add_label_to_sizer (table, _metadata_panel, _("Creator"), true);
- _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
- table->Add (_creator, 1, wxEXPAND);
-
- Config* config = Config::instance ();
-
- _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
- _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::issuer_changed, this));
- _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
- _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::creator_changed, this));
- }
-
- static string
- server_column (string s)
- {
- return s;
- }
-
- void
- ConfigDialog::make_servers_panel ()
- {
- _servers_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _servers_panel->SetSizer (s);
-
- _use_any_servers = new wxCheckBox (_servers_panel, wxID_ANY, _("Use all servers"));
- s->Add (_use_any_servers, 0, wxALL, DCPOMATIC_SIZER_X_GAP);
+ void creator_changed ()
+ {
+ libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+ m.creator = wx_to_std (_creator->GetValue ());
+ Config::instance()->set_dcp_metadata (m);
+ }
- vector<string> columns;
- columns.push_back (wx_to_std (_("IP address / host name")));
- _servers_list = new EditableList<std::string, ServerDialog> (
- _servers_panel,
- columns,
- boost::bind (&Config::servers, Config::instance()),
- boost::bind (&Config::set_servers, Config::instance(), _1),
- boost::bind (&server_column, _1)
- );
-
- s->Add (_servers_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_X_GAP);
-
- _use_any_servers->SetValue (Config::instance()->use_any_servers ());
- _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ConfigDialog::use_any_servers_changed, this));
- }
-
- void
- ConfigDialog::use_any_servers_changed ()
- {
- Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
- }
-
- void
- ConfigDialog::language_changed ()
- {
- switch (_language->GetSelection ()) {
- case 0:
- Config::instance()->set_language ("en");
- break;
- case 1:
- Config::instance()->set_language ("fr");
- break;
- case 2:
- Config::instance()->set_language ("it");
- break;
- case 3:
- Config::instance()->set_language ("es");
- break;
- case 4:
- Config::instance()->set_language ("sv");
- break;
- case 5:
- Config::instance()->set_language ("de");
- break;
+ wxSpinCtrl* _j2k_bandwidth;
+ wxSpinCtrl* _audio_delay;
+ wxButton* _dci_metadata_button;
+ wxSpinCtrl* _still_length;
+ #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+ DirPickerCtrl* _directory;
+ #else
+ wxDirPickerCtrl* _directory;
+ #endif
+ wxChoice* _container;
+ wxChoice* _dcp_content_type;
+ wxTextCtrl* _issuer;
+ wxTextCtrl* _creator;
+ };
+
+ class EncodingServersPage : public wxPreferencesPage, public Page
+ {
+ public:
+ EncodingServersPage (wxSize panel_size, int border)
+ : Page (panel_size, border)
+ {}
+
+ wxString GetName () const
+ {
+ return _("Servers");
}
- }
-
- void
- ConfigDialog::tms_ip_changed ()
- {
- Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
- }
-
- void
- ConfigDialog::tms_path_changed ()
- {
- Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
- }
-
- void
- ConfigDialog::tms_user_changed ()
- {
- Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
- }
- void
- ConfigDialog::tms_password_changed ()
- {
- Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
- }
+ #ifdef DCPOMATIC_OSX
+ wxBitmap GetLargeIcon () const
+ {
+ return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
+ }
+ #endif
- void
- ConfigDialog::num_local_encoding_threads_changed ()
- {
- Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
- }
+ wxWindow* CreateWindow (wxWindow* parent)
+ {
+ wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
+ wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+ panel->SetSizer (s);
+
+ _use_any_servers = new wxCheckBox (panel, wxID_ANY, _("Use all servers"));
+ s->Add (_use_any_servers, 0, wxALL, _border);
+
+ vector<string> columns;
+ columns.push_back (wx_to_std (_("IP address / host name")));
+ _servers_list = new EditableList<string, ServerDialog> (
+ panel,
+ columns,
+ boost::bind (&Config::servers, Config::instance()),
+ boost::bind (&Config::set_servers, Config::instance(), _1),
+ boost::bind (&EncodingServersPage::server_column, this, _1)
+ );
+
+ s->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
+
+ _use_any_servers->SetValue (Config::instance()->use_any_servers ());
+ _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
+
+ return panel;
+ }
- void
- ConfigDialog::default_directory_changed ()
- {
- Config::instance()->set_default_directory (wx_to_std (_default_directory->GetPath ()));
- }
+ private:
- void
- ConfigDialog::edit_default_dci_metadata_clicked ()
- {
- DCIMetadataDialog* d = new DCIMetadataDialog (this, Config::instance()->default_dci_metadata ());
- d->ShowModal ();
- Config::instance()->set_default_dci_metadata (d->dci_metadata ());
- d->Destroy ();
- }
-
- void
- ConfigDialog::set_language_changed ()
- {
- setup_language_sensitivity ();
- if (_set_language->GetValue ()) {
- language_changed ();
- } else {
- Config::instance()->unset_language ();
+ void use_any_servers_changed ()
+ {
+ Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
}
- }
-
- void
- ConfigDialog::setup_language_sensitivity ()
- {
- _language->Enable (_set_language->GetValue ());
- }
- void
- ConfigDialog::default_still_length_changed ()
- {
- Config::instance()->set_default_still_length (_default_still_length->GetValue ());
- }
+ string server_column (string s)
+ {
+ return s;
+ }
- void
- ConfigDialog::default_container_changed ()
- {
- vector<Ratio const *> ratio = Ratio::all ();
- Config::instance()->set_default_container (ratio[_default_container->GetSelection()]);
- }
+ wxCheckBox* _use_any_servers;
+ EditableList<string, ServerDialog>* _servers_list;
+ };
- void
- ConfigDialog::default_dcp_content_type_changed ()
+ class ColourConversionsPage : public wxPreferencesPage, public Page
{
- vector<DCPContentType const *> ct = DCPContentType::all ();
- Config::instance()->set_default_dcp_content_type (ct[_default_dcp_content_type->GetSelection()]);
- }
+ public:
+ ColourConversionsPage (wxSize panel_size, int border)
+ : Page (panel_size, border)
+ {}
+
+ wxString GetName () const
+ {
+ return _("Colour Conversions");
+ }
- void
- ConfigDialog::issuer_changed ()
- {
- dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
- m.issuer = wx_to_std (_issuer->GetValue ());
- Config::instance()->set_dcp_metadata (m);
- }
+ #ifdef DCPOMATIC_OSX
+ wxBitmap GetLargeIcon () const
+ {
+ return wxBitmap ("colour_conversions", wxBITMAP_TYPE_PNG_RESOURCE);
+ }
+ #endif
+ wxWindow* CreateWindow (wxWindow* parent)
+ {
+ wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
+ wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+ panel->SetSizer (s);
+
+ vector<string> columns;
+ columns.push_back (wx_to_std (_("Name")));
+ wxPanel* list = new EditableList<PresetColourConversion, PresetColourConversionDialog> (
+ panel,
+ columns,
+ boost::bind (&Config::colour_conversions, Config::instance()),
+ boost::bind (&Config::set_colour_conversions, Config::instance(), _1),
+ boost::bind (&ColourConversionsPage::colour_conversion_column, this, _1),
+ 300
+ );
+
+ s->Add (list, 1, wxEXPAND | wxALL, _border);
+ return panel;
+ }
- void
- ConfigDialog::creator_changed ()
- {
- dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
- m.creator = wx_to_std (_creator->GetValue ());
- Config::instance()->set_dcp_metadata (m);
- }
+ private:
+ string colour_conversion_column (PresetColourConversion c)
+ {
+ return c.name;
+ }
+ };
- void
- ConfigDialog::default_j2k_bandwidth_changed ()
+ class TMSPage : public wxPreferencesPage, public Page
{
- Config::instance()->set_default_j2k_bandwidth (_default_j2k_bandwidth->GetValue() * 1000000);
- }
+ public:
+ TMSPage (wxSize panel_size, int border)
+ : Page (panel_size, border)
+ {}
- void
- ConfigDialog::default_audio_delay_changed ()
- {
- Config::instance()->set_default_audio_delay (_default_audio_delay->GetValue());
- }
+ wxString GetName () const
+ {
+ return _("TMS");
+ }
- static std::string
- colour_conversion_column (PresetColourConversion c)
- {
- return c.name;
- }
+ #ifdef DCPOMATIC_OSX
+ wxBitmap GetLargeIcon () const
+ {
+ return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
+ }
+ #endif
- void
- ConfigDialog::make_colour_conversions_panel ()
- {
- vector<string> columns;
- columns.push_back (wx_to_std (_("Name")));
- _colour_conversions_panel = new EditableList<PresetColourConversion, PresetColourConversionDialog> (
- _notebook,
- columns,
- boost::bind (&Config::colour_conversions, Config::instance()),
- boost::bind (&Config::set_colour_conversions, Config::instance(), _1),
- boost::bind (&colour_conversion_column, _1),
- 300
- );
- }
+ wxWindow* CreateWindow (wxWindow* parent)
+ {
+ wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
+ wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+ panel->SetSizer (s);
+
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ table->AddGrowableCol (1, 1);
+ s->Add (table, 1, wxALL | wxEXPAND, _border);
+
+ add_label_to_sizer (table, panel, _("IP address"), true);
+ _tms_ip = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_tms_ip, 1, wxEXPAND);
+
+ add_label_to_sizer (table, panel, _("Target path"), true);
+ _tms_path = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_tms_path, 1, wxEXPAND);
+
+ add_label_to_sizer (table, panel, _("User name"), true);
+ _tms_user = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_tms_user, 1, wxEXPAND);
+
+ add_label_to_sizer (table, panel, _("Password"), true);
+ _tms_password = new wxTextCtrl (panel, wxID_ANY);
+ table->Add (_tms_password, 1, wxEXPAND);
+
+ Config* config = Config::instance ();
+
+ _tms_ip->SetValue (std_to_wx (config->tms_ip ()));
+ _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
+ _tms_path->SetValue (std_to_wx (config->tms_path ()));
+ _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
+ _tms_user->SetValue (std_to_wx (config->tms_user ()));
+ _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
+ _tms_password->SetValue (std_to_wx (config->tms_password ()));
+ _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
+
+ return panel;
+ }
- void
- ConfigDialog::mail_server_changed ()
- {
- Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
- }
+ private:
+ void tms_ip_changed ()
+ {
+ Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
+ }
+
+ void tms_path_changed ()
+ {
+ Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
+ }
+
+ void tms_user_changed ()
+ {
+ Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
+ }
+
+ void tms_password_changed ()
+ {
+ Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
+ }
- void
- ConfigDialog::mail_user_changed ()
- {
- Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
- }
+ wxTextCtrl* _tms_ip;
+ wxTextCtrl* _tms_path;
+ wxTextCtrl* _tms_user;
+ wxTextCtrl* _tms_password;
+ };
- void
- ConfigDialog::mail_password_changed ()
+ class KDMEmailPage : public wxPreferencesPage, public Page
{
- Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
- }
+ public:
- void
- ConfigDialog::kdm_from_changed ()
- {
- Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
- }
+ KDMEmailPage (wxSize panel_size, int border)
+ : Page (panel_size, border)
+ {}
+
+ wxString GetName () const
+ {
+ return _("KDM Email");
+ }
- void
- ConfigDialog::make_kdm_email_panel ()
- {
- _kdm_email_panel = new wxPanel (_notebook);
- wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
- _kdm_email_panel->SetSizer (s);
+ #ifdef DCPOMATIC_OSX
+ wxBitmap GetLargeIcon () const
+ {
+ return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
+ }
+ #endif
- _kdm_email = new wxTextCtrl (_kdm_email_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
- s->Add (_kdm_email, 1, wxEXPAND | wxALL, 12);
+ wxWindow* CreateWindow (wxWindow* parent)
+ {
+ /* We have to force both width and height of this one */
+ #ifdef DCPOMATIC_OSX
+ wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize (480, 128));
+ #else
+ wxPanel* panel = new wxPanel (parent);
+ #endif
+ wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+ panel->SetSizer (s);
+
+ _kdm_email = new wxTextCtrl (panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
+ s->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
+
+ _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
+ _kdm_email->SetValue (wx_to_std (Config::instance()->kdm_email ()));
+
+ return panel;
+ }
- _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ConfigDialog::kdm_email_changed, this));
- _kdm_email->SetValue (wx_to_std (Config::instance()->kdm_email ()));
- }
+ private:
+ void kdm_email_changed ()
+ {
+ Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
+ }
- void
- ConfigDialog::kdm_email_changed ()
- {
- Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
- }
+ wxTextCtrl* _kdm_email;
+ };
- void
- ConfigDialog::check_for_updates_changed ()
+ wxPreferencesEditor*
+ create_config_dialog ()
{
- Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
- }
+ wxPreferencesEditor* e = new wxPreferencesEditor ();
- void
- ConfigDialog::check_for_test_updates_changed ()
- {
- Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
+ #ifdef DCPOMATIC_OSX
+ /* Width that we force some of the config panels to be on OSX so that
+ the containing window doesn't shrink too much when we select those panels.
+ This is obviously an unpleasant hack.
+ */
+ wxSize ps = wxSize (480, -1);
+ int const border = 16;
+ #else
+ wxSize ps = wxDefaultSize;
+ int const border = 8;
+ #endif
+
+ e->AddPage (new GeneralPage (ps, border));
+ e->AddPage (new DefaultsPage (ps, border));
+ e->AddPage (new EncodingServersPage (ps, border));
+ e->AddPage (new ColourConversionsPage (ps, border));
+ e->AddPage (new TMSPage (ps, border));
+ e->AddPage (new KDMEmailPage (ps, border));
+ return e;
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
#include "lib/player.h"
#include "lib/video_content.h"
#include "lib/video_decoder.h"
+#include "lib/timer.h"
#include "film_viewer.h"
#include "wx_util.h"
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
: wxPanel (p)
_frame.reset ();
_slider->SetValue (0);
- set_position_text (0);
+ set_position_text (DCPTime ());
if (!_film) {
return;
}
_player->disable_audio ();
+ _player->set_approximate_size ();
_player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5));
_player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
fetch_next_frame ();
- Time const len = _film->length ();
+ DCPTime const len = _film->length ();
- if (len) {
- int const new_slider_position = 4096 * _player->video_position() / len;
+ if (len.get ()) {
+ int const new_slider_position = 4096 * _player->video_position().get() / len.get();
if (new_slider_position != _slider->GetValue()) {
_slider->SetValue (new_slider_position);
}
FilmViewer::slider_moved ()
{
if (_film && _player) {
- _player->seek (_slider->GetValue() * _film->length() / 4096, false);
- fetch_next_frame ();
+ try {
+ _player->seek (DCPTime (_film->length().get() * _slider->GetValue() / 4096), false);
+ fetch_next_frame ();
+ } catch (OpenFileError& e) {
+ /* There was a problem opening a content file; we'll let this slide as it
+ probably means a missing content file, which we're already taking care of.
+ */
+ }
}
}
_out_size.width = max (64, _out_size.width);
_out_size.height = max (64, _out_size.height);
+ /* The player will round its image down to the nearest 4 pixels
+ to speed up its scale, so do similar here to avoid black borders
+ around things. This is a bit of a hack.
+ */
+ _out_size.width &= ~3;
+ _out_size.height &= ~3;
+
_player->set_video_container_size (_out_size);
}
}
void
-FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t)
+FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, DCPTime t)
{
if (eyes == EYES_RIGHT) {
return;
}
-
- _frame = image->image ();
+
+ /* Going via BGRA here makes the scaler faster then using RGB24 directly (about
+ twice on x86 Linux).
+ */
+ shared_ptr<Image> im = image->image (PIX_FMT_BGRA, true);
+ _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
_got_frame = true;
set_position_text (t);
}
void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text (DCPTime t)
{
if (!_film) {
_frame_number->SetLabel ("0");
double const fps = _film->video_frame_rate ();
/* Count frame number from 1 ... not sure if this is the best idea */
- _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
+ _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t.seconds() * fps)) + 1));
- double w = static_cast<double>(t) / TIME_HZ;
+ double w = t.seconds ();
int const h = (w / 3600);
w -= h * 3600;
int const m = (w / 60);
We want to see the one before it, so we need to go back 2.
*/
- Time p = _player->video_position() - _film->video_frames_to_time (2);
- if (p < 0) {
- p = 0;
+ DCPTime p = _player->video_position() - DCPTime::from_frames (2, _film->video_frame_rate ());
+ if (p < DCPTime ()) {
+ p = DCPTime ();
}
- _player->seek (p, true);
- fetch_next_frame ();
+ try {
+ _player->seek (p, true);
+ fetch_next_frame ();
+ } catch (OpenFileError& e) {
+ /* There was a problem opening a content file; we'll let this slide as it
+ probably means a missing content file, which we're already taking care of.
+ */
+ }
}
void
if (frequent) {
return;
}
-
+
calculate_sizes ();
fetch_current_frame_again ();
}
_colour_conversion->SetLabel (preset ? std_to_wx (cc[preset.get()].name) : _("Custom"));
} else if (property == FFmpegContentProperty::FILTERS) {
if (fcs) {
- pair<string, string> p = Filter::ffmpeg_strings (fcs->filters ());
- if (p.first.empty () && p.second.empty ()) {
+ string const p = Filter::ffmpeg_string (fcs->filters ());
+ if (p.empty ()) {
_filters->SetLabel (_("None"));
} else {
- string const b = p.first + " " + p.second;
- _filters->SetLabel (std_to_wx (b));
+ _filters->SetLabel (std_to_wx (p));
}
}
}
}
Crop const crop = vcs->crop ();
- if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
- libdcp::Size cropped = vcs->video_size_after_crop ();
+ if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
+ dcp::Size cropped = vcs->video_size_after_crop ();
d << wxString::Format (
_("Cropped to %dx%d (%.2f:1)\n"),
cropped.width, cropped.height,
++lines;
}
- dcp::Size const container_size = fit_ratio_within (_editor->film()->container()->ratio (), _editor->film()->full_frame ());
- dcp::Size const scaled = vcs->scale().size (vcs, container_size);
- libdcp::Size const container_size = _editor->film()->frame_size ();
- libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
++ dcp::Size const container_size = _editor->film()->frame_size ();
++ dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
if (scaled != vcs->video_size_after_crop ()) {
d << wxString::Format (
d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
++lines;
- FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+ FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
d << std_to_wx (frc.description) << "\n";
++lines;
import sys
APPNAME = 'dcpomatic'
-VERSION = '1.66.1devel'
+VERSION = '2.0.0devel'
def options(opt):
opt.load('compiler_cxx')
conf.env.STLIB_SWSCALE = ['swscale']
conf.check_cfg(package='libswresample', args='--cflags', uselib_store='SWRESAMPLE', mandatory=True)
conf.env.STLIB_SWRESAMPLE = ['swresample']
- conf.check_cfg(package='libpostproc', args='--cflags', uselib_store='POSTPROC', mandatory=True)
- conf.env.STLIB_POSTPROC = ['postproc']
def dynamic_ffmpeg(conf):
conf.check_cfg(package='libavformat', args='--cflags --libs', uselib_store='AVFORMAT', mandatory=True)
conf.check_cfg(package='libavutil', args='--cflags --libs', uselib_store='AVUTIL', mandatory=True)
conf.check_cfg(package='libswscale', args='--cflags --libs', uselib_store='SWSCALE', mandatory=True)
conf.check_cfg(package='libswresample', args='--cflags --libs', uselib_store='SWRESAMPLE', mandatory=True)
- conf.check_cfg(package='libpostproc', args='--cflags --libs', uselib_store='POSTPROC', mandatory=True)
def static_openjpeg(conf):
conf.check_cfg(package='libopenjpeg', args='--cflags', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.1', mandatory=True)
def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
- conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
+ conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
- conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+ conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp', 'kumu-libdcp']
conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
if static_boost:
conf.env.LIB_DCP.append('ssh')
def dynamic_dcp(conf):
- conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+ conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
def dynamic_ssh(conf):
conf.check_cfg(package='glib-2.0', args='--cflags --libs', uselib_store='GLIB', mandatory=True)
conf.check_cfg(package= '', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
+ conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
+ conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
conf.check_cc(fragment="""
#include <glib.h>