+2014-03-07 Carl Hetherington <cth@carlh.net>
+
+ * Add subtitle view.
+
+ 2014-05-29 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.18 released.
+
+ 2014-05-28 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.17 released.
+
+ 2014-05-28 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.16 released.
+
+ 2014-05-28 Carl Hetherington <cth@carlh.net>
+
+ * Rework KDM generation to be about CPLs rather than DCPs,
+ and allow specification of any CPL to generate KDMs for.
+
+ Requested-by: Richard Turner
+
+ 2014-05-27 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.15 released.
+
+ 2014-05-26 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.14 released.
+
+ 2014-05-26 Carl Hetherington <cth@carlh.net>
+
+ * Fix problems with non-zero FFmpeg content start times.
+
+ 2014-05-24 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.13 released.
+
+ 2014-05-24 Carl Hetherington <cth@carlh.net>
+
+ * Fix problems with log setup from config.
+
+ 2014-05-23 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.12 released.
+
+ 2014-05-22 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.11 released.
+
+ 2014-05-21 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.69.10 released.
+
+ 2014-05-21 Carl Hetherington <cth@carlh.net>
+
+ * Tidy up logging a bit and make it configurable from the GUI
+ (moving a few things into an Advanced preferences tab at
+ the same time).
+
2014-05-19 Carl Hetherington <cth@carlh.net>
* Version 1.69.9 released.
#include <glib.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include "config.h"
#include "server.h"
using boost::optional;
using boost::algorithm::is_any_of;
using boost::algorithm::split;
-using libdcp::raw_convert;
+using dcp::raw_convert;
Config* Config::_instance = 0;
, _check_for_updates (false)
, _check_for_test_updates (false)
, _maximum_j2k_bandwidth (250000000)
+ , _log_types (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR)
{
_allowed_dcp_frame_rates.push_back (24);
_allowed_dcp_frame_rates.push_back (25);
_allowed_dcp_frame_rates.push_back (50);
_allowed_dcp_frame_rates.push_back (60);
- _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
- _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
- _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
}
void
/* Loading version 0 (before Rec. 709 was added as a preset).
Add it in.
*/
- _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
}
list<cxml::NodePtr> cin = f.node_children ("Cinema");
_maximum_j2k_bandwidth = f.optional_number_child<int> ("MaximumJ2KBandwidth").get_value_or (250000000);
_allow_any_dcp_frame_rate = f.optional_bool_child ("AllowAnyDCPFrameRate");
+
+ _log_types = f.optional_number_child<int> ("LogTypes").get_value_or (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR);
}
void
root->add_child("MaximumJ2KBandwidth")->add_child_text (raw_convert<string> (_maximum_j2k_bandwidth));
root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0");
+ root->add_child("LogTypes")->add_child_text (raw_convert<string> (_log_types));
doc.write_to_file_formatted (file(false).string ());
}
#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"
return _default_dcp_content_type;
}
- libdcp::XMLMetadata dcp_metadata () const {
+ dcp::XMLMetadata dcp_metadata () const {
return _dcp_metadata;
}
int maximum_j2k_bandwidth () const {
return _maximum_j2k_bandwidth;
}
+
+ int log_types () const {
+ return _log_types;
+ }
/** @param n New number of local encoding threads */
void set_num_local_encoding_threads (int n) {
changed ();
}
- void set_dcp_metadata (libdcp::XMLMetadata m) {
+ void set_dcp_metadata (dcp::XMLMetadata m) {
_dcp_metadata = m;
changed ();
}
_maximum_j2k_bandwidth = b;
changed ();
}
+
+ void set_log_types (int t) {
+ _log_types = t;
+ changed ();
+ }
boost::filesystem::path signer_chain_directory () 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;
bool _check_for_test_updates;
/** maximum allowed J2K bandwidth in bits per second */
int _maximum_j2k_bandwidth;
+ int _log_types;
/** Singleton instance, or 0 */
static Config* _instance;
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-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
*/
+/** @file src/lib/content.h
+ * @brief Content class.
+ */
+
#ifndef DCPOMATIC_CONTENT_H
#define DCPOMATIC_CONTENT_H
#include <boost/thread/mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
#include "types.h"
+#include "dcpomatic_time.h"
namespace cxml {
class Node;
static int const TRIM_END;
};
+/** @class Content
+ * @brief A piece of content represented by one or more files on disk.
+ */
class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
{
public:
Content (boost::shared_ptr<const Film>);
- Content (boost::shared_ptr<const Film>, Time);
+ Content (boost::shared_ptr<const Film>, DCPTime);
Content (boost::shared_ptr<const Film>, boost::filesystem::path);
- Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+ Content (boost::shared_ptr<const Film>, cxml::ConstNodePtr);
Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
virtual ~Content () {}
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
- virtual Time full_length () const = 0;
+ virtual DCPTime full_length () const = 0;
virtual std::string identifier () const;
boost::shared_ptr<Content> clone () const;
return _digest;
}
- void set_position (Time);
+ void set_position (DCPTime);
- /** Time that this content starts; i.e. the time that the first
+ /** DCPTime that this content starts; i.e. the time that the first
* bit of the content (trimmed or not) will happen.
*/
- Time position () const {
+ DCPTime position () const {
boost::mutex::scoped_lock lm (_mutex);
return _position;
}
- void set_trim_start (Time);
+ void set_trim_start (DCPTime);
- Time trim_start () const {
+ DCPTime trim_start () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_start;
}
- void set_trim_end (Time);
+ void set_trim_end (DCPTime);
- Time trim_end () const {
+ DCPTime trim_end () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_end;
}
- Time end () const {
- return position() + length_after_trim() - 1;
+ DCPTime end () const {
+ return position() + length_after_trim();
}
- Time length_after_trim () const;
+ DCPTime length_after_trim () const;
void set_change_signals_frequent (bool f) {
_change_signals_frequent = f;
}
- bool trimmed (Time) const;
++ boost::shared_ptr<const Film> film () const {
++ return _film.lock ();
++ }
+
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
protected:
boost::weak_ptr<const Film> _film;
/** _mutex which should be used to protect accesses, as examine
- jobs can update content state in threads other than the main one.
- */
+ * jobs can update content state in threads other than the main one.
+ */
mutable boost::mutex _mutex;
/** Paths of our data files */
private:
std::string _digest;
- Time _position;
- Time _trim_start;
- Time _trim_end;
+ DCPTime _position;
+ DCPTime _trim_start;
+ DCPTime _trim_end;
bool _change_signals_frequent;
};
/*
- 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
#endif
#include "exceptions.h"
+ #include "i18n.h"
+
+ #define LOG_GENERAL(...) log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+ #define LOG_ERROR(...) log->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
+ #define LOG_ERROR_NC(...) log->log (__VA_ARGS__, Log::TYPE_ERROR);
+
using std::pair;
using std::list;
using std::ifstream;
HANDLE child_stderr_read;
HANDLE child_stderr_write;
if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
- log->log ("ffprobe call failed (could not CreatePipe)");
+ LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)");
return;
}
PROCESS_INFORMATION process_info;
ZeroMemory (&process_info, sizeof (process_info));
if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
- log->log ("ffprobe call failed (could not CreateProcess)");
+ LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)"));
return;
}
FILE* o = fopen_boost (out, "w");
if (!o) {
- log->log ("ffprobe call failed (could not create output file)");
+ LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
return;
}
#ifdef DCPOMATIC_LINUX
string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
- log->log (String::compose ("Probing with %1", ffprobe));
+ LOG_GENERAL (N_("Probing with %1"), ffprobe);
system (ffprobe.c_str ());
#endif
path /= "ffprobe";
string ffprobe = path.string() + " \"" + content.string() + "\" 2> \"" + out.string() + "\"";
- log->log (String::compose ("Probing with %1", ffprobe));
+ LOG_GENERAL (N_("Probing with %1"), ffprobe);
system (ffprobe.c_str ());
#endif
}
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
#include <openssl/md5.h>
-#include <libdcp/rec709_linearised_gamma_lut.h>
-#include <libdcp/srgb_linearised_gamma_lut.h>
-#include <libdcp/gamma_lut.h>
-#include <libdcp/xyz_frame.h>
-#include <libdcp/rgb_xyz.h>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/gamma_lut.h>
+#include <dcp/xyz_frame.h>
+#include <dcp/rgb_xyz.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include "film.h"
#include "dcp_video_frame.h"
#include "cross.h"
#include "player_video_frame.h"
+ #define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+
#include "i18n.h"
using std::string;
using std::stringstream;
using std::cout;
using boost::shared_ptr;
-using libdcp::Size;
-using libdcp::raw_convert;
+using boost::lexical_cast;
+using dcp::Size;
+using dcp::raw_convert;
#define DCI_COEFFICENT (48.0 / 52.37)
shared_ptr<EncodedData>
DCPVideoFrame::encode_locally ()
{
- shared_ptr<libdcp::LUT> in_lut;
- if (_frame->colour_conversion().input_gamma_linearised) {
- in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _frame->colour_conversion().input_gamma);
- } else {
- in_lut = libdcp::GammaLUT::cache.get (12, _frame->colour_conversion().input_gamma);
- }
-
+ shared_ptr<dcp::GammaLUT> in_lut = dcp::GammaLUT::cache.get (
+ 12, _frame->colour_conversion().input_gamma, _frame->colour_conversion().input_gamma_linearised
+ );
+
/* XXX: libdcp should probably use boost */
double matrix[3][3];
}
}
- shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+ shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
_frame->image(),
in_lut,
- libdcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma),
+ dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false),
matrix
);
switch (_frame->eyes()) {
case EYES_BOTH:
- _log->log (String::compose (N_("Finished locally-encoded frame %1 for mono"), _index));
+ LOG_GENERAL (N_("Finished locally-encoded frame %1 for mono"), _index);
break;
case EYES_LEFT:
- _log->log (String::compose (N_("Finished locally-encoded frame %1 for L"), _index));
+ LOG_GENERAL (N_("Finished locally-encoded frame %1 for L"), _index);
break;
case EYES_RIGHT:
- _log->log (String::compose (N_("Finished locally-encoded frame %1 for R"), _index));
+ LOG_GENERAL (N_("Finished locally-encoded frame %1 for R"), _index);
break;
default:
break;
root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
add_metadata (root);
- _log->log (String::compose (N_("Sending frame %1 to remote"), _index));
+ LOG_GENERAL (N_("Sending frame %1 to remote"), _index);
/* Send XML metadata */
stringstream xml;
shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
socket->read (e->data(), e->size());
- _log->log (String::compose (N_("Finished remotely-encoded frame %1"), _index));
+ LOG_GENERAL (N_("Finished remotely-encoded frame %1"), _index);
return e;
}
}
void
-EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const
{
boost::filesystem::path const info = film->info_path (frame, eyes);
FILE* h = fopen_boost (info, "w");
*/
/** @file src/decoder.h
- * @brief Parent class for decoders of content.
+ * @brief Decoder class.
*/
#ifndef DCPOMATIC_DECODER_H
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
+#include "types.h"
+#include "dcpomatic_time.h"
-class Film;
+class Decoded;
- class Film;
/** @class Decoder.
* @brief Parent class for decoders of content.
class Decoder : public boost::noncopyable
{
public:
- Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- /** Perform one decode pass of the content, which may or may not
- * cause the object to emit some data.
+protected:
+ /** Seek so that the next pass() will yield the next thing
+ * (video/sound frame, subtitle etc.) at or after the requested
+ * time. Pass accurate = true to try harder to get close to
+ * the request. Note that seeking to time t may mean that
+ * the next pass() yields, for example, audio at time t and then
+ * video before it.
*/
- virtual void pass () = 0;
- virtual bool done () const = 0;
-
-protected:
-
- virtual void flush () {};
-
- /** The Film that we are decoding in */
- boost::weak_ptr<const Film> _film;
+ virtual void seek (ContentTime time, bool accurate) = 0;
+ virtual bool pass () = 0;
};
#endif
#include "i18n.h"
+ #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+ #define LOG_ERROR(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
+ #define LOG_TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TYPE_TIMING);
+
using std::pair;
using std::string;
using std::stringstream;
, _video_frames_out (0)
, _terminate (false)
{
- _have_a_real_frame[EYES_BOTH] = false;
- _have_a_real_frame[EYES_LEFT] = false;
- _have_a_real_frame[EYES_RIGHT] = false;
+
}
Encoder::~Encoder ()
void
Encoder::add_worker_threads (ServerDescription d)
{
- _film->log()->log (String::compose (N_("Adding %1 worker threads for remote %2"), d.host_name ()));
+ LOG_GENERAL (N_("Adding %1 worker threads for remote %2"), d.host_name ());
for (int i = 0; i < d.threads(); ++i) {
_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, d)));
}
{
boost::mutex::scoped_lock lock (_mutex);
- _film->log()->log (String::compose (N_("Clearing queue of %1"), _queue.size ()));
+ LOG_GENERAL (N_("Clearing queue of %1"), _queue.size ());
/* Keep waking workers until the queue is empty */
while (!_queue.empty ()) {
- _film->log()->log (String::compose (N_("Waking with %1"), _queue.size ()), Log::VERBOSE);
_condition.notify_all ();
_condition.wait (lock);
}
terminate_threads ();
- _film->log()->log (String::compose (N_("Mopping up %1"), _queue.size()));
+ LOG_GENERAL (N_("Mopping up %1"), _queue.size());
/* The following sequence of events can occur in the above code:
1. a remote worker takes the last image off the queue
*/
for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
- _film->log()->log (String::compose (N_("Encode left-over frame %1"), (*i)->index ()));
+ LOG_GENERAL (N_("Encode left-over frame %1"), (*i)->index ());
try {
_writer->write ((*i)->encode_locally(), (*i)->index (), (*i)->eyes ());
frame_done ();
} catch (std::exception& e) {
- _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
+ LOG_ERROR (N_("Local encode failed (%1)"), e.what ());
}
}
}
void
-Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same)
+Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf)
{
_waker.nudge ();
/* Wait until the queue has gone down a bit */
while (_queue.size() >= _threads.size() * 2 && !_terminate) {
- TIMING ("decoder sleeps with queue of %1", _queue.size());
+ LOG_TIMING ("decoder sleeps with queue of %1", _queue.size());
_condition.wait (lock);
- TIMING ("decoder wakes with queue of %1", _queue.size());
+ LOG_TIMING ("decoder wakes with queue of %1", _queue.size());
}
if (_terminate) {
if (_writer->can_fake_write (_video_frames_out)) {
_writer->fake_write (_video_frames_out, pvf->eyes ());
- _have_a_real_frame[pvf->eyes()] = false;
- frame_done ();
- } else if (same && _have_a_real_frame[pvf->eyes()]) {
- /* Use the last frame that we encoded. */
- _writer->repeat (_video_frames_out, pvf->eyes());
frame_done ();
} else {
/* Queue this new frame for encoding */
- TIMING ("adding to queue of %1", _queue.size ());
+ LOG_TIMING ("adding to queue of %1", _queue.size ());
_queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- pvf, _video_frames_out, _film->video_frame_rate(),
- _film->j2k_bandwidth(), _film->resolution(), _film->log()
+ pvf,
+ _video_frames_out,
+ _film->video_frame_rate(),
+ _film->j2k_bandwidth(),
+ _film->resolution(),
+ _film->log()
)
));
_condition.notify_all ();
- _have_a_real_frame[pvf->eyes()] = true;
}
if (pvf->eyes() != EYES_LEFT) {
while (1) {
- TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id());
+ LOG_TIMING ("[%1] encoder thread sleeps", boost::this_thread::get_id());
boost::mutex::scoped_lock lock (_mutex);
while (_queue.empty () && !_terminate) {
_condition.wait (lock);
return;
}
- TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
+ LOG_TIMING ("[%1] encoder thread wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
shared_ptr<DCPVideoFrame> vf = _queue.front ();
- TIMING ("encoder thread %1 pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->index(), vf->eyes ());
+ LOG_TIMING ("[%1] encoder thread pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->index(), vf->eyes ());
_queue.pop_front ();
lock.unlock ();
encoded = vf->encode_remotely (server.get ());
if (remote_backoff > 0) {
- _film->log()->log (String::compose (N_("%1 was lost, but now she is found; removing backoff"), server->host_name ()));
+ LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", server->host_name ());
}
/* This job succeeded, so remove any backoff */
/* back off more */
remote_backoff += 10;
}
- _film->log()->log (
- String::compose (
- N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"),
- vf->index(), server->host_name(), e.what(), remote_backoff)
+ LOG_ERROR (
+ N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"),
+ vf->index(), server->host_name(), e.what(), remote_backoff
);
}
} else {
try {
- TIMING ("encoder thread %1 begins local encode of %2", boost::this_thread::get_id(), vf->index());
+ LOG_TIMING ("[%1] encoder thread begins local encode of %2", boost::this_thread::get_id(), vf->index());
encoded = vf->encode_locally ();
- TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->index());
+ LOG_TIMING ("[%1] encoder thread finishes local encode of %2", boost::this_thread::get_id(), vf->index());
} catch (std::exception& e) {
- _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
+ LOG_ERROR (N_("Local encode failed (%1)"), e.what ());
}
}
frame_done ();
} else {
lock.lock ();
- _film->log()->log (
- String::compose (N_("Encoder thread %1 pushes frame %2 back onto queue after failure"), boost::this_thread::get_id(), vf->index())
- );
+ LOG_GENERAL (N_("[%1] Encoder thread pushes frame %2 back onto queue after failure"), boost::this_thread::get_id(), vf->index());
_queue.push_front (vf);
lock.unlock ();
}
#include <libavformat/avformat.h>
}
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "ffmpeg_content.h"
#include "ffmpeg_examiner.h"
+#include "ffmpeg_subtitle_stream.h"
+#include "ffmpeg_audio_stream.h"
#include "compose.hpp"
#include "job.h"
#include "util.h"
#include "i18n.h"
+ #define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+
using std::string;
using std::stringstream;
using std::vector;
using std::pair;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
}
-FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version, list<string>& notes)
: Content (f, node)
, VideoContent (f, node, version)
, AudioContent (f, node)
}
if (_first_video) {
- node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get ()));
+ node->add_child("FirstVideo")->add_child_text (raw_convert<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 ();
- LOG_GENERAL ("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 ())));
++ LOG_GENERAL ("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);
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_after_3d_combine(), video_frame_rate()) << "\n";
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().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 ();
- float const vfr = video_frame_rate ();
- VideoContent::Frame const vl = video_length_after_3d_combine ();
-
- boost::mutex::scoped_lock lm (_mutex);
- if (!_audio_stream) {
- return 0;
+ if (!audio_stream ()) {
+ return ContentTime ();
}
-
- return video_frames_to_audio_frames (vl, cafr, vfr);
+
+ return video_length ();
}
int
}
int
-FFmpegContent::content_audio_frame_rate () const
+FFmpegContent::audio_frame_rate () const
{
boost::mutex::scoped_lock lm (_mutex);
return _audio_stream->frame_rate;
}
-int
-FFmpegContent::output_audio_frame_rate () const
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- /* 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());
-
- /* 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();
- }
-
- return rint (t);
-}
-
bool
operator== (FFmpegStream const & a, FFmpegStream const & b)
{
return a._id != b._id;
}
-FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node)
- : name (node->string_child ("Name"))
- , _id (node->number_child<int> ("Id"))
-{
-
-}
-
-void
-FFmpegStream::as_xml (xmlpp::Node* root) const
-{
- root->add_child("Name")->add_child_text (name);
- root->add_child("Id")->add_child_text (raw_convert<string> (_id));
-}
-
-FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
- : FFmpegStream (node)
- , mapping (node->node_child ("Mapping"), version)
-{
- frame_rate = node->number_child<int> ("FrameRate");
- channels = node->number_child<int64_t> ("Channels");
- first_audio = node->optional_number_child<double> ("FirstAudio");
-}
-
-void
-FFmpegAudioStream::as_xml (xmlpp::Node* root) const
-{
- FFmpegStream::as_xml (root);
- root->add_child("FrameRate")->add_child_text (raw_convert<string> (frame_rate));
- root->add_child("Channels")->add_child_text (raw_convert<string> (channels));
- if (first_audio) {
- root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get ()));
- }
- mapping.as_xml (root->add_child("Mapping"));
-}
-
-bool
-FFmpegStream::uses_index (AVFormatContext const * fc, int index) const
-{
- size_t i = 0;
- while (i < fc->nb_streams) {
- if (fc->streams[i]->id == _id) {
- return int (i) == index;
- }
- ++i;
- }
-
- return false;
-}
-
-AVStream *
-FFmpegStream::stream (AVFormatContext const * fc) const
-{
- size_t i = 0;
- while (i < fc->nb_streams) {
- if (fc->streams[i]->id == _id) {
- return fc->streams[i];
- }
- ++i;
- }
-
- assert (false);
- return 0;
-}
-
-/** Construct a SubtitleStream from a value returned from to_string().
- * @param t String returned from to_string().
- * @param v State file version.
- */
-FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
- : FFmpegStream (node)
-{
-
-}
-
-void
-FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
-{
- 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_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate ();
+ return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
}
AudioMapping
FFmpegContent::set_audio_mapping (AudioMapping m)
{
audio_stream()->mapping = m;
- signal_changed (AudioContentProperty::AUDIO_MAPPING);
+ AudioContent::set_audio_mapping (m);
}
string
p /= name;
return p;
}
+
+bool
+FFmpegContent::has_subtitle_during (ContentTimePeriod period) const
+{
+ shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream ();
+
+ /* XXX: inefficient */
+ for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) {
+ if (i->from <= period.to && i->to >= period.from) {
+ return true;
+ }
+ }
+
+ return false;
+}
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
-#include "film.h"
#include "filter.h"
#include "exceptions.h"
#include "image.h"
#include "util.h"
#include "log.h"
#include "ffmpeg_decoder.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
#include "filter_graph.h"
#include "audio_buffers.h"
#include "ffmpeg_content.h"
#include "image_proxy.h"
++#include "film.h"
#include "i18n.h"
-#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
-#define LOG_ERROR(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
-#define LOG_WARNING(...) film->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
++#define LOG_GENERAL(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
++#define LOG_ERROR(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
++#define LOG_WARNING(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+
using std::cout;
using std::string;
using std::vector;
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)
+ : VideoDecoder (c)
+ , AudioDecoder (c)
+ , SubtitleDecoder (c)
, FFmpeg (c)
+ , _log (log)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
- , _decode_video (video)
- , _decode_audio (audio)
- , _pts_offset (0)
- , _just_sought (false)
{
- setup_subtitle ();
-
/* Audio and video frame PTS values may not start with 0. We want
to fiddle them so that:
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_video = c->first_video();
+ bool const have_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;
}
}
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
- if (_decode_video) {
- while (decode_video_packet ()) {}
- }
+ while (decode_video_packet ()) {}
- if (_ffmpeg_content->audio_stream() && _decode_audio) {
+ if (_ffmpeg_content->audio_stream()) {
decode_audio_packet ();
+ AudioDecoder::flush ();
}
-
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length_after_3d_combine ();
- _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));
- _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
+ LOG_ERROR (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) {
+ if (si == _video_stream) {
decode_video_packet ();
- } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
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_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 got_picture = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
+ if (r >= 0 && got_picture) {
+ 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 got_frame;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
+ if (r >= 0 && got_frame) {
+ 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.
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ VideoDecoder::seek (time, accurate);
+ AudioDecoder::seek (time, accurate);
+
+ /* If we are doing an accurate seek, our initial shot will be 2s (2 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.
*/
- if (!accurate) {
- _just_sought = true;
+
+ ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
+ ContentTime initial_seek = time - pre_roll;
+ if (initial_seek < ContentTime (0)) {
+ initial_seek = ContentTime (0);
}
-
- _video_position = frame;
-
- if (frame == 0 || !accurate) {
- /* We're already there, or we're as close as we need to be */
+
+ /* Initial seek time in the video stream's timebase */
+
+ seek_and_flush (initial_seek);
+
+ if (!accurate) {
+ /* That'll do */
return;
}
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
+ int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
- 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()
- );
-
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ 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) {
- _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
+ LOG_ERROR ("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);
- _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
-
+ LOG_GENERAL (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);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) {
shared_ptr<Image> image = i->first;
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
- )
- );
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- black->make_black ();
- video (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), 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 (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), false, _video_position);
- }
-
+ double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
- video (shared_ptr<ImageProxy> (new RawImageProxy (image)), rint (pts * _ffmpeg_content->video_frame_rate ()));
++ video (
++ shared_ptr<ImageProxy> (new RawImageProxy (image, _video_content->film()->log())),
++ rint (pts * _ffmpeg_content->video_frame_rate ())
++ );
} else {
- _log->log ("Dropping frame without PTS");
+ LOG_WARNING ("Dropping frame without PTS");
}
}
return true;
}
-
-
-void
-FFmpegDecoder::setup_subtitle ()
-{
- boost::mutex::scoped_lock lm (_mutex);
-
- if (!_ffmpeg_content->subtitle_stream()) {
- return;
- }
-
- _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
- if (_subtitle_codec_context == 0) {
- throw DecodeError (N_("could not find subtitle stream"));
- }
-
- _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
-
- if (_subtitle_codec == 0) {
- throw DecodeError (N_("could not find subtitle decoder"));
- }
-
- if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
- throw DecodeError (N_("could not open subtitle decoder"));
- }
-}
-
-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 (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
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;
-
- /* 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;
+ ContentTimePeriod period = subtitle_period (sub) + _pts_offset;
AVSubtitleRect const * rect = sub.rects[0];
if (rect->type != SUBTITLE_BITMAP) {
- throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ /* XXX */
+ // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ return;
}
/* 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 (
+ period,
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
static_cast<double> (rect->y) / vs.height,
static_cast<double> (rect->w) / vs.width,
static_cast<double> (rect->h) / vs.height
- ),
- from,
- to
+ )
);
-
avsubtitle_free (&sub);
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-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 "ffmpeg_examiner.h"
#include "ffmpeg_content.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
+#include "util.h"
#include "i18n.h"
}
}
- /* Run through until we find the first audio (for each stream) and video */
-
+ /* Run through until we find:
+ * - the first video.
+ * - the first audio for each stream.
+ * - the subtitle periods for each stream.
+ *
+ * We have to note subtitle periods as otherwise we have no way of knowing
+ * where we should look for subtitles (video and audio are always present,
+ * so they are ok).
+ */
while (1) {
int r = av_read_frame (_format_context, &_packet);
if (r < 0) {
break;
}
- int frame_finished;
-
AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec;
- if (_packet.stream_index == _video_stream && !_first_video) {
- if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- _first_video = frame_time (_format_context->streams[_video_stream]);
- }
- } else {
- for (size_t i = 0; i < _audio_streams.size(); ++i) {
- if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index) && !_audio_streams[i]->first_audio) {
- if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->stream (_format_context));
- }
- }
+ if (_packet.stream_index == _video_stream) {
+ video_packet (context);
+ }
+
+ for (size_t i = 0; i < _audio_streams.size(); ++i) {
+ if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index)) {
+ audio_packet (context, _audio_streams[i]);
}
}
- bool have_all_audio = true;
- size_t i = 0;
- while (i < _audio_streams.size() && have_all_audio) {
- have_all_audio = _audio_streams[i]->first_audio;
- ++i;
+ for (size_t i = 0; i < _subtitle_streams.size(); ++i) {
+ if (_subtitle_streams[i]->uses_index (_format_context, _packet.stream_index)) {
+ subtitle_packet (context, _subtitle_streams[i]);
+ }
}
av_free_packet (&_packet);
-
- if (_first_video && have_all_audio) {
- break;
+ }
+}
+
+void
+FFmpegExaminer::video_packet (AVCodecContext* context)
+{
+ if (_first_video) {
+ return;
+ }
+
+ int frame_finished;
+ if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ _first_video = frame_time (_format_context->streams[_video_stream]);
+ }
+}
+
+void
+FFmpegExaminer::audio_packet (AVCodecContext* context, shared_ptr<FFmpegAudioStream> stream)
+{
+ if (stream->first_audio) {
+ return;
+ }
+
+ int frame_finished;
+ if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ stream->first_audio = frame_time (stream->stream (_format_context));
+ }
+}
+
+void
+FFmpegExaminer::subtitle_packet (AVCodecContext* context, shared_ptr<FFmpegSubtitleStream> stream)
+{
+ int frame_finished;
+ AVSubtitle sub;
+ if (avcodec_decode_subtitle2 (context, &sub, &frame_finished, &_packet) >= 0 && frame_finished) {
+ ContentTimePeriod const period = subtitle_period (sub);
+ if (sub.num_rects == 0 && !stream->periods.empty () && stream->periods.back().to > period.from) {
+ /* Finish the last subtitle */
+ stream->periods.back().to = period.from;
+ } else if (sub.num_rects == 1) {
+ stream->periods.push_back (period);
}
}
}
-optional<double>
+optional<ContentTime>
FFmpegExaminer::frame_time (AVStream* s) const
{
- optional<double> t;
+ optional<ContentTime> t;
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- t = bet * av_q2d (s->time_base);
+ t = ContentTime::from_seconds (bet * av_q2d (s->time_base));
}
return t;
float
FFmpegExaminer::video_frame_rate () const
{
- AVStream* s = _format_context->streams[_video_stream];
-
- if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
- return av_q2d (s->avg_frame_rate);
- }
-
- return av_q2d (s->r_frame_rate);
+ /* This use of r_frame_rate is debateable; there's a few different
+ * frame rates in the format context, but this one seems to be the most
+ * reliable.
+ */
+ return av_q2d (av_stream_get_r_frame_rate (_format_context->streams[_video_stream]));
}
-libdcp::Size
+dcp::Size
FFmpegExaminer::video_size () const
{
- return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+ return dcp::Size (video_codec_context()->width, video_codec_context()->height);
}
-/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+/** @return Length according to our content's header */
+ContentTime
FFmpegExaminer::video_length () const
{
- ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE);
- VideoContent::Frame const length = (double (_format_context->duration - _format_context->start_time) / AV_TIME_BASE) * video_frame_rate();
- return max (1, length);
++ ContentTime const length = ContentTime::from_seconds (double (_format_context->duration - _format_context->start_time) / AV_TIME_BASE);
+ return ContentTime (max (int64_t (1), length.get ()));
}
string
#include <unistd.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
-#include <boost/date_time.hpp>
+#include <boost/lexical_cast.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 <libdcp/raw_convert.h>
+#include <dcp/signer_chain.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
+#include <dcp/util.h>
+#include <dcp/local_time.h>
+#include <dcp/raw_convert.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 libdcp::raw_convert;
+using dcp::Size;
+using dcp::Signer;
+using dcp::raw_convert;
+ #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+ #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, Log::TYPE_GENERAL);
+
/* 5 -> 6
* AudioMapping XML changed.
* 6 -> 7
* Subtitle offset changed to subtitle y offset, and subtitle x offset added.
* 7 -> 8
* Use <Scale> tag in <VideoContent> rather than <Ratio>.
+ *
+ * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
+ * than frames now.
*/
-int const Film::current_state_version = 8;
+int const Film::current_state_version = 32;
/** Construct a Film object in a given directory.
*
*/
write_metadata ();
- log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
+ LOG_GENERAL ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary());
{
char buffer[128];
gethostname (buffer, sizeof (buffer));
- log()->log (String::compose ("Starting to make DCP on %1", buffer));
+ LOG_GENERAL ("Starting to make DCP on %1", buffer);
}
ContentList cl = content ();
for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
- log()->log (String::compose ("Content: %1", (*i)->technical_summary()));
+ LOG_GENERAL ("Content: %1", (*i)->technical_summary());
}
- log()->log (String::compose ("DCP video rate %1 fps", video_frame_rate()));
- log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
- log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
+ LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
+ LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads());
+ LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
#ifdef DCPOMATIC_DEBUG
- log()->log ("DCP-o-matic built in debug mode.");
+ LOG_GENERAL_NC ("DCP-o-matic built in debug mode.");
#else
- log()->log ("DCP-o-matic built in optimised mode.");
+ LOG_GENERAL_NC ("DCP-o-matic built in optimised mode.");
#endif
#ifdef LIBDCP_DEBUG
- log()->log ("libdcp built in debug mode.");
+ LOG_GENERAL_NC ("libdcp built in debug mode.");
#else
- log()->log ("libdcp built in optimised mode.");
+ LOG_GENERAL_NC ("libdcp built in optimised mode.");
#endif
#ifdef DCPOMATIC_WINDOWS
OSVERSIONINFO info;
info.dwOSVersionInfoSize = sizeof (info);
GetVersionEx (&info);
- log()->log (String::compose ("Windows version %1.%2.%3 SP %4", info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber, info.szCSDVersion));
+ LOG_GENERAL ("Windows version %1.%2.%3 SP %4", info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber, info.szCSDVersion);
#endif
- log()->log (String::compose ("CPU: %1, %2 processors", cpu_info(), boost::thread::hardware_concurrency ()));
+ LOG_GENERAL ("CPU: %1, %2 processors", cpu_info(), boost::thread::hardware_concurrency ());
list<pair<string, string> > const m = mount_info ();
for (list<pair<string, string> >::const_iterator i = m.begin(); i != m.end(); ++i) {
- log()->log (String::compose ("Mount: %1 %2", i->first, i->second));
+ LOG_GENERAL ("Mount: %1 %2", i->first, i->second);
}
if (container() == 0) {
_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"));
list<string> notes;
/* This method is the only one that can return notes (so far) */
return file (p);
}
- /** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */
- list<boost::filesystem::path>
- Film::dcps () const
+ /** Find all the DCPs in our directory that can be libdcp::DCP::read() and return details of their CPLs */
+ vector<CPLSummary>
+ Film::cpls () const
{
- list<boost::filesystem::path> out;
+ vector<CPLSummary> out;
boost::filesystem::path const dir = directory ();
for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
) {
try {
- libdcp::DCP dcp (*i);
+ dcp::DCP dcp (*i);
dcp.read ();
- out.push_back (i->path().leaf ());
+ out.push_back (
+ CPLSummary (
- i->path().leaf().string(), dcp.cpls().front()->id(), dcp.cpls().front()->name(), dcp.cpls().front()->filename()
++ i->path().leaf().string(),
++ dcp.cpls().front()->id(),
++ dcp.cpls().front()->annotation_text(),
++ dcp.cpls().front()->file()
+ )
+ );
} 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 */
}
/** @return Size of the largest possible image in whatever resolution we are using */
-libdcp::Size
+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 ();
}
/** @return Size of the frame */
-libdcp::Size
+dcp::Size
Film::frame_size () const
{
return fit_ratio_within (container()->ratio(), full_frame ());
}
-/** @param from KDM from time in local time.
- * @param to KDM to time in local time.
- */
-libdcp::KDM
+dcp::EncryptedKDM
Film::make_kdm (
- shared_ptr<libdcp::Certificate> target,
+ shared_ptr<dcp::Certificate> target,
- boost::filesystem::path dcp_dir,
+ boost::filesystem::path cpl_file,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const
{
-- shared_ptr<const Signer> signer = make_signer ();
--
- dcp::DCP dcp (dir (dcp_dir.string ()));
-
- try {
- dcp.read ();
- } catch (...) {
- throw KDMError (_("Could not read DCP to make KDM for"));
- }
-
- dcp.cpls().front()->set_mxf_keys (key ());
- time_t now = time (0);
- struct tm* tm = localtime (&now);
- string const issue_date = libdcp::tm_to_string (tm);
--
- return libdcp::KDM (cpl_file, signer, target, key (), from, until, "DCP-o-matic", issue_date);
++ shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
+ return dcp::DecryptedKDM (
- dcp.cpls().front(), from, until, "DCP-o-matic", dcp.cpls().front()->content_title_text(), dcp::LocalTime().as_string()
- ).encrypt (signer, target);
++ cpl, from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string()
++ ).encrypt (make_signer(), target);
}
-list<libdcp::KDM>
+list<dcp::EncryptedKDM>
Film::make_kdms (
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const
{
- list<libdcp::KDM> kdms;
+ list<dcp::EncryptedKDM> 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/decrypted_kdm.h>
+#include <dcp/encrypted_kdm.h>
#include "util.h"
#include "types.h"
#include "dci_metadata.h"
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;
+ std::vector<CPLSummary> cpls () 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::EncryptedKDM
make_kdm (
- boost::shared_ptr<libdcp::Certificate> target,
+ boost::shared_ptr<dcp::Certificate> target,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl_file,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const;
- std::list<libdcp::KDM> make_kdms (
+ std::list<dcp::EncryptedKDM> make_kdms (
std::list<boost::shared_ptr<Screen> >,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl_file,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime 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 libdcp::Size;
+using dcp::Size;
-ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c)
- : Decoder (f)
- , VideoDecoder (f, c)
+ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c)
+ : VideoDecoder (c)
, _image_content (c)
{
}
-void
+bool
ImageDecoder::pass ()
{
- if (_video_position >= _image_content->video_length ()) {
- return;
+ if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) {
+ return true;
}
- if (_image && _image_content->still ()) {
- video (_image, true, _video_position);
- return;
+ if (!_image_content->still() || !_image) {
+ /* Either we need an image or we are using moving images, so load one */
- _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position)));
++ _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position), _image_content->film()->log ()));
}
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position), film->log ()));
- video (_image, false, _video_position);
+
+ video (_image, _video_position);
+ ++_video_position;
+ return false;
}
void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
- _video_position = frame;
-}
-
-bool
-ImageDecoder::done () const
+ImageDecoder::seek (ContentTime time, bool accurate)
{
- return _video_position >= _image_content->video_length ();
+ VideoDecoder::seek (time, accurate);
+ _video_position = time.frames (_image_content->video_frame_rate ());
}
*/
#include <Magick++.h>
-#include <libdcp/util.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/util.h>
+#include <dcp/raw_convert.h>
#include "image_proxy.h"
#include "image.h"
#include "exceptions.h"
#include "cross.h"
+ #include "log.h"
#include "i18n.h"
+ #define LOG_TIMING(...) _log->microsecond_log (String::compose (__VA_ARGS__), Log::TYPE_TIMING);
+
using std::cout;
using std::string;
using std::stringstream;
using boost::shared_ptr;
- RawImageProxy::RawImageProxy (shared_ptr<Image> image)
- : _image (image)
+ ImageProxy::ImageProxy (shared_ptr<Log> log)
+ : _log (log)
{
}
- RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket)
+ RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log)
+ : ImageProxy (log)
+ , _image (image)
+ {
+
+ }
+
+ RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log)
+ : ImageProxy (log)
{
- libdcp::Size size (
+ dcp::Size size (
xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
);
RawImageProxy::add_metadata (xmlpp::Node* node) const
{
node->add_child("Type")->add_child_text (N_("Raw"));
- node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width));
- node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height));
+ node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_image->size().width));
+ node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_image->size().height));
}
void
_image->write_to_socket (socket);
}
- MagickImageProxy::MagickImageProxy (boost::filesystem::path path)
+ MagickImageProxy::MagickImageProxy (boost::filesystem::path path, shared_ptr<Log> log)
+ : ImageProxy (log)
{
/* Read the file into a Blob */
delete[] data;
}
- MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket)
+ MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket, shared_ptr<Log> log)
+ : ImageProxy (log)
{
uint32_t const size = socket->read_uint32 ();
uint8_t* data = new uint8_t[size];
return _image;
}
+ LOG_TIMING ("[%1] MagickImageProxy begins decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length());
+
Magick::Image* magick_image = 0;
try {
magick_image = new Magick::Image (_blob);
throw DecodeError (_("Could not decode image file"));
}
+ dcp::Size size (magick_image->columns(), magick_image->rows());
+ LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ());
- libdcp::Size size (magick_image->columns(), magick_image->rows());
-
_image.reset (new Image (PIX_FMT_RGB24, size, true));
using namespace MagickCore;
delete magick_image;
+ LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length());
+
return _image;
}
}
shared_ptr<ImageProxy>
- image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket)
+ image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log)
{
if (xml->string_child("Type") == N_("Raw")) {
- return shared_ptr<ImageProxy> (new RawImageProxy (xml, socket));
+ return shared_ptr<ImageProxy> (new RawImageProxy (xml, socket, log));
} else if (xml->string_child("Type") == N_("Magick")) {
- return shared_ptr<MagickImageProxy> (new MagickImageProxy (xml, socket));
+ return shared_ptr<MagickImageProxy> (new MagickImageProxy (xml, socket, log));
}
throw NetworkError (_("Unexpected image type received by server"));
/*
- 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/thread.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
#include "job.h"
#include "util.h"
#include "cross.h"
#include "ui_signaller.h"
#include "exceptions.h"
+#include "film.h"
+#include "log.h"
#include "i18n.h"
run ();
- } catch (libdcp::FileError& e) {
-
+ } catch (dcp::FileError& e) {
+
string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
try {
}
}
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
int
Job::elapsed_time () const
{
void
Job::set_error (string s, string d)
{
- _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d));
++ _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d), Log::TYPE_ERROR);
boost::mutex::scoped_lock lm (_state_mutex);
_error_summary = s;
_error_details = d;
#include <boost/shared_ptr.hpp>
#include <quickmail.h>
#include <zip.h>
-#include <libdcp/kdm.h>
+#include <dcp/encrypted_kdm.h>
#include "kdm.h"
#include "cinema.h"
#include "exceptions.h"
struct ScreenKDM
{
- ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
+ ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
: screen (s)
, kdm (k)
{}
shared_ptr<Screen> screen;
- libdcp::KDM kdm;
+ dcp::EncryptedKDM kdm;
};
static string
make_screen_kdms (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
- list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, dcp, from, to);
- list<libdcp::KDM> kdms = film->make_kdms (screens, cpl, from, to);
++ list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, cpl, from, to);
list<ScreenKDM> screen_kdms;
list<shared_ptr<Screen> >::iterator i = screens.begin ();
- list<libdcp::KDM>::iterator j = kdms.begin ();
+ list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
while (i != screens.end() && j != kdms.end ()) {
screen_kdms.push_back (ScreenKDM (*i, *j));
++i;
make_cinema_kdms (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
- list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
+ list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, cpl, from, to);
list<CinemaKDMs> cinema_kdms;
while (!screen_kdms.empty ()) {
write_kdm_files (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
)
{
- list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
+ list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, cpl, from, to);
/* Write KDMs to the specified directory */
for (list<ScreenKDM>::iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
write_kdm_zip_files (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
)
{
- list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
+ list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, cpl, from, to);
for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
boost::filesystem::path path = directory;
email_kdms (
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
- list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
+ list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, cpl, from, to);
for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
extern void write_kdm_files (
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
);
extern void write_kdm_zip_files (
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
);
extern void email_kdms (
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
- boost::filesystem::path dcp,
+ boost::filesystem::path cpl,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
);
*/
#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 "image_proxy.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
+#include "config.h"
+#include "content_video.h"
#include "player_video_frame.h"
+ #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+
using std::list;
using std::cout;
using std::min;
using std::max;
+using std::min;
using std::vector;
using std::pair;
using std::map;
+using std::make_pair;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
: _film (f)
, _playlist (p)
- , _video (true)
- , _audio (true)
, _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))
- , _last_emit_was_black (false)
+ , _approximate_size (false)
+ , _burn_subtitles (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));
}
void
-Player::disable_video ()
-{
- _video = false;
-}
-
-void
-Player::disable_audio ()
+Player::setup_pieces ()
{
- _audio = false;
-}
+ list<shared_ptr<Piece> > old_pieces = _pieces;
+ _pieces.clear ();
-bool
-Player::pass ()
-{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
+ ContentList content = _playlist->content ();
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
+ for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
+ if (!(*i)->paths_valid ()) {
continue;
}
-
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
-
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
+
+ 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;
}
- }
-
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
+
+ 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;
}
}
- }
- if (!earliest) {
- flush ();
- return true;
- }
-
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
} else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
- } else {
- earliest->decoder->pass ();
- }
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
}
- 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 ());
- }
- }
- }
+ /* FFmpeg */
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+ if (fc) {
+ decoder.reset (new FFmpegDecoder (fc, _film->log()));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
- break;
- }
- 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;
+ /* ImageContent */
+ shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
+ if (ic) {
+ /* 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) {
+ decoder = imd;
+ }
}
- 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 (!decoder) {
+ decoder.reset (new ImageDecoder (ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
- 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 ());
+ /* SndfileContent */
+ shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
+ if (sc) {
+ decoder.reset (new SndfileDecoder (sc));
+ frc = best_overlap_frc;
}
+
+ /* 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 (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
-
- return false;
+
+ _have_valid_pieces = true;
}
-/** @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 ImageProxy> image, Eyes eyes, Part part, bool same, VideoContent::Frame frame, Time extra)
+Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
{
- /* 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.part = part;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
-
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
-
- 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;
- }
-
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
+ shared_ptr<Content> c = w.lock ();
+ if (!c) {
return;
}
- 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<PlayerVideoFrame> pi (
- new PlayerVideoFrame (
- image,
- content->crop(),
- image_size,
- _video_container_size,
- _film->scaler(),
- eyes,
- part,
- content->colour_conversion()
- )
- );
-
- if (_film->with_subtitles ()) {
- for (list<Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- if (i->covers (time)) {
- /* This may be true for more than one of _subtitles, but the last (latest-starting)
- one is the one we want to use, so that's ok.
- */
- Position<int> const container_offset (
- (_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
- );
-
- pi->set_subtitle (i->out_image(), i->out_position() + container_offset);
- }
- }
- }
-
- /* Clear out old subtitles */
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ) {
- list<Subtitle>::iterator j = i;
- ++j;
+ if (
+ property == ContentProperty::POSITION ||
+ property == ContentProperty::LENGTH ||
+ property == ContentProperty::TRIM_START ||
+ property == ContentProperty::TRIM_END ||
+ property == ContentProperty::PATH ||
+ property == VideoContentProperty::VIDEO_FRAME_TYPE
+ ) {
- if (i->ends_before (time)) {
- _subtitles.erase (i);
- }
-
- i = j;
- }
-
-#ifdef DCPOMATIC_DEBUG
- _last_video = piece->content;
-#endif
-
- Video (pi, same, time);
-
- _last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+ _have_valid_pieces = false;
+ Changed (frequent);
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+ } else if (
+ property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
+ property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
+ property == SubtitleContentProperty::SUBTITLE_SCALE ||
+ property == VideoContentProperty::VIDEO_CROP ||
+ property == VideoContentProperty::VIDEO_SCALE ||
+ property == VideoContentProperty::VIDEO_FRAME_RATE
+ ) {
+
+ Changed (frequent);
}
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::playlist_changed ()
{
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
+ _have_valid_pieces = false;
+ Changed (false);
+}
- shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
- assert (content);
+void
+Player::set_video_container_size (dcp::Size s)
+{
+ _video_container_size = s;
- /* Gain */
- if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
- gain->apply_gain (content->audio_gain ());
- audio = gain;
- }
+ _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
+ _black_image->make_black ();
+}
- /* 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;
- }
-
- Time const relative_time = _film->audio_frames_to_time (frame);
+void
+Player::film_changed (Film::Property p)
+{
+ /* Here we should notice Film properties that affect our output, and
+ alert listeners that our output now would be different to how it was
+ last time we were run.
+ */
- if (content->trimmed (relative_time)) {
- return;
+ if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
+ Changed (false);
}
+}
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
+list<PositionImage>
+Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
+{
+ list<PositionImage> all;
- /* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->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) {
- dcp_mapped->accumulate_channel (
- audio.get(),
- i,
- static_cast<libdcp::Channel> (j),
- map.get (i, static_cast<libdcp::Channel> (j))
- );
- }
+ for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ if (!(*i)->image) {
+ continue;
}
+
+ dcpomatic::Rect<double> in_rect = (*i)->rectangle;
+ dcp::Size scaled_size;
+
+ in_rect.x += content->subtitle_x_offset ();
+ in_rect.y += content->subtitle_y_offset ();
+
+ /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
+ scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
+ scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
+
+ /* Then we need a corrective translation, consisting of two parts:
+ *
+ * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
+ * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
+ *
+ * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
+ * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
+ * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+ *
+ * Combining these two translations gives these expressions.
+ */
+
+ all.push_back (
+ PositionImage (
+ (*i)->image->scale (
+ scaled_size,
+ Scaler::from_id ("bicubic"),
+ (*i)->image->pixel_format (),
+ true
+ ),
+ Position<int> (
+ rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
+ rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
+ )
+ )
+ );
}
- audio = dcp_mapped;
+ return all;
+}
- /* 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 ()) {
- return;
+list<PositionImage>
+Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
+{
+ list<PositionImage> all;
+ for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
+ if (!(*i)->subs.empty ()) {
+ all.push_back (render_subtitles ((*i)->subs, _video_container_size));
}
-
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
-
- audio = trimmed;
- time = 0;
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ return all;
}
void
-Player::flush ()
+Player::set_approximate_size ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
- if (_audio && tb.audio) {
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
- }
+ _approximate_size = true;
+}
- while (_video && _video_position < _audio_position) {
- emit_black ();
- }
+shared_ptr<PlayerVideoFrame>
+Player::black_player_video_frame () const
+{
+ return shared_ptr<PlayerVideoFrame> (
+ new PlayerVideoFrame (
- shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
++ shared_ptr<const ImageProxy> (new RawImageProxy (_black_image, _film->log ())),
+ Crop (),
+ _video_container_size,
+ _video_container_size,
+ Scaler::from_id ("bicubic"),
+ EYES_BOTH,
+ PART_WHOLE,
+ Config::instance()->colour_conversions().front().conversion
+ )
+ );
+}
- while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+shared_ptr<PlayerVideoFrame>
+Player::content_to_player_video_frame (
+ shared_ptr<VideoContent> content,
+ ContentVideo content_video,
+ list<shared_ptr<Piece> > subs,
+ DCPTime time,
+ dcp::Size image_size) const
+{
+ shared_ptr<PlayerVideoFrame> pvf (
+ new PlayerVideoFrame (
+ content_video.image,
+ content->crop (),
+ image_size,
+ _video_container_size,
+ _film->scaler(),
+ content_video.eyes,
+ content_video.part,
+ content->colour_conversion ()
+ )
+ );
+
+
+ /* Add subtitles */
+
+ list<PositionImage> sub_images;
+
+ for (list<shared_ptr<Piece> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
+ shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
+ ContentTime const from = dcp_to_content_subtitle (*i, time);
+ ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
+
+ list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to));
+ if (!image_subtitles.empty ()) {
+ list<PositionImage> im = process_content_image_subtitles (
+ subtitle_content,
+ image_subtitles
+ );
+
+ copy (im.begin(), im.end(), back_inserter (sub_images));
+ }
+
+ if (_burn_subtitles) {
+ list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to));
+ if (!text_subtitles.empty ()) {
+ list<PositionImage> im = process_content_text_subtitles (text_subtitles);
+ copy (im.begin(), im.end(), back_inserter (sub_images));
+ }
+ }
}
+ if (!sub_images.empty ()) {
+ pvf->set_subtitle (merge (sub_images));
+ }
+
+ return pvf;
}
-/** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- * @return true on error
- */
-void
-Player::seek (Time t, bool accurate)
+/** @return All PlayerVideoFrames at the given time (there may be two frames for 3D) */
+list<shared_ptr<PlayerVideoFrame> >
+Player::get_video (DCPTime time, bool accurate)
{
if (!_have_valid_pieces) {
setup_pieces ();
}
+
+ list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
+ time,
+ time + DCPTime::from_frames (1, _film->video_frame_rate ())
+ );
- if (_pieces.empty ()) {
- return;
+ list<shared_ptr<PlayerVideoFrame> > pvf;
+
+ if (ov.empty ()) {
+ /* No video content at this time */
+ pvf.push_back (black_player_video_frame ());
+ return pvf;
}
- 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);
+ /* Create a PlayerVideoFrame from the content's video at this time */
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
-
- /* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
+ shared_ptr<Piece> piece = ov.back ();
+ shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
+ assert (decoder);
+ shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+ assert (content);
- (*i)->reset_repeat ();
+ list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
+ if (content_video.empty ()) {
+ pvf.push_back (black_player_video_frame ());
+ return pvf;
}
- _video_position = _audio_position = t;
+ dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
+ }
- /* XXX: don't seek audio because we don't need to... */
+ for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
+ list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
+ time,
+ time + DCPTime::from_frames (1, _film->video_frame_rate ())
+ );
+
+ pvf.push_back (content_to_player_video_frame (content, *i, subs, time, image_size));
+ }
+
+ return pvf;
}
-void
-Player::setup_pieces ()
+shared_ptr<AudioBuffers>
+Player::get_audio (DCPTime time, DCPTime length, bool accurate)
{
- list<shared_ptr<Piece> > old_pieces = _pieces;
+ if (!_have_valid_pieces) {
+ setup_pieces ();
+ }
- _pieces.clear ();
+ AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
- ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
+ audio->make_silent ();
+
+ list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
+ if (ov.empty ()) {
+ return audio;
+ }
- for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+ for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
- if (!(*i)->paths_valid ()) {
+ shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
+ assert (content);
+ shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ assert (decoder);
+
+ if (content->audio_frame_rate() == 0) {
+ /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
+ * audio stream).
+ */
continue;
}
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
-
- 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, _5, 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;
+ /* The time that we should request from the content */
+ DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
+ DCPTime offset;
+ if (request < DCPTime ()) {
+ /* We went off the start of the content, so we will need to offset
+ the stuff we get back.
+ */
+ offset = -request;
+ request = DCPTime ();
}
-
- 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;
- }
- }
- 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, _5, 0));
- piece->decoder = id;
- }
- }
+ AudioFrame const content_frame = dcp_to_content_audio (*i, request);
- 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));
+ /* Audio from this piece's decoder (which might be more or less than what we asked for) */
+ shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate);
- piece->decoder = sd;
+ /* Gain */
+ if (content->audio_gain() != 0) {
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
+ gain->apply_gain (content->audio_gain ());
+ all->audio = gain;
}
- _pieces.push_back (piece);
- }
-
- _have_valid_pieces = true;
-}
-
-void
-Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
-{
- shared_ptr<Content> c = w.lock ();
- if (!c) {
- return;
- }
-
- if (
- property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
- property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
- property == VideoContentProperty::VIDEO_FRAME_TYPE
- ) {
-
- _have_valid_pieces = false;
- Changed (frequent);
-
- } else if (
- property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
- property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
- property == SubtitleContentProperty::SUBTITLE_SCALE
- ) {
-
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- i->update (_film, _video_container_size);
+ /* Remap channels */
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->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<dcp::Channel> (j)) > 0) {
+ dcp_mapped->accumulate_channel (
+ all->audio.get(),
+ i,
+ j,
+ map.get (i, static_cast<dcp::Channel> (j))
+ );
+ }
+ }
}
- Changed (frequent);
+ all->audio = dcp_mapped;
- } else if (
- property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE ||
- property == VideoContentProperty::VIDEO_FRAME_RATE
- ) {
-
- Changed (frequent);
-
- } else if (property == ContentProperty::PATH) {
-
- _have_valid_pieces = false;
- Changed (frequent);
+ audio->accumulate_frames (
+ all->audio.get(),
+ content_frame - all->frame,
+ offset.frames (_film->audio_frame_rate()),
+ min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ())
+ );
}
-
- return audio;
}
-void
-Player::playlist_changed ()
-{
- _have_valid_pieces = false;
- Changed (false);
-}
-
-void
-Player::set_video_container_size (libdcp::Size s)
-{
- _video_container_size = s;
-
- shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
- im->make_black ();
-
- _black_frame.reset (
- new PlayerVideoFrame (
- shared_ptr<ImageProxy> (new RawImageProxy (im, _film->log ())),
- Crop(),
- _video_container_size,
- _video_container_size,
- Scaler::from_id ("bicubic"),
- EYES_BOTH,
- PART_WHOLE,
- ColourConversion ()
- )
- );
-}
-
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
+VideoFrame
+Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- if (!create) {
- return shared_ptr<Resampler> ();
- }
-
- LOG_GENERAL (
- "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;
+ /* Convert this to the content frame */
+ return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
}
-void
-Player::emit_black ()
+AudioFrame
+Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
{
-#ifdef DCPOMATIC_DEBUG
- _last_video.reset ();
-#endif
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- Video (_black_frame, _last_emit_was_black, _video_position);
- _video_position += _film->video_frames_to_time (1);
- _last_emit_was_black = true;
+ /* Convert this to the content frame */
+ return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
}
-void
-Player::emit_silence (OutputAudioFrame most)
+ContentTime
+Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
{
- if (most == 0) {
- return;
- }
-
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
- silence->make_silent ();
- Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
-}
-
-void
-Player::film_changed (Film::Property p)
-{
- /* Here we should notice Film properties that affect our output, and
- alert listeners that our output now would be different to how it was
- last time we were run.
- */
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
- Changed (false);
- }
+ return ContentTime (s + piece->content->trim_start(), piece->frc);
}
void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+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()));
- if (!image) {
- /* A null image means that we should stop any current subtitles at `from' */
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- i->set_stop (from);
- }
- } else {
- _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to));
- }
++ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat), Log::TYPE_GENERAL);
++ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()), Log::TYPE_GENERAL);
}
-/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
- * @return false if this could not be done.
- */
-bool
-Player::repeat_last_video ()
+PlayerStatistics const &
+Player::statistics () const
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
- return false;
- }
-
- process_video (
- _last_incoming_video.weak_piece,
- _last_incoming_video.image,
- _last_incoming_video.eyes,
- _last_incoming_video.part,
- _last_incoming_video.same,
- _last_incoming_video.frame,
- _last_incoming_video.extra
- );
-
- return true;
+ return _statistics;
}
*/
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "player_video_frame.h"
#include "image.h"
#include "image_proxy.h"
using std::string;
using std::cout;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
PlayerVideoFrame::PlayerVideoFrame (
shared_ptr<const ImageProxy> in,
Crop crop,
- libdcp::Size inter_size,
- libdcp::Size out_size,
+ dcp::Size inter_size,
+ dcp::Size out_size,
Scaler const * scaler,
Eyes eyes,
Part part,
}
- PlayerVideoFrame::PlayerVideoFrame (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket)
+ PlayerVideoFrame::PlayerVideoFrame (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket, shared_ptr<Log> log)
{
_crop = Crop (node);
- _inter_size = libdcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight"));
- _out_size = libdcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
+ _inter_size = dcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight"));
+ _out_size = dcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
_scaler = Scaler::from_id (node->string_child ("Scaler"));
_eyes = (Eyes) node->number_child<int> ("Eyes");
_part = (Part) node->number_child<int> ("Part");
_colour_conversion = ColourConversion (node);
- _in = image_proxy_factory (node->node_child ("In"), socket);
+ _in = image_proxy_factory (node->node_child ("In"), socket, log);
if (node->optional_number_child<int> ("SubtitleX")) {
- _subtitle_position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY"));
+ _subtitle.position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY"));
- shared_ptr<Image> image (
- new Image (PIX_FMT_RGBA, libdcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true)
+ _subtitle.image.reset (
+ new Image (PIX_FMT_RGBA, dcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true)
);
- image->read_from_socket (socket);
- _subtitle_image = image;
+ _subtitle.image->read_from_socket (socket);
}
}
void
-PlayerVideoFrame::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
+PlayerVideoFrame::set_subtitle (PositionImage image)
{
- _subtitle_image = image;
- _subtitle_position = pos;
+ _subtitle = image;
}
shared_ptr<Image>
break;
}
- shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
+ shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, true);
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
- if (_subtitle_image) {
- out->alpha_blend (_subtitle_image, _subtitle_position);
+ if (_subtitle.image) {
+ out->alpha_blend (_subtitle.image, _subtitle.position);
}
return out;
node->add_child("Eyes")->add_child_text (raw_convert<string> (_eyes));
node->add_child("Part")->add_child_text (raw_convert<string> (_part));
_colour_conversion.as_xml (node);
- if (_subtitle_image) {
- node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle_image->size().width));
- node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle_image->size().height));
- node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle_position.x));
- node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle_position.y));
+ if (_subtitle.image) {
+ node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle.image->size().width));
+ node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle.image->size().height));
+ node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle.position.x));
+ node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle.position.y));
}
}
PlayerVideoFrame::send_binary (shared_ptr<Socket> socket) const
{
_in->send_binary (socket);
- if (_subtitle_image) {
- _subtitle_image->write_to_socket (socket);
+ if (_subtitle.image) {
+ _subtitle.image->write_to_socket (socket);
}
}
#include "types.h"
#include "position.h"
#include "colour_conversion.h"
+#include "position_image.h"
class Image;
class ImageProxy;
class Scaler;
class Socket;
+ class Log;
/** Everything needed to describe a video frame coming out of the player, but with the
* bits still their raw form. We may want to combine the bits on a remote machine,
class PlayerVideoFrame
{
public:
- PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, libdcp::Size, libdcp::Size, Scaler const *, Eyes, Part, ColourConversion);
+ PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, dcp::Size, dcp::Size, Scaler const *, Eyes, Part, ColourConversion);
- PlayerVideoFrame (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>);
+ PlayerVideoFrame (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>, boost::shared_ptr<Log>);
- void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
+ void set_subtitle (PositionImage);
boost::shared_ptr<Image> image () const;
private:
boost::shared_ptr<const ImageProxy> _in;
Crop _crop;
- libdcp::Size _inter_size;
- libdcp::Size _out_size;
+ dcp::Size _inter_size;
+ dcp::Size _out_size;
Scaler const * _scaler;
Eyes _eyes;
Part _part;
ColourConversion _colour_conversion;
- boost::shared_ptr<const Image> _subtitle_image;
- Position<int> _subtitle_position;
+ PositionImage _subtitle;
};
#include <boost/algorithm/string.hpp>
#include <boost/scoped_array.hpp>
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "server.h"
#include "util.h"
#include "scaler.h"
#include "i18n.h"
+ #define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+ #define LOG_GENERAL_NC(...) _log->log (__VA_ARGS__, Log::TYPE_GENERAL);
+ #define LOG_ERROR(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
+ #define LOG_ERROR_NC(...) _log->log (__VA_ARGS__, Log::TYPE_ERROR);
+
using std::string;
using std::stringstream;
using std::multimap;
using boost::bind;
using boost::scoped_array;
using boost::optional;
-using libdcp::Size;
-using libdcp::raw_convert;
+using dcp::Size;
+using dcp::raw_convert;
Server::Server (shared_ptr<Log> log, bool verbose)
: _log (log)
xml->read_stream (s);
if (xml->number_child<int> ("Version") != SERVER_LINK_VERSION) {
cerr << "Mismatched server/client versions\n";
- _log->log ("Mismatched server/client versions");
+ LOG_ERROR_NC ("Mismatched server/client versions");
return -1;
}
- shared_ptr<PlayerVideoFrame> pvf (new PlayerVideoFrame (xml, socket));
+ shared_ptr<PlayerVideoFrame> pvf (new PlayerVideoFrame (xml, socket, _log));
DCPVideoFrame dcp_video_frame (pvf, xml, _log);
try {
encoded->send (socket);
} catch (std::exception& e) {
- _log->log (String::compose ("Send failed; frame %1", dcp_video_frame.index()));
+ LOG_ERROR ("Send failed; frame %1", dcp_video_frame.index());
throw;
}
frame = process (socket, after_read, after_encode);
ip = socket->socket().remote_endpoint().address().to_string();
} catch (std::exception& e) {
- _log->log (String::compose ("Error: %1", e.what()));
+ LOG_ERROR ("Error: %1", e.what());
}
gettimeofday (&end, 0);
cout << message.str() << "\n";
}
- _log->log (message.str ());
+ LOG_GENERAL_NC (message.str ());
}
_worker_condition.notify_all ();
void
Server::run (int num_threads)
{
- _log->log (String::compose ("Server starting with %1 threads", num_threads));
+ LOG_GENERAL ("Server starting with %1 threads", num_threads);
if (_verbose) {
cout << "DCP-o-matic server starting with " << num_threads << " threads.\n";
}
#include "i18n.h"
+ #define LOG_GENERAL_NC(...) _film->log()->log (__VA_ARGS__, Log::TYPE_GENERAL);
+ #define LOG_ERROR_NC(...) _film->log()->log (__VA_ARGS__, Log::TYPE_ERROR);
+
using std::string;
using std::stringstream;
using std::fixed;
using std::setprecision;
+using std::cout;
using boost::shared_ptr;
/** @param s Film to use.
{
try {
- _film->log()->log (N_("Transcode job starting"));
+ LOG_GENERAL_NC (N_("Transcode job starting"));
_transcoder.reset (new Transcoder (_film, shared_from_this ()));
_transcoder->go ();
set_progress (1);
set_state (FINISHED_OK);
- _film->log()->log (N_("Transcode job completed successfully"));
+ LOG_GENERAL_NC (N_("Transcode job completed successfully"));
_transcoder.reset ();
} catch (...) {
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+ LOG_ERROR_NC (N_("Transcode job failed or cancelled"));
_transcoder.reset ();
throw;
}
return s.str ();
}
+/** @return Approximate remaining time in seconds */
int
TranscodeJob::remaining_time () const
{
}
/* Compute approximate proposed length here, as it's only here that we need it */
- OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
- return left / fps;
+ return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps;
}
#include <vector>
#include <stdint.h>
#include <boost/shared_ptr.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include "dcpomatic_time.h"
+#include "position.h"
class Content;
class VideoContent;
*/
#define SERVER_LINK_VERSION 2
-typedef int64_t Time;
-#define TIME_MAX INT64_MAX
-#define TIME_HZ ((Time) 96000)
-typedef int64_t OutputAudioFrame;
-typedef int OutputVideoFrame;
typedef std::vector<boost::shared_ptr<Content> > ContentList;
typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
typedef std::vector<boost::shared_ptr<SubtitleContent> > SubtitleContentList;
typedef std::vector<boost::shared_ptr<FFmpegContent> > FFmpegContentList;
-template<class T>
+typedef int64_t VideoFrame;
+typedef int64_t AudioFrame;
+
+/* XXX -> DCPAudio */
struct TimedAudioBuffers
{
TimedAudioBuffers ()
: time (0)
{}
- TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
+ TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, DCPTime t)
: audio (a)
, time (t)
{}
boost::shared_ptr<AudioBuffers> audio;
- T time;
+ DCPTime time;
};
enum VideoFrameType
/** Number of pixels to remove from the bottom */
int bottom;
- libdcp::Size apply (libdcp::Size s, int minimum = 4) const {
+ dcp::Size apply (dcp::Size s, int minimum = 4) const {
s.width -= left + right;
s.height -= top + bottom;
void as_xml (xmlpp::Node *) const;
};
+ struct CPLSummary
+ {
+ CPLSummary (std::string d, std::string i, std::string a, boost::filesystem::path f)
+ : dcp_directory (d)
+ , cpl_id (i)
+ , cpl_annotation_text (a)
+ , cpl_file (f)
+ {}
+
+ std::string dcp_directory;
+ std::string cpl_id;
+ std::string cpl_annotation_text;
+ boost::filesystem::path cpl_file;
+ };
+
extern bool operator== (Crop const & a, Crop const & b);
extern bool operator!= (Crop const & a, Crop const & b);
#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include <dcp/signer.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include "types.h"
#include "video_content.h"
- #ifdef DCPOMATIC_DEBUG
- #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
- #else
- #define TIMING(...)
- #endif
-
#undef check
/** The maximum number of audio channels that we can have in a DCP */
}
class Job;
+struct AVSubtitle;
extern std::string seconds_to_hms (int);
extern std::string seconds_to_approximate_hms (int);
extern boost::filesystem::path mo_path ();
#endif
extern std::string tidy_for_filename (std::string);
-extern boost::shared_ptr<const libdcp::Signer> make_signer ();
-extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
+extern boost::shared_ptr<const dcp::Signer> make_signer ();
+extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
extern std::string entities_to_text (std::string e);
extern std::map<std::string, std::string> split_get_request (std::string url);
-
-struct FrameRateConversion
-{
- FrameRateConversion (float, int);
-
- /** @return factor by which to multiply a source frame rate
- to get the effective rate after any skip or repeat has happened.
- */
- float factor () const {
- if (skip) {
- return 0.5;
- }
-
- return repeat;
- }
-
- /** true to skip every other frame */
- bool skip;
- /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
- int repeat;
- /** true if this DCP will run its video faster or slower than the source
- * without taking into account `repeat' nor `skip'.
- * (e.g. change_speed will be true if
- * source is 29.97fps, DCP is 30fps
- * source is 14.50fps, DCP is 30fps
- * but not if
- * source is 15.00fps, DCP is 30fps
- * source is 12.50fps, DCP is 25fps)
- */
- bool change_speed;
-
- std::string description;
-};
-
extern int dcp_audio_frame_rate (int);
extern int stride_round_up (int, int const *, int);
extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
extern std::string get_optional_string (std::multimap<std::string, std::string> const & kv, std::string k);
extern void* wrapped_av_malloc (size_t);
extern int64_t divide_with_round (int64_t a, int64_t b);
+extern ContentTimePeriod subtitle_period (AVSubtitle const &);
/** @class Socket
* @brief A class to wrap a boost::asio::ip::tcp::socket with some things
extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+/** @class ScopedTemporary
+ * @brief A temporary file which is deleted when the ScopedTemporary object goes out of scope.
+ */
class ScopedTemporary
{
public:
ScopedTemporary ();
~ScopedTemporary ();
+ /** @return temporary filename */
boost::filesystem::path file () const {
return _file;
}
-
+
char const * c_str () const;
FILE* open (char const *);
void close ();
#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"
+ #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+ #define LOG_TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TYPE_TIMING);
+ #define LOG_WARNING_NC(...) _film->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+
/* OS X strikes again */
#undef set_key
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;
, _last_written_eyes (EYES_RIGHT)
, _full_written (0)
, _fake_written (0)
- , _repeat_written (0)
, _pushed_to_disk (0)
{
/* Remove any old DCP */
*/
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_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
+ );
if (_film->audio_channels ()) {
- _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)
{
- if (_sound_asset) {
- _sound_asset_writer->write (audio->data(), audio->frames());
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->write (audio->data(), audio->frames());
}
}
break;
}
- TIMING (N_("writer sleeps with a queue of %1"), _queue.size());
+ LOG_TIMING (N_("writer sleeps with a queue of %1"), _queue.size());
_empty_condition.wait (lock);
- TIMING (N_("writer wakes with a queue of %1"), _queue.size());
+ LOG_TIMING (N_("writer wakes with a queue of %1"), _queue.size());
}
if (_finish && _queue.empty()) {
switch (qi.type) {
case QueueItem::FULL:
{
- _film->log()->log (String::compose (N_("Writer FULL-writes %1 to MXF"), qi.frame));
+ LOG_GENERAL (N_("Writer FULL-writes %1 to MXF"), qi.frame);
if (!qi.encoded) {
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;
break;
}
case QueueItem::FAKE:
- _film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame));
+ LOG_GENERAL (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:
- {
- LOG_GENERAL (N_("Writer REPEAT-writes %1 to MXF"), qi.frame);
- libdcp::FrameInfo fin = _picture_asset_writer->write (
- _last_written[qi.eyes]->data(),
- _last_written[qi.eyes]->size()
- );
-
- _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
- ++_repeat_written;
- break;
- }
}
lock.lock ();
_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;
- }
- job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
+ 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) / total);
}
}
lock.unlock ();
- _film->log()->log (
- String::compose (
- "Writer full (awaiting %1 [last eye was %2]); pushes %3 to disk",
- _last_written_frame + 1,
- _last_written_eyes, qi.frame)
+ LOG_GENERAL (
+ "Writer full (awaiting %1 [last eye was %2]); pushes %3 to disk",
+ _last_written_frame + 1,
+ _last_written_eyes, qi.frame
);
qi.encoded->write (_film, qi.frame, qi.eyes);
terminate_thread (true);
- _picture_asset_writer->finalize ();
- if (_sound_asset_writer) {
- _sound_asset_writer->finalize ();
+ _picture_mxf_writer->finalize ();
+ if (_sound_mxf_writer) {
+ _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();
if (ec) {
/* hard link failed; copy instead */
boost::filesystem::copy_file (video_from, video_to);
- _film->log()->log ("Hard-link failed; fell back to copying");
+ LOG_WARNING_NC ("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 ());
+ _picture_mxf->set_file (video_to);
/* Move the audio MXF into the DCP */
- if (_sound_asset) {
+ if (_sound_mxf) {
boost::filesystem::path audio_to;
audio_to /= _film->dir (_film->dcp_name ());
audio_to /= _film->audio_mxf_filename ();
String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
);
}
-
- _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
- _sound_asset->set_duration (frames);
+
+ _sound_mxf->set_file (audio_to);
}
-
- libdcp::DCP dcp (_film->dir (_film->dcp_name()));
- shared_ptr<libdcp::CPL> cpl (
- new libdcp::CPL (
- _film->dir (_film->dcp_name()),
+ dcp::DCP dcp (_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 ());
- cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
- _picture_asset,
- _sound_asset,
- shared_ptr<libdcp::SubtitleAsset> ()
- )
- ));
+ 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)));
+ dcp.add (mono);
+ }
+
+ 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)));
+ dcp.add (stereo);
+ }
+
+ if (_sound_mxf) {
+ reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+ dcp.add (_sound_mxf);
+ }
+
+ 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));
- if (_sound_asset) {
+ if (_sound_mxf) {
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 pushed to disk"), _full_written, _fake_written, _pushed_to_disk)
+ LOG_GENERAL (
- N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
++ N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk
);
}
-/** Tell the writer that frame `f' should be a repeat of the frame before it */
-void
-Writer::repeat (int f, Eyes e)
-{
- boost::mutex::scoped_lock lock (_mutex);
-
- while (_queued_full_in_memory > _maximum_frames_in_memory) {
- _full_condition.wait (lock);
- }
-
- QueueItem qi;
- qi.type = QueueItem::REPEAT;
- qi.frame = f;
- if (_film->three_d() && e == EYES_BOTH) {
- qi.eyes = EYES_LEFT;
- _queue.push_back (qi);
- qi.eyes = EYES_RIGHT;
- _queue.push_back (qi);
- } else {
- qi.eyes = e;
- _queue.push_back (qi);
- }
-
- _empty_condition.notify_all ();
-}
-
bool
Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
{
/* Read the frame info as written */
FILE* ifi = fopen_boost (_film->info_path (f, eyes), "r");
if (!ifi) {
- _film->log()->log (String::compose ("Existing frame %1 has no info file", f));
+ LOG_GENERAL ("Existing frame %1 has no info file", f);
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));
+ LOG_GENERAL ("Existing frame %1 has no info file", f);
return false;
}
EncodedData data (info.size);
size_t const read = fread (data.data(), 1, data.size(), mxf);
if (read != static_cast<size_t> (data.size ())) {
- _film->log()->log (String::compose ("Existing frame %1 is incomplete", f));
+ LOG_GENERAL ("Existing frame %1 is incomplete", f);
return false;
}
string const existing_hash = md5_digest (data.data(), data.size());
if (existing_hash != info.hash) {
- _film->log()->log (String::compose ("Existing frame %1 failed hash check", f));
+ LOG_GENERAL ("Existing frame %1 failed hash check", f);
return false;
}
p /= _film->internal_video_mxf_filename ();
FILE* mxf = fopen_boost (p, "rb");
if (!mxf) {
- _film->log()->log (String::compose ("Could not open existing MXF at %1 (errno=%2)", p.string(), errno));
+ LOG_GENERAL ("Could not open existing MXF at %1 (errno=%2)", p.string(), errno);
return;
}
}
}
- _film->log()->log (String::compose ("Have existing frame %1", _first_nonexistant_frame));
+ LOG_GENERAL ("Have existing frame %1", _first_nonexistant_frame);
++_first_nonexistant_frame;
}
#include <wx/stdpaths.h>
#include <wx/cmdline.h>
#include <wx/preferences.h>
+ #include <libdcp/exceptions.h>
#include "wx/film_viewer.h"
#include "wx/film_editor.h"
#include "wx/job_manager_view.h"
static FilmEditor* film_editor = 0;
static FilmViewer* film_viewer = 0;
static shared_ptr<Film> film;
- static std::string log_level;
static std::string film_to_load;
static std::string film_to_create;
static std::string content_to_add;
for (list<string>::const_iterator i = notes.begin(); i != notes.end(); ++i) {
error_dialog (0, std_to_wx (*i));
}
- film->log()->set_level (log_level);
}
#define ALWAYS 0x0
#define NEEDS_FILM 0x1
#define NOT_DURING_DCP_CREATION 0x2
- #define NEEDS_DCP 0x4
+ #define NEEDS_CPL 0x4
map<wxMenuItem*, int> menu_items;
++i;
}
bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
- bool const have_dcp = film && !film->dcps().empty ();
+ bool const have_cpl = film && !film->cpls().empty ();
for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
enabled = false;
}
- if ((j->second & NEEDS_DCP) && !have_dcp) {
+ if ((j->second & NEEDS_CPL) && !have_cpl) {
enabled = false;
}
jobs_menu = new wxMenu;
add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
- add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM | NEEDS_DCP);
- add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_DCP);
- add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_DCP);
+ add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM);
+ add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
+ add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
wxMenu* tools = new wxMenu;
add_item (tools, _("Hints..."), ID_tools_hints, 0);
maybe_save_then_delete_film ();
film.reset (new Film (d->get_path ()));
film->write_metadata ();
- film->log()->set_level (log_level);
film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
set_film ();
}
try {
if (d->write_to ()) {
- write_kdm_files (film, d->screens (), d->dcp (), d->from (), d->until (), d->directory ());
+ write_kdm_files (film, d->screens (), d->cpl (), d->from (), d->until (), d->directory ());
} else {
JobManager::instance()->add (
- shared_ptr<Job> (new SendKDMEmailJob (film, d->screens (), d->dcp (), d->from (), d->until ()))
+ shared_ptr<Job> (new SendKDMEmailJob (film, d->screens (), d->cpl (), d->from (), d->until ()))
);
}
+ } catch (libdcp::NotEncryptedError& e) {
+ error_dialog (this, _("CPL's content is not encrypted."));
} catch (exception& e) {
error_dialog (this, e.what ());
} catch (...) {
};
static const wxCmdLineEntryDesc command_line_description[] = {
- { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_OPTION, "c", "content", "add content file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
};
+/** @class App
+ * @brief The magic App class for wxWidgets.
+ */
class App : public wxApp
{
bool OnInit ()
if (!film_to_create.empty ()) {
film.reset (new Film (film_to_create));
film->write_metadata ();
- film->log()->set_level (log_level);
film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
}
content_to_add = wx_to_std (content);
}
- wxString log;
- if (parser.Found (wxT ("log"), &log)) {
- log_level = wx_to_std (log);
- }
-
return true;
}
#include <iostream>
#include <iomanip>
#include <getopt.h>
-#include <libdcp/version.h>
+#include <dcp/version.h>
#include "lib/film.h"
#include "lib/filter.h"
#include "lib/transcode_job.h"
string film_dir;
bool progress = true;
bool no_remote = false;
- int log_level = 0;
int json_port = 0;
bool keep_going = false;
{ "flags", no_argument, 0, 'f'},
{ "no-progress", no_argument, 0, 'n'},
{ "no-remote", no_argument, 0, 'r'},
- { "log-level", required_argument, 0, 'l' },
{ "json", required_argument, 0, 'j' },
{ "keep-going", no_argument, 0, 'k' },
{ 0, 0, 0, 0 }
};
- int c = getopt_long (argc, argv, "vhdfnrl:j:k", long_options, &option_index);
+ int c = getopt_long (argc, argv, "vhdfnrj:k", long_options, &option_index);
if (c == -1) {
break;
case 'r':
no_remote = true;
break;
- case 'l':
- log_level = atoi (optarg);
- break;
case 'j':
json_port = atoi (optarg);
break;
exit (EXIT_FAILURE);
}
- film->log()->set_level ((Log::Level) log_level);
-
cout << "\nMaking DCP for " << film->name() << "\n";
// cout << "Content: " << film->content() << "\n";
// pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
*/
#include <getopt.h>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
#include "lib/film.h"
#include "lib/cinema.h"
#include "lib/kdm.h"
using std::cout;
using std::cerr;
using std::list;
+ using std::vector;
using boost::shared_ptr;
static string program_name;
cerr << "Syntax: " << program_name << " [OPTION] [<FILM>]\n"
" -h, --help show this help\n"
" -o, --output output file or directory\n"
- " -f, --valid-from valid from time (e.g. \"2013-09-28 01:41:51\") or \"now\"\n"
- " -t, --valid-to valid to time (e.g. \"2014-09-28 01:41:51\")\n"
+ " -f, --valid-from valid from time (in local time zone) (e.g. \"2013-09-28 01:41:51\") or \"now\"\n"
+ " -t, --valid-to valid to time (in local time zone) (e.g. \"2014-09-28 01:41:51\")\n"
" -d, --valid-duration valid duration (e.g. \"1 day\", \"4 hours\", \"2 weeks\")\n"
" -z, --zip ZIP each cinema's KDMs into its own file\n"
" -v, --verbose be verbose\n"
}
/* XXX: allow specification of this */
- list<boost::filesystem::path> dcps = film->dcps ();
- if (dcps.empty ()) {
- error ("no DCPs found in film");
- } else if (dcps.size() > 1) {
- error ("more than one DCP found in film");
+ vector<CPLSummary> cpls = film->cpls ();
+ if (cpls.empty ()) {
+ error ("no CPLs found in film");
+ } else if (cpls.size() > 1) {
+ error ("more than one CPL found in film");
}
- boost::filesystem::path dcp = dcps.front ();
+ boost::filesystem::path cpl = cpls.front().cpl_file;
if (cinema_name.empty ()) {
error ("you must specify --output");
}
- shared_ptr<libdcp::Certificate> certificate (new libdcp::Certificate (boost::filesystem::path (certificate_file)));
- libdcp::KDM kdm = film->make_kdm (certificate, cpl, valid_from.get(), valid_to.get());
+ shared_ptr<dcp::Certificate> certificate (new dcp::Certificate (boost::filesystem::path (certificate_file)));
- dcp::EncryptedKDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get());
++ dcp::EncryptedKDM kdm = film->make_kdm (certificate, cpl, valid_from.get(), valid_to.get());
kdm.as_xml (output);
if (verbose) {
cout << "Generated KDM " << output << " for certificate.\n";
try {
if (zip) {
- write_kdm_zip_files (film, (*i)->screens(), dcp, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
- write_kdm_zip_files (film, (*i)->screens(), cpl, valid_from.get(), valid_to.get(), output);
++ write_kdm_zip_files (film, (*i)->screens(), cpl, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
++
if (verbose) {
cout << "Wrote ZIP files to " << output << "\n";
}
} else {
- write_kdm_files (film, (*i)->screens(), dcp, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
- write_kdm_files (film, (*i)->screens(), cpl, valid_from.get(), valid_to.get(), output);
++ write_kdm_files (film, (*i)->screens(), cpl, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
if (verbose) {
cout << "Wrote KDM files to " << output << "\n";
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-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
*/
+/** @file src/wx/about_dialog.cc
+ * @brief The "about DCP-o-matic" dialogue box.
+ */
+
#include <wx/notebook.h>
#include <wx/hyperlink.h>
#include "lib/version.h"
tested_by.Add (wxT ("Andreas Eli"));
tested_by.Add (wxT ("Maurizio Giampà "));
tested_by.Add (wxT ("Luke Granger-Brown"));
+ tested_by.Add (wxT ("Sumit Guha"));
tested_by.Add (wxT ("Steve Guttag"));
tested_by.Add (wxT ("Patrick Haderer"));
+ tested_by.Add (wxT ("Bill Hamell"));
tested_by.Add (wxT ("Jonathan Jensen"));
tested_by.Add (wxT ("Thierry Journet"));
tested_by.Add (wxT ("Ada de Kamper"));
SetSizerAndFit (overall_sizer);
}
+/** Add a section of credits.
+ * @param name Name of section.
+ * @param credits List of names.
+ */
void
AboutDialog::add_section (wxString name, wxArrayString credits)
{
#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"
_num_local_encoding_threads = new wxSpinCtrl (panel);
table->Add (_num_local_encoding_threads, 1);
- add_label_to_sizer (table, panel, _("Maximum JPEG2000 bandwidth"), true);
- _maximum_j2k_bandwidth = new wxSpinCtrl (panel);
- table->Add (_maximum_j2k_bandwidth, 1);
-
- _allow_any_dcp_frame_rate = new wxCheckBox (panel, wxID_ANY, _("Allow any DCP frame rate"));
- table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
- table->AddSpacer (0);
-
add_label_to_sizer (table, panel, _("Outgoing mail server"), true);
_mail_server = new wxTextCtrl (panel, wxID_ANY);
table->Add (_mail_server, 1, wxEXPAND | wxALL);
_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));
- _maximum_j2k_bandwidth->SetRange (1, 500);
- _maximum_j2k_bandwidth->SetValue (config->maximum_j2k_bandwidth() / 1000000);
- _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::maximum_j2k_bandwidth_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 ()));
_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));
- _allow_any_dcp_frame_rate->SetValue (config->allow_any_dcp_frame_rate ());
- _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::allow_any_dcp_frame_rate_changed, this));
return panel;
}
Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
}
- void maximum_j2k_bandwidth_changed ()
- {
- Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
- }
-
- void allow_any_dcp_frame_rate_changed ()
- {
- Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
- }
-
wxCheckBox* _set_language;
wxChoice* _language;
wxSpinCtrl* _num_local_encoding_threads;
- wxSpinCtrl* _maximum_j2k_bandwidth;
- wxCheckBox* _allow_any_dcp_frame_rate;
wxTextCtrl* _mail_server;
wxTextCtrl* _mail_user;
wxTextCtrl* _mail_password;
void issuer_changed ()
{
- libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
m.issuer = wx_to_std (_issuer->GetValue ());
Config::instance()->set_dcp_metadata (m);
}
void creator_changed ()
{
- libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
m.creator = wx_to_std (_creator->GetValue ());
Config::instance()->set_dcp_metadata (m);
}
wxTextCtrl* _kdm_email;
};
+ class AdvancedPage : public wxStockPreferencesPage, public Page
+ {
+ public:
+
+ AdvancedPage (wxSize panel_size, int border)
+ : wxStockPreferencesPage (Kind_Advanced)
+ , Page (panel_size, border)
+ {}
+
+ 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, _("Maximum JPEG2000 bandwidth"), true);
+ _maximum_j2k_bandwidth = new wxSpinCtrl (panel);
+ table->Add (_maximum_j2k_bandwidth, 1);
+
+ _allow_any_dcp_frame_rate = new wxCheckBox (panel, wxID_ANY, _("Allow any DCP frame rate"));
+ table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
+ table->AddSpacer (0);
+
+ add_label_to_sizer (table, panel, _("Log"), true);
+ _log_general = new wxCheckBox (panel, wxID_ANY, _("General"));
+ table->Add (_log_general, 1, wxEXPAND | wxALL);
+ _log_warning = new wxCheckBox (panel, wxID_ANY, _("Warnings"));
+ table->AddSpacer (0);
+ table->Add (_log_warning, 1, wxEXPAND | wxALL);
+ _log_error = new wxCheckBox (panel, wxID_ANY, _("Errors"));
+ table->AddSpacer (0);
+ table->Add (_log_error, 1, wxEXPAND | wxALL);
+ _log_timing = new wxCheckBox (panel, wxID_ANY, _("Timing"));
+ table->AddSpacer (0);
+ table->Add (_log_timing, 1, wxEXPAND | wxALL);
+
+ Config* config = Config::instance ();
+
+ _maximum_j2k_bandwidth->SetRange (1, 500);
+ _maximum_j2k_bandwidth->SetValue (config->maximum_j2k_bandwidth() / 1000000);
+ _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
+ _allow_any_dcp_frame_rate->SetValue (config->allow_any_dcp_frame_rate ());
+ _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
+ _log_general->SetValue (config->log_types() & Log::TYPE_GENERAL);
+ _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
+ _log_warning->SetValue (config->log_types() & Log::TYPE_WARNING);
+ _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
+ _log_error->SetValue (config->log_types() & Log::TYPE_ERROR);
+ _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
+ _log_timing->SetValue (config->log_types() & Log::TYPE_TIMING);
+ _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
+
+ return panel;
+ }
+
+ private:
+
+ void maximum_j2k_bandwidth_changed ()
+ {
+ Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
+ }
+
+ void allow_any_dcp_frame_rate_changed ()
+ {
+ Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
+ }
+
+ void log_changed ()
+ {
+ int types = 0;
+ if (_log_general->GetValue ()) {
+ types |= Log::TYPE_GENERAL;
+ }
+ if (_log_warning->GetValue ()) {
+ types |= Log::TYPE_WARNING;
+ }
+ if (_log_error->GetValue ()) {
+ types |= Log::TYPE_ERROR;
+ }
+ if (_log_timing->GetValue ()) {
+ types |= Log::TYPE_TIMING;
+ }
+ Config::instance()->set_log_types (types);
+ }
+
+ wxSpinCtrl* _maximum_j2k_bandwidth;
+ wxCheckBox* _allow_any_dcp_frame_rate;
+ wxCheckBox* _log_general;
+ wxCheckBox* _log_warning;
+ wxCheckBox* _log_error;
+ wxCheckBox* _log_timing;
+ };
+
wxPreferencesEditor*
create_config_dialog ()
{
e->AddPage (new ColourConversionsPage (ps, border));
e->AddPage (new TMSPage (ps, border));
e->AddPage (new KDMEmailPage (ps, border));
+ e->AddPage (new AdvancedPage (ps, border));
return e;
}
_content->InsertColumn (0, wxT(""));
_content->SetColumnWidth (0, 512);
- #ifdef DCPOMATIC_OSX
- int const pad = 2;
- #else
- int const pad = 0;
- #endif
-
wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
_content_add_file = new wxButton (_content_panel, wxID_ANY, _("Add file(s)..."));
- b->Add (_content_add_file, 1, wxEXPAND | wxALL, pad);
+ b->Add (_content_add_file, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
_content_add_folder = new wxButton (_content_panel, wxID_ANY, _("Add folder..."));
- b->Add (_content_add_folder, 1, wxEXPAND | wxALL, pad);
+ b->Add (_content_add_folder, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
_content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
- b->Add (_content_remove, 1, wxEXPAND | wxALL, pad);
+ b->Add (_content_remove, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
_content_earlier = new wxButton (_content_panel, wxID_ANY, _("Up"));
- b->Add (_content_earlier, 1, wxEXPAND | wxALL, pad);
+ b->Add (_content_earlier, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
_content_later = new wxButton (_content_panel, wxID_ANY, _("Down"));
- b->Add (_content_later, 1, wxEXPAND | wxALL, pad);
+ b->Add (_content_later, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
_content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
- b->Add (_content_timeline, 1, wxEXPAND | wxALL, pad);
+ b->Add (_content_timeline, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
s->Add (b, 0, wxALL, 4);
_video_panel->Enable (video_selection.size() > 0 && _generally_sensitive);
_audio_panel->Enable (audio_selection.size() > 0 && _generally_sensitive);
- _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
+ _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive);
_timing_panel->Enable (selection.size() == 1 && _generally_sensitive);
}
*/
+/** @file test/client_server_test.cc
+ * @brief Test the server class.
+ *
+ * Create a test image and then encode it using the standard mechanism
+ * and also using a Server object running on localhost. Compare the resulting
+ * encoded data to check that they are the same.
+ */
+
#include <boost/test/unit_test.hpp>
#include <boost/thread.hpp>
#include "lib/server.h"
BOOST_CHECK (remotely_encoded);
BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
- BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
+ BOOST_CHECK_EQUAL (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()), 0);
}
BOOST_AUTO_TEST_CASE (client_server_test)
{
- shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_RGB24, dcp::Size (1998, 1080), true));
uint8_t* p = image->data()[0];
for (int y = 0; y < 1080; ++y) {
p += image->stride()[0];
}
- shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
+ shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, dcp::Size (100, 200), true));
p = sub_image->data()[0];
for (int y = 0; y < 200; ++y) {
uint8_t* q = p;
p += sub_image->stride()[0];
}
+ shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
+
shared_ptr<PlayerVideoFrame> pvf (
new PlayerVideoFrame (
- shared_ptr<ImageProxy> (new RawImageProxy (image)),
+ shared_ptr<ImageProxy> (new RawImageProxy (image, log)),
Crop (),
- libdcp::Size (1998, 1080),
- libdcp::Size (1998, 1080),
+ dcp::Size (1998, 1080),
+ dcp::Size (1998, 1080),
Scaler::from_id ("bicubic"),
EYES_BOTH,
PART_WHOLE,
)
);
- pvf->set_subtitle (sub_image, Position<int> (50, 60));
+ pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
- shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
-
shared_ptr<DCPVideoFrame> frame (
new DCPVideoFrame (
pvf,
/*
- 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
*/
+/** @file test/ffmpeg_dcp_test.cc
+ * @brief Test creation of a very simple DCP from some FFmpegContent (data/test.mp4).
+ *
+ * Also a quick test of Film::have_dcp ().
+ */
+
#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
#include "lib/film.h"
using boost::shared_ptr;
-/** @file test/ffmpeg_dcp_test.cc
- * @brief Test scaling and black-padding of images from a still-image source.
- */
-
BOOST_AUTO_TEST_CASE (ffmpeg_dcp_test)
{
shared_ptr<Film> film = new_test_film ("ffmpeg_dcp_test");
wait_for_jobs ();
}
- /** Test Film::have_dcp(). Requires the output from ffmpeg_dcp_test above */
+ /** Briefly test Film::cpls(). Requires the output from ffmpeg_dcp_test above */
BOOST_AUTO_TEST_CASE (ffmpeg_have_dcp_test)
{
boost::filesystem::path p = test_film_dir ("ffmpeg_dcp_test");
shared_ptr<Film> f (new Film (p.string ()));
f->read_metadata ();
- BOOST_CHECK (!f->dcps().empty());
+ BOOST_CHECK (!f->cpls().empty());
p /= f->dcp_name();
p /= f->video_mxf_filename();
boost::filesystem::remove (p);
- BOOST_CHECK (f->dcps().empty());
+ BOOST_CHECK (f->cpls().empty());
}