From: Carl Hetherington Date: Tue, 20 May 2014 12:23:26 +0000 (+0100) Subject: Merge master. X-Git-Tag: v2.0.48~819 X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=39bc73fe192f932ed6695eb87b19de446e8b4f55 Merge master. --- 39bc73fe192f932ed6695eb87b19de446e8b4f55 diff --cc ChangeLog index 33b7e2e21,da42e7ba1..6f31dd5d2 --- a/ChangeLog +++ b/ChangeLog @@@ -1,7 -1,12 +1,16 @@@ +2014-03-07 Carl Hetherington + + * Add subtitle view. + + 2014-05-19 Carl Hetherington + + * Version 1.69.9 released. + + 2014-05-19 Carl Hetherington + + * Decode image sources in the multi-threaded part + of the transcoder, rather than the single-threaded. + 2014-05-16 Carl Hetherington * Version 1.69.8 released. diff --cc src/lib/content_video.h index 20b5b8dec,000000000..a7f73597c mode 100644,000000..100644 --- a/src/lib/content_video.h +++ b/src/lib/content_video.h @@@ -1,46 -1,0 +1,48 @@@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DCPOMATIC_CONTENT_VIDEO_H +#define DCPOMATIC_CONTENT_VIDEO_H + - class Image; ++class ImageProxy; + +/** @class ContentVideo + * @brief A frame of video straight out of some content. + */ +class ContentVideo +{ +public: + ContentVideo () + : eyes (EYES_BOTH) + {} + - ContentVideo (boost::shared_ptr i, Eyes e, VideoFrame f) ++ ContentVideo (boost::shared_ptr i, Eyes e, Part p, VideoFrame f) + : image (i) + , eyes (e) ++ , part (p) + , frame (f) + {} + - boost::shared_ptr image; ++ boost::shared_ptr image; + Eyes eyes; ++ Part part; + VideoFrame frame; +}; + +#endif diff --cc src/lib/dcp_video_frame.cc index d860c3195,5cd6a118e..d154ba96b --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@@ -42,12 -42,14 +42,13 @@@ #include #include #include +#include + #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include "film.h" #include "dcp_video_frame.h" @@@ -120,28 -109,47 +109,44 @@@ DCPVideoFrame::DCPVideoFrame (shared_pt shared_ptr DCPVideoFrame::encode_locally () { - shared_ptr in_lut; - in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised); - shared_ptr 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 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]; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - matrix[i][j] = _conversion.matrix (i, j); + matrix[i][j] = _frame->colour_conversion().matrix (i, j); } } - + - shared_ptr xyz = libdcp::rgb_to_xyz ( + shared_ptr xyz = dcp::rgb_to_xyz ( - _image, + _frame->image(), in_lut, - dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false), - libdcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma), ++ dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false), matrix ); + + { + MD5_CTX md5_context; + MD5_Init (&md5_context); + MD5_Update (&md5_context, xyz->data(0), 1998 * 1080 * 4); + MD5_Update (&md5_context, xyz->data(1), 1998 * 1080 * 4); + MD5_Update (&md5_context, xyz->data(2), 1998 * 1080 * 4); + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_Final (digest, &md5_context); + stringstream s; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); + } + } + /* Set the max image and component sizes based on frame_rate */ int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second; - if (_eyes == EYES_LEFT || _eyes == EYES_RIGHT) { + if (_frame->eyes() == EYES_LEFT || _frame->eyes() == EYES_RIGHT) { /* In 3D we have only half the normal bandwidth per eye */ max_cs_len /= 2; } diff --cc src/lib/dcp_video_frame.h index c51a3f02b,e4006d986..7393efde6 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@@ -100,8 -103,8 +101,8 @@@ public class DCPVideoFrame : public boost::noncopyable { public: - DCPVideoFrame (boost::shared_ptr, int, Eyes, ColourConversion, int, int, Resolution, boost::shared_ptr); - DCPVideoFrame (boost::shared_ptr, cxml::ConstNodePtr, boost::shared_ptr); + DCPVideoFrame (boost::shared_ptr, int, int, int, Resolution, boost::shared_ptr); - DCPVideoFrame (boost::shared_ptr, boost::shared_ptr, boost::shared_ptr); ++ DCPVideoFrame (boost::shared_ptr, cxml::ConstNodePtr, boost::shared_ptr); boost::shared_ptr encode_locally (); boost::shared_ptr encode_remotely (ServerDescription); diff --cc src/lib/encoder.cc index b83cbc10a,4fc2d7f81..2364b67a7 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@@ -178,7 -180,7 +178,7 @@@ Encoder::frame_done ( } void - Encoder::process_video (shared_ptr frame) -Encoder::process_video (shared_ptr pvf, bool same) ++Encoder::process_video (shared_ptr pvf) { _waker.nudge (); @@@ -205,28 -207,28 +205,26 @@@ rethrow (); if (_writer->can_fake_write (_video_frames_out)) { - _writer->fake_write (_video_frames_out, frame->eyes ()); + _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 ()); _queue.push_back (shared_ptr ( new DCPVideoFrame ( - frame->image(PIX_FMT_RGB24, false), - pvf, _video_frames_out, _film->video_frame_rate(), - _film->j2k_bandwidth(), _film->resolution(), _film->log() ++ pvf, + _video_frames_out, - frame->eyes(), - frame->conversion(), + _film->video_frame_rate(), + _film->j2k_bandwidth(), + _film->resolution(), + _film->log() ) )); _condition.notify_all (); - _have_a_real_frame[pvf->eyes()] = true; } - if (frame->eyes() != EYES_LEFT) { + if (pvf->eyes() != EYES_LEFT) { ++_video_frames_out; } } diff --cc src/lib/encoder.h index 6c465f816,a8ee220aa..ac1d74c57 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@@ -67,9 -67,10 +67,9 @@@ public void process_begin (); /** Call with a frame of video. - * @param pvf Video frame image. - * @param same true if pvf is the same as the last time we were called. + * @param f Video frame. */ - void process_video (boost::shared_ptr f); - void process_video (boost::shared_ptr pvf, bool same); ++ void process_video (boost::shared_ptr f); /** Call with some audio data */ void process_audio (boost::shared_ptr); diff --cc src/lib/ffmpeg_decoder.cc index 9ae5f0485,7a5bf8ba8..d251a3744 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@@ -483,10 -473,49 +484,10 @@@ FFmpegDecoder::decode_video_packet ( shared_ptr 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 black ( - new Image ( - static_cast (_frame->format), - libdcp::Size (video_codec_context()->width, video_codec_context()->height), - true - ) - ); - - black->make_black (); - video (shared_ptr (new RawImageProxy (image)), false, _video_position); - delta -= one_frame; - } - - if (delta > -one_frame) { - /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */ - video (shared_ptr (new RawImageProxy (image)), false, _video_position); - } - + double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds (); - video (image, rint (pts * _ffmpeg_content->video_frame_rate ())); ++ video (shared_ptr (new RawImageProxy (image)), rint (pts * _ffmpeg_content->video_frame_rate ())); } else { - shared_ptr film = _film.lock (); - assert (film); - film->log()->log ("Dropping frame without PTS"); + _log->log ("Dropping frame without PTS"); } } diff --cc src/lib/image.cc index 432cfbd54,1fa55e242..d4ec6f99a --- a/src/lib/image.cc +++ b/src/lib/image.cc @@@ -39,9 -38,9 +40,10 @@@ using std::string using std::min; using std::cout; using std::cerr; +using std::list; + using std::stringstream; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; int Image::line_factor (int n) const @@@ -642,23 -621,24 +644,44 @@@ Image::aligned () cons return _aligned; } +PositionImage +merge (list images) +{ + if (images.empty ()) { + return PositionImage (); + } + + dcpomatic::Rect all (images.front().position, images.front().image->size().width, images.front().image->size().height); + for (list::const_iterator i = images.begin(); i != images.end(); ++i) { + all.extend (dcpomatic::Rect (i->position, i->image->size().width, i->image->size().height)); + } + + shared_ptr merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true)); + merged->make_transparent (); + for (list::const_iterator i = images.begin(); i != images.end(); ++i) { + merged->alpha_blend (i->image, i->position); + } + + return PositionImage (merged, all.position ()); +} ++ + string + Image::digest () const + { + MD5_CTX md5_context; + MD5_Init (&md5_context); + + for (int i = 0; i < components(); ++i) { + MD5_Update (&md5_context, data()[i], line_size()[i]); + } + + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_Final (digest, &md5_context); + + stringstream s; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); + } + + return s.str (); + } - diff --cc src/lib/image_decoder.cc index 5de0c8582,d33b64cd4..9f83d1d89 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@@ -39,53 -41,20 +40,21 @@@ ImageDecoder::ImageDecoder (shared_ptr< } -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, _video_position); - ++_video_position; - return false; - 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))); } - - Magick::Image* magick_image = 0; - - boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position); - - try { - magick_image = new Magick::Image (path.string ()); - } catch (...) { - throw OpenFileError (path); - } - - dcp::Size size (magick_image->columns(), magick_image->rows()); - - _image.reset (new Image (PIX_FMT_RGB24, size, true)); - - using namespace MagickCore; - - uint8_t* p = _image->data()[0]; - for (int y = 0; y < size.height; ++y) { - uint8_t* q = p; - for (int x = 0; x < size.width; ++x) { - Magick::Color c = magick_image->pixelColor (x, y); - *q++ = c.redQuantum() * 255 / QuantumRange; - *q++ = c.greenQuantum() * 255 / QuantumRange; - *q++ = c.blueQuantum() * 255 / QuantumRange; - } - p += _image->stride()[0]; - } - - delete magick_image; -- - _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position))); - video (_image, false, _video_position); ++ + video (_image, _video_position); + ++_video_position; - + return false; } void diff --cc src/lib/image_decoder.h index 8d88df3de,5b82dd85c..242f69477 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@@ -34,13 -34,14 +34,13 @@@ public return _image_content; } - /* Decoder */ - - void pass (); - void seek (VideoContent::Frame, bool); - bool done () const; + void seek (ContentTime, bool); private: + bool pass (); + boost::shared_ptr _image_content; - boost::shared_ptr _image; + boost::shared_ptr _image; + VideoFrame _video_position; }; diff --cc src/lib/image_proxy.cc index 000000000,47ac5d372..c74e846c9 mode 000000,100644..100644 --- a/src/lib/image_proxy.cc +++ b/src/lib/image_proxy.cc @@@ -1,0 -1,161 +1,161 @@@ + /* + Copyright (C) 2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + + #include -#include -#include ++#include ++#include + #include "image_proxy.h" + #include "image.h" + #include "exceptions.h" + #include "cross.h" + + #include "i18n.h" + + using std::cout; + using std::string; + using std::stringstream; + using boost::shared_ptr; + + RawImageProxy::RawImageProxy (shared_ptr image) + : _image (image) + { + + } + + RawImageProxy::RawImageProxy (shared_ptr xml, shared_ptr socket) + { - libdcp::Size size ( ++ dcp::Size size ( + xml->number_child ("Width"), xml->number_child ("Height") + ); + + _image.reset (new Image (PIX_FMT_RGB24, size, true)); + _image->read_from_socket (socket); + } + + shared_ptr + RawImageProxy::image () const + { + return _image; + } + + void + 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 (_image->size().width)); - node->add_child("Height")->add_child_text (libdcp::raw_convert (_image->size().height)); ++ node->add_child("Width")->add_child_text (dcp::raw_convert (_image->size().width)); ++ node->add_child("Height")->add_child_text (dcp::raw_convert (_image->size().height)); + } + + void + RawImageProxy::send_binary (shared_ptr socket) const + { + _image->write_to_socket (socket); + } + + MagickImageProxy::MagickImageProxy (boost::filesystem::path path) + { + /* Read the file into a Blob */ + + boost::uintmax_t const size = boost::filesystem::file_size (path); + FILE* f = fopen_boost (path, "rb"); + if (!f) { + throw OpenFileError (path); + } + + uint8_t* data = new uint8_t[size]; + if (fread (data, 1, size, f) != size) { + delete[] data; + throw ReadFileError (path); + } + + fclose (f); + _blob.update (data, size); + delete[] data; + } + + MagickImageProxy::MagickImageProxy (shared_ptr, shared_ptr socket) + { + uint32_t const size = socket->read_uint32 (); + uint8_t* data = new uint8_t[size]; + socket->read (data, size); + _blob.update (data, size); + delete[] data; + } + + shared_ptr + MagickImageProxy::image () const + { + if (_image) { + return _image; + } + + Magick::Image* magick_image = 0; + try { + magick_image = new Magick::Image (_blob); + } catch (...) { + throw DecodeError (_("Could not decode image file")); + } + - libdcp::Size size (magick_image->columns(), magick_image->rows()); ++ dcp::Size size (magick_image->columns(), magick_image->rows()); + + _image.reset (new Image (PIX_FMT_RGB24, size, true)); + + using namespace MagickCore; + + uint8_t* p = _image->data()[0]; + for (int y = 0; y < size.height; ++y) { + uint8_t* q = p; + for (int x = 0; x < size.width; ++x) { + Magick::Color c = magick_image->pixelColor (x, y); + *q++ = c.redQuantum() * 255 / QuantumRange; + *q++ = c.greenQuantum() * 255 / QuantumRange; + *q++ = c.blueQuantum() * 255 / QuantumRange; + } + p += _image->stride()[0]; + } + + delete magick_image; + + return _image; + } + + void + MagickImageProxy::add_metadata (xmlpp::Node* node) const + { + node->add_child("Type")->add_child_text (N_("Magick")); + } + + void + MagickImageProxy::send_binary (shared_ptr socket) const + { + socket->write (_blob.length ()); + socket->write ((uint8_t *) _blob.data (), _blob.length ()); + } + + shared_ptr + image_proxy_factory (shared_ptr xml, shared_ptr socket) + { + if (xml->string_child("Type") == N_("Raw")) { + return shared_ptr (new RawImageProxy (xml, socket)); + } else if (xml->string_child("Type") == N_("Magick")) { + return shared_ptr (new MagickImageProxy (xml, socket)); + } + + throw NetworkError (_("Unexpected image type received by server")); + } diff --cc src/lib/player.cc index 75b550093,9f0f380e3..ab0d8f356 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@@ -34,13 -30,12 +34,14 @@@ #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 "dcp_video.h" +#include "config.h" +#include "content_video.h" + #include "player_video_frame.h" using std::list; using std::cout; @@@ -282,167 -340,79 +283,167 @@@ Player::process_content_text_subtitles } void -Player::flush () +Player::set_approximate_size () { - TimedAudioBuffers