+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.
_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:
#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.
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()
)
));
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);
#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;
/* 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 ();
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 (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;
}
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");
}
}
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
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
) {
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 (...) {
}
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,
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;
/* 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;
}
}
-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
}
- 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")
);
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;
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;
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;
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
)
{
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
)
{
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 "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;
}
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 ();
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;
}
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;
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;
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 */
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
);
}
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;
}
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";
}
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,