diff options
| author | Carl Hetherington <cth@carlh.net> | 2012-10-15 12:42:22 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2012-10-15 12:42:22 +0100 |
| commit | 13511ed2fcc23f4d5f9c507c775c3c5cfd82d155 (patch) | |
| tree | 5ab1d1600725873a199725e50d67da9791c25d67 /src | |
| parent | cb33319a820b17a05cfb2ef78ba1799f4d0c54b9 (diff) | |
| parent | 43990add893eccf350f280e2dd3f947a94f3e9aa (diff) | |
Merge branch 'master' of /home/carl/git/dvdomatic
Diffstat (limited to 'src')
60 files changed, 1316 insertions, 393 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc index d94f56d0a..fd8236bf0 100644 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@ -35,8 +35,8 @@ using namespace boost; * @param o Options. * @Param l A log that we can write to. */ -ABTranscodeJob::ABTranscodeJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) - : Job (s, o, l) +ABTranscodeJob::ABTranscodeJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, shared_ptr<Job> req) + : Job (s, o, l, req) { _fs_b.reset (new FilmState (*_fs)); _fs_b->scaler = Config::instance()->reference_scaler (); diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h index 478049068..4b80593f4 100644 --- a/src/lib/ab_transcode_job.h +++ b/src/lib/ab_transcode_job.h @@ -34,7 +34,7 @@ class ABTranscodeJob : public Job { public: - ABTranscodeJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); + ABTranscodeJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l, boost::shared_ptr<Job> req); std::string name () const; void run (); diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc index 1c20ae477..54153ec76 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -70,7 +70,7 @@ ABTranscoder::~ABTranscoder () } void -ABTranscoder::process_video (shared_ptr<Image> yuv, int frame, int index) +ABTranscoder::process_video (shared_ptr<Image> yuv, int frame, shared_ptr<Subtitle> sub, int index) { if (index == 0) { /* Keep this image around until we get the other half */ @@ -80,19 +80,20 @@ ABTranscoder::process_video (shared_ptr<Image> yuv, int frame, int index) for (int i = 0; i < yuv->components(); ++i) { int const line_size = yuv->line_size()[i]; int const half_line_size = line_size / 2; + int const stride = yuv->stride()[i]; uint8_t* p = _image->data()[i]; uint8_t* q = yuv->data()[i]; for (int j = 0; j < yuv->lines (i); ++j) { memcpy (p + half_line_size, q + half_line_size, half_line_size); - p += line_size; - q += line_size; + p += stride; + q += stride; } } /* And pass it to the encoder */ - _encoder->process_video (_image, frame); + _encoder->process_video (_image, frame, sub); _image.reset (); } diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h index 0310bb923..491205ef7 100644 --- a/src/lib/ab_transcoder.h +++ b/src/lib/ab_transcoder.h @@ -32,6 +32,7 @@ class FilmState; class Options; class Image; class Log; +class Subtitle; /** @class ABTranscoder * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one @@ -54,7 +55,7 @@ public: void go (); private: - void process_video (boost::shared_ptr<Image>, int, int); + void process_video (boost::shared_ptr<Image>, int, boost::shared_ptr<Subtitle>, int); boost::shared_ptr<const FilmState> _fs_a; boost::shared_ptr<const FilmState> _fs_b; diff --git a/src/lib/check_hashes_job.cc b/src/lib/check_hashes_job.cc index f60a2d40d..f07a5ab2a 100644 --- a/src/lib/check_hashes_job.cc +++ b/src/lib/check_hashes_job.cc @@ -31,8 +31,8 @@ using namespace std; using namespace boost; -CheckHashesJob::CheckHashesJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) - : Job (s, o, l) +CheckHashesJob::CheckHashesJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, shared_ptr<Job> req) + : Job (s, o, l, req) , _bad (0) { @@ -73,13 +73,13 @@ CheckHashesJob::run () shared_ptr<Job> tc; if (_fs->dcp_ab) { - tc.reset (new ABTranscodeJob (_fs, _opt, _log)); + tc.reset (new ABTranscodeJob (_fs, _opt, _log, shared_from_this())); } else { - tc.reset (new TranscodeJob (_fs, _opt, _log)); + tc.reset (new TranscodeJob (_fs, _opt, _log, shared_from_this())); } JobManager::instance()->add_after (shared_from_this(), tc); - JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_fs, _opt, _log))); + JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_fs, _opt, _log, tc))); } set_progress (1); diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h index b59cf031b..6a68e936c 100644 --- a/src/lib/check_hashes_job.h +++ b/src/lib/check_hashes_job.h @@ -22,7 +22,7 @@ class CheckHashesJob : public Job { public: - CheckHashesJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); + CheckHashesJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l, boost::shared_ptr<Job> req); std::string name () const; void run (); diff --git a/src/lib/copy_from_dvd_job.cc b/src/lib/copy_from_dvd_job.cc index d1000f54c..f7281fc10 100644 --- a/src/lib/copy_from_dvd_job.cc +++ b/src/lib/copy_from_dvd_job.cc @@ -35,8 +35,8 @@ using namespace boost; /** @param fs FilmState for the film to write DVD data into. * @param l Log that we can write to. */ -CopyFromDVDJob::CopyFromDVDJob (shared_ptr<const FilmState> fs, Log* l) - : Job (fs, shared_ptr<Options> (), l) +CopyFromDVDJob::CopyFromDVDJob (shared_ptr<const FilmState> fs, Log* l, shared_ptr<Job> req) + : Job (fs, shared_ptr<Options> (), l, req) { } diff --git a/src/lib/copy_from_dvd_job.h b/src/lib/copy_from_dvd_job.h index 6b56f6f0a..ce3837100 100644 --- a/src/lib/copy_from_dvd_job.h +++ b/src/lib/copy_from_dvd_job.h @@ -29,7 +29,7 @@ class CopyFromDVDJob : public Job { public: - CopyFromDVDJob (boost::shared_ptr<const FilmState>, Log *); + CopyFromDVDJob (boost::shared_ptr<const FilmState>, Log *, boost::shared_ptr<Job> req); std::string name () const; void run (); diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc index 1f50c8dc4..aae805308 100644 --- a/src/lib/dcp_content_type.cc +++ b/src/lib/dcp_content_type.cc @@ -28,9 +28,10 @@ using namespace std; vector<DCPContentType const *> DCPContentType::_dcp_content_types; -DCPContentType::DCPContentType (string p, libdcp::ContentKind k) +DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d) : _pretty_name (p) , _libdcp_kind (k) + , _dci_name (d) { } @@ -38,16 +39,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k) void DCPContentType::setup_dcp_content_types () { - _dcp_content_types.push_back (new DCPContentType ("Feature", libdcp::FEATURE)); - _dcp_content_types.push_back (new DCPContentType ("Short", libdcp::SHORT)); - _dcp_content_types.push_back (new DCPContentType ("Trailer", libdcp::TRAILER)); - _dcp_content_types.push_back (new DCPContentType ("Test", libdcp::TEST)); - _dcp_content_types.push_back (new DCPContentType ("Transitional", libdcp::TRANSITIONAL)); - _dcp_content_types.push_back (new DCPContentType ("Rating", libdcp::RATING)); - _dcp_content_types.push_back (new DCPContentType ("Teaser", libdcp::TEASER)); - _dcp_content_types.push_back (new DCPContentType ("Policy", libdcp::POLICY)); - _dcp_content_types.push_back (new DCPContentType ("Public Service Announcement", libdcp::PUBLIC_SERVICE_ANNOUNCEMENT)); - _dcp_content_types.push_back (new DCPContentType ("Advertisement", libdcp::ADVERTISEMENT)); + _dcp_content_types.push_back (new DCPContentType ("Feature", libdcp::FEATURE, "FTR")); + _dcp_content_types.push_back (new DCPContentType ("Short", libdcp::SHORT, "SHR")); + _dcp_content_types.push_back (new DCPContentType ("Trailer", libdcp::TRAILER, "TLR")); + _dcp_content_types.push_back (new DCPContentType ("Test", libdcp::TEST, "TST")); + _dcp_content_types.push_back (new DCPContentType ("Transitional", libdcp::TRANSITIONAL, "XSN")); + _dcp_content_types.push_back (new DCPContentType ("Rating", libdcp::RATING, "RTG")); + _dcp_content_types.push_back (new DCPContentType ("Teaser", libdcp::TEASER, "TSR")); + _dcp_content_types.push_back (new DCPContentType ("Policy", libdcp::POLICY, "POL")); + _dcp_content_types.push_back (new DCPContentType ("Public Service Announcement", libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, "PSA")); + _dcp_content_types.push_back (new DCPContentType ("Advertisement", libdcp::ADVERTISEMENT, "ADV")); } DCPContentType const * diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h index cb858cf5a..b703970a3 100644 --- a/src/lib/dcp_content_type.h +++ b/src/lib/dcp_content_type.h @@ -31,7 +31,7 @@ class DCPContentType { public: - DCPContentType (std::string, libdcp::ContentKind); + DCPContentType (std::string, libdcp::ContentKind, std::string); /** @return user-visible `pretty' name */ std::string pretty_name () const { @@ -42,6 +42,10 @@ public: return _libdcp_kind; } + std::string dci_name () const { + return _dci_name; + } + static DCPContentType const * from_pretty_name (std::string); static DCPContentType const * from_index (int); static int as_index (DCPContentType const *); @@ -51,6 +55,7 @@ public: private: std::string _pretty_name; libdcp::ContentKind _libdcp_kind; + std::string _dci_name; /** All available DCP content types */ static std::vector<DCPContentType const *> _dcp_content_types; diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index 90826a99f..04735269c 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -55,6 +55,7 @@ #include "scaler.h" #include "image.h" #include "log.h" +#include "subtitle.h" using namespace std; using namespace boost; @@ -72,10 +73,16 @@ using namespace boost; * @param l Log to write to. */ DCPVideoFrame::DCPVideoFrame ( - shared_ptr<Image> yuv, Size out, int p, Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l) + shared_ptr<Image> yuv, shared_ptr<Subtitle> sub, + Size out, int p, int subtitle_offset, float subtitle_scale, + Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l + ) : _input (yuv) + , _subtitle (sub) , _out_size (out) , _padding (p) + , _subtitle_offset (subtitle_offset) + , _subtitle_scale (subtitle_scale) , _scaler (s) , _frame (f) /* we round here; not sure if this is right */ @@ -147,18 +154,28 @@ DCPVideoFrame::~DCPVideoFrame () shared_ptr<EncodedData> DCPVideoFrame::encode_locally () { - shared_ptr<Image> prepared = _input; - if (!_post_process.empty ()) { - prepared = prepared->post_process (_post_process); + _input = _input->post_process (_post_process); } - prepared = prepared->scale_and_convert_to_rgb (_out_size, _padding, _scaler); + shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler); + + if (_subtitle) { + list<shared_ptr<SubtitleImage> > subs = _subtitle->images (); + for (list<shared_ptr<SubtitleImage> >::iterator i = subs.begin(); i != subs.end(); ++i) { + Rectangle tx = transformed_subtitle_area ( + float (_out_size.width) / _input->size().width, + float (_out_size.height) / _input->size().height, + (*i)->area(), _subtitle_offset, _subtitle_scale + ); + + shared_ptr<Image> im = (*i)->image()->scale (Size (tx.w, tx.h), _scaler); + prepared->alpha_blend (im, Position (tx.x, tx.y)); + } + } create_openjpeg_container (); - int const size = _out_size.width * _out_size.height; - struct { double r, g, b; } s; @@ -169,27 +186,41 @@ DCPVideoFrame::encode_locally () /* Copy our RGB into the openjpeg container, converting to XYZ in the process */ - uint8_t* p = prepared->data()[0]; - for (int i = 0; i < size; ++i) { - /* In gamma LUT (converting 8-bit input to 12-bit) */ - s.r = lut_in[_colour_lut_index][*p++ << 4]; - s.g = lut_in[_colour_lut_index][*p++ << 4]; - s.b = lut_in[_colour_lut_index][*p++ << 4]; - - /* RGB to XYZ Matrix */ - d.x = ((s.r * color_matrix[_colour_lut_index][0][0]) + (s.g * color_matrix[_colour_lut_index][0][1]) + (s.b * color_matrix[_colour_lut_index][0][2])); - d.y = ((s.r * color_matrix[_colour_lut_index][1][0]) + (s.g * color_matrix[_colour_lut_index][1][1]) + (s.b * color_matrix[_colour_lut_index][1][2])); - d.z = ((s.r * color_matrix[_colour_lut_index][2][0]) + (s.g * color_matrix[_colour_lut_index][2][1]) + (s.b * color_matrix[_colour_lut_index][2][2])); - - /* DCI companding */ - d.x = d.x * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); - d.y = d.y * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); - d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); - - /* Out gamma LUT */ - _image->comps[0].data[i] = lut_out[LO_DCI][(int) d.x]; - _image->comps[1].data[i] = lut_out[LO_DCI][(int) d.y]; - _image->comps[2].data[i] = lut_out[LO_DCI][(int) d.z]; + int jn = 0; + for (int y = 0; y < _out_size.height; ++y) { + uint8_t* p = prepared->data()[0] + y * prepared->stride()[0]; + for (int x = 0; x < _out_size.width; ++x) { + + /* In gamma LUT (converting 8-bit input to 12-bit) */ + s.r = lut_in[_colour_lut_index][*p++ << 4]; + s.g = lut_in[_colour_lut_index][*p++ << 4]; + s.b = lut_in[_colour_lut_index][*p++ << 4]; + + /* RGB to XYZ Matrix */ + d.x = ((s.r * color_matrix[_colour_lut_index][0][0]) + + (s.g * color_matrix[_colour_lut_index][0][1]) + + (s.b * color_matrix[_colour_lut_index][0][2])); + + d.y = ((s.r * color_matrix[_colour_lut_index][1][0]) + + (s.g * color_matrix[_colour_lut_index][1][1]) + + (s.b * color_matrix[_colour_lut_index][1][2])); + + d.z = ((s.r * color_matrix[_colour_lut_index][2][0]) + + (s.g * color_matrix[_colour_lut_index][2][1]) + + (s.b * color_matrix[_colour_lut_index][2][2])); + + /* DCI companding */ + d.x = d.x * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); + d.y = d.y * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); + d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); + + /* Out gamma LUT */ + _image->comps[0].data[jn] = lut_out[LO_DCI][(int) d.x]; + _image->comps[1].data[jn] = lut_out[LO_DCI][(int) d.y]; + _image->comps[2].data[jn] = lut_out[LO_DCI][(int) d.z]; + + ++jn; + } } /* Set the max image and component sizes based on frame_rate */ @@ -289,6 +320,8 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv) << _input->pixel_format() << " " << _out_size.width << " " << _out_size.height << " " << _padding << " " + << _subtitle_offset << " " + << _subtitle_scale << " " << _scaler->id () << " " << _frame << " " << _frames_per_second << " " @@ -296,14 +329,10 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv) << Config::instance()->colour_lut_index () << " " << Config::instance()->j2k_bandwidth () << " "; - for (int i = 0; i < _input->components(); ++i) { - s << _input->line_size()[i] << " "; - } - socket.write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30); for (int i = 0; i < _input->components(); ++i) { - socket.write (_input->data()[i], _input->line_size()[i] * _input->lines(i), 30); + socket.write (_input->data()[i], _input->stride()[i] * _input->lines(i), 30); } char buffer[32]; diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index 72f885e45..4e9a777bd 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -31,6 +31,7 @@ class ServerDescription; class Scaler; class Image; class Log; +class Subtitle; /** @class EncodedData * @brief Container for J2K-encoded data. @@ -105,7 +106,7 @@ public: class DCPVideoFrame { public: - DCPVideoFrame (boost::shared_ptr<Image>, Size, int, Scaler const *, int, float, std::string, int, int, Log *); + DCPVideoFrame (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>, Size, int, int, float, Scaler const *, int, float, std::string, int, int, Log *); virtual ~DCPVideoFrame (); boost::shared_ptr<EncodedData> encode_locally (); @@ -120,8 +121,11 @@ private: void write_encoded (boost::shared_ptr<const Options>, uint8_t *, int); boost::shared_ptr<Image> _input; ///< the input image + boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image Size _out_size; ///< the required size of the output, in pixels int _padding; + int _subtitle_offset; + float _subtitle_scale; Scaler const * _scaler; ///< scaler to use int _frame; ///< frame index within the Film int _frames_per_second; ///< Frames per second that we will use for the DCP (rounded) diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 324d1a296..1f771da2d 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -48,6 +48,7 @@ extern "C" { #include "filter.h" #include "delay_line.h" #include "ffmpeg_compatibility.h" +#include "subtitle.h" using namespace std; using namespace boost; @@ -303,8 +304,13 @@ Decoder::process_video (AVFrame* frame) image->make_black (); } + shared_ptr<Subtitle> sub; + if (_subtitle && _subtitle->displayed_at (double (last_video_frame()) / rint (_fs->frames_per_second))) { + sub = _subtitle; + } + TIMING ("Decoder emits %1", _video_frame); - Video (image, _video_frame); + Video (image, _video_frame, sub); ++_video_frame; } } @@ -405,3 +411,16 @@ Decoder::setup_video_filters () /* XXX: leaking `inputs' / `outputs' ? */ } +void +Decoder::process_subtitle (shared_ptr<Subtitle> s) +{ + _subtitle = s; + + if (_opt->apply_crop) { + list<shared_ptr<SubtitleImage> > im = _subtitle->images (); + for (list<shared_ptr<SubtitleImage> >::iterator i = im.begin(); i != im.end(); ++i) { + Position const p = (*i)->position (); + (*i)->set_position (Position (p.x - _fs->crop.left, p.y - _fs->crop.top)); + } + } +} diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 04ff512eb..805955b9d 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -37,6 +37,7 @@ class Options; class Image; class Log; class DelayLine; +class Subtitle; /** @class Decoder. * @brief Parent class for decoders of content. @@ -66,6 +67,7 @@ public: /** @return format of audio samples */ virtual AVSampleFormat audio_sample_format () const = 0; virtual int64_t audio_channel_layout () const = 0; + virtual bool has_subtitles () const = 0; void process_begin (); bool pass (); @@ -80,8 +82,9 @@ public: /** Emitted when a video frame is ready. * First parameter is the frame. * Second parameter is its index within the content. + * Third parameter is either 0 or a subtitle that should be on this frame. */ - sigc::signal<void, boost::shared_ptr<Image>, int> Video; + sigc::signal<void, boost::shared_ptr<Image>, int, boost::shared_ptr<Subtitle> > Video; /** Emitted when some audio data is ready. * First parameter is the interleaved sample data, format is given in the FilmState. @@ -100,6 +103,7 @@ protected: void process_video (AVFrame *); void process_audio (uint8_t *, int); + void process_subtitle (boost::shared_ptr<Subtitle>); /** our FilmState */ boost::shared_ptr<const FilmState> _fs; @@ -135,6 +139,8 @@ private: (at the DCP sample rate). */ int64_t _audio_frames_processed; + + boost::shared_ptr<Subtitle> _subtitle; }; #endif diff --git a/src/lib/encoder.h b/src/lib/encoder.h index ea356cec4..02a2d7723 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -36,6 +36,7 @@ class FilmState; class Options; class Image; class Log; +class Subtitle; /** @class Encoder * @brief Parent class for classes which can encode video and audio frames. @@ -58,8 +59,9 @@ public: /** Called with a frame of video. * @param i Video frame image. * @param f Frame number within the film. + * @param s A subtitle that should be on this frame, or 0. */ - virtual void process_video (boost::shared_ptr<Image> i, int f) = 0; + virtual void process_video (boost::shared_ptr<Image> i, int f, boost::shared_ptr<Subtitle> s) = 0; /** Called with some audio data. * @param d Data. diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc index d77ede2f9..36b4cbabc 100644 --- a/src/lib/examine_content_job.cc +++ b/src/lib/examine_content_job.cc @@ -30,8 +30,8 @@ using namespace std; using namespace boost; -ExamineContentJob::ExamineContentJob (shared_ptr<const FilmState> fs, Log* l) - : Job (fs, shared_ptr<Options> (), l) +ExamineContentJob::ExamineContentJob (shared_ptr<const FilmState> fs, Log* l, shared_ptr<Job> req) + : Job (fs, shared_ptr<Options> (), l, req) { } diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h index d149341b4..3bbd673a8 100644 --- a/src/lib/examine_content_job.h +++ b/src/lib/examine_content_job.h @@ -31,7 +31,7 @@ class Decoder; class ExamineContentJob : public Job { public: - ExamineContentJob (boost::shared_ptr<const FilmState>, Log *); + ExamineContentJob (boost::shared_ptr<const FilmState>, Log *, boost::shared_ptr<Job> req); ~ExamineContentJob (); std::string name () const; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 767299ea6..e01405191 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -27,6 +27,7 @@ #include <iomanip> #include <iostream> #include <stdint.h> +#include <boost/lexical_cast.hpp> extern "C" { #include <tiffio.h> #include <libavcodec/avcodec.h> @@ -47,6 +48,7 @@ extern "C" { #include "util.h" #include "log.h" #include "ffmpeg_decoder.h" +#include "subtitle.h" using namespace std; using namespace boost; @@ -56,15 +58,19 @@ FFmpegDecoder::FFmpegDecoder (boost::shared_ptr<const FilmState> s, boost::share , _format_context (0) , _video_stream (-1) , _audio_stream (-1) + , _subtitle_stream (-1) , _frame (0) , _video_codec_context (0) , _video_codec (0) , _audio_codec_context (0) , _audio_codec (0) + , _subtitle_codec_context (0) + , _subtitle_codec (0) { setup_general (); setup_video (); setup_audio (); + setup_subtitle (); } FFmpegDecoder::~FFmpegDecoder () @@ -76,6 +82,10 @@ FFmpegDecoder::~FFmpegDecoder () if (_video_codec_context) { avcodec_close (_video_codec_context); } + + if (_subtitle_codec_context) { + avcodec_close (_subtitle_codec_context); + } av_free (_frame); avformat_close_input (&_format_context); @@ -101,6 +111,8 @@ FFmpegDecoder::setup_general () _video_stream = i; } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { _audio_stream = i; + } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { + _subtitle_stream = i; } } @@ -156,6 +168,26 @@ FFmpegDecoder::setup_audio () } } +void +FFmpegDecoder::setup_subtitle () +{ + if (_subtitle_stream < 0) { + return; + } + + _subtitle_codec_context = _format_context->streams[_subtitle_stream]->codec; + _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); + + if (_subtitle_codec == 0) { + throw DecodeError ("could not find subtitle decoder"); + } + + if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) { + throw DecodeError ("could not open subtitle decoder"); + } +} + + bool FFmpegDecoder::do_pass () { @@ -210,6 +242,15 @@ FFmpegDecoder::do_pass () assert (_audio_codec_context->channels == _fs->audio_channels); process_audio (_frame->data[0], data_size); } + + } else if (_subtitle_stream >= 0 && _packet.stream_index == _subtitle_stream && _opt->decode_subtitles) { + + int got_subtitle; + AVSubtitle sub; + if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) && got_subtitle) { + process_subtitle (shared_ptr<Subtitle> (new Subtitle (sub))); + avsubtitle_free (&sub); + } } av_free_packet (&_packet); @@ -310,3 +351,8 @@ FFmpegDecoder::sample_aspect_ratio_denominator () const return _video_codec_context->sample_aspect_ratio.den; } +bool +FFmpegDecoder::has_subtitles () const +{ + return (_subtitle_stream != -1); +} diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 4e5445f67..d34c22785 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -44,6 +44,7 @@ class FilmState; class Options; class Image; class Log; +class Subtitle; /** @class FFmpegDecoder * @brief A decoder using FFmpeg to decode content. @@ -63,6 +64,7 @@ public: int audio_sample_rate () const; AVSampleFormat audio_sample_format () const; int64_t audio_channel_layout () const; + bool has_subtitles () const; private: @@ -76,16 +78,22 @@ private: void setup_general (); void setup_video (); void setup_audio (); + void setup_subtitle (); + + void maybe_add_subtitle (); AVFormatContext* _format_context; int _video_stream; int _audio_stream; ///< may be < 0 if there is no audio + int _subtitle_stream; ///< may be < 0 if there is no subtitle AVFrame* _frame; AVCodecContext* _video_codec_context; AVCodec* _video_codec; - AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio - AVCodec* _audio_codec; ///< may be 0 if there is no audio + AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio + AVCodec* _audio_codec; ///< may be 0 if there is no audio + AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle + AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle AVPacket _packet; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index e2b3d4bc3..31af2f1c2 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -27,9 +27,10 @@ #include <unistd.h> #include <boost/filesystem.hpp> #include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> #include "film.h" #include "format.h" -#include "tiff_encoder.h" +#include "imagemagick_encoder.h" #include "job.h" #include "filter.h" #include "transcoder.h" @@ -217,6 +218,7 @@ Film::set_content (string c) _state.audio_channels = d->audio_channels (); _state.audio_sample_rate = d->audio_sample_rate (); _state.audio_sample_format = d->audio_sample_format (); + _state.has_subtitles = d->has_subtitles (); _state.content_digest = md5_digest (s->content_path ()); _state.content = c; @@ -395,7 +397,7 @@ Film::update_thumbs_post_gui () string const l = i->leaf (); #endif - size_t const d = l.find (".tiff"); + size_t const d = l.find (".png"); if (d != string::npos) { _state.thumbs.push_back (atoi (l.substr (0, d).c_str())); } @@ -533,17 +535,20 @@ Film::make_dcp (bool transcode, int freq) o->decode_video_frequency = freq; o->padding = format()->dcp_padding (this); o->ratio = format()->ratio_as_float (this); + o->decode_subtitles = with_subtitles (); + + shared_ptr<Job> r; if (transcode) { if (_state.dcp_ab) { - JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (fs, o, log ()))); + r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (fs, o, log(), shared_ptr<Job> ()))); } else { - JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (fs, o, log ()))); + r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (fs, o, log(), shared_ptr<Job> ()))); } } - JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (fs, o, log ()))); - JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (fs, o, log ()))); + r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (fs, o, log(), r))); + JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (fs, o, log(), r))); } shared_ptr<FilmState> @@ -582,7 +587,7 @@ Film::examine_content () return; } - _examine_content_job.reset (new ExamineContentJob (state_copy (), log ())); + _examine_content_job.reset (new ExamineContentJob (state_copy (), log(), shared_ptr<Job> ())); _examine_content_job->Finished.connect (sigc::mem_fun (*this, &Film::examine_content_post_gui)); JobManager::instance()->add (_examine_content_job); } @@ -631,14 +636,14 @@ Film::set_still_duration (int d) void Film::send_dcp_to_tms () { - shared_ptr<Job> j (new SCPDCPJob (state_copy (), log ())); + shared_ptr<Job> j (new SCPDCPJob (state_copy (), log(), shared_ptr<Job> ())); JobManager::instance()->add (j); } void Film::copy_from_dvd () { - shared_ptr<Job> j (new CopyFromDVDJob (state_copy (), log ())); + shared_ptr<Job> j (new CopyFromDVDJob (state_copy (), log(), shared_ptr<Job> ())); j->Finished.connect (sigc::mem_fun (*this, &Film::copy_from_dvd_post_gui)); JobManager::instance()->add (j); } @@ -658,3 +663,68 @@ Film::encoded_frames () const return N; } + +void +Film::set_with_subtitles (bool w) +{ + _state.with_subtitles = w; + signal_changed (WITH_SUBTITLES); +} + +void +Film::set_subtitle_offset (int o) +{ + _state.subtitle_offset = o; + signal_changed (SUBTITLE_OFFSET); +} + +void +Film::set_subtitle_scale (float s) +{ + _state.subtitle_scale = s; + signal_changed (SUBTITLE_SCALE); +} + +list<pair<Position, string> > +Film::thumb_subtitles (int n) const +{ + string sub_file = _state.thumb_base(n) + ".sub"; + if (!filesystem::exists (sub_file)) { + return list<pair<Position, string> > (); + } + + ifstream f (sub_file.c_str ()); + string line; + + int sub_number; + int sub_x; + list<pair<Position, string> > subs; + + while (getline (f, line)) { + if (line.empty ()) { + continue; + } + + if (line[line.size() - 1] == '\r') { + line = line.substr (0, line.size() - 1); + } + + size_t const s = line.find (' '); + if (s == string::npos) { + continue; + } + + string const k = line.substr (0, s); + int const v = lexical_cast<int> (line.substr(s + 1)); + + if (k == "image") { + sub_number = v; + } else if (k == "x") { + sub_x = v; + } else if (k == "y") { + subs.push_back (make_pair (Position (sub_x, v), String::compose ("%1.sub.%2.png", _state.thumb_base(n), sub_number))); + } + } + + return subs; +} diff --git a/src/lib/film.h b/src/lib/film.h index cd3b1b8a8..c006eae36 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -119,6 +119,18 @@ public: int still_duration () const { return _state.still_duration; } + + bool with_subtitles () const { + return _state.with_subtitles; + } + + int subtitle_offset () const { + return _state.subtitle_offset; + } + + float subtitle_scale () const { + return _state.subtitle_scale; + } void set_filters (std::vector<Filter const *> const &); @@ -144,6 +156,9 @@ public: void set_audio_gain (float); void set_audio_delay (int); void set_still_duration (int); + void set_with_subtitles (bool); + void set_subtitle_offset (int); + void set_subtitle_scale (float); /** @return size, in pixels, of the source (ignoring cropping) */ Size size () const { @@ -174,6 +189,10 @@ public: AVSampleFormat audio_sample_format () const { return _state.audio_sample_format; } + + bool has_subtitles () const { + return _state.has_subtitles; + } std::string j2k_dir () const; @@ -184,6 +203,7 @@ public: int num_thumbs () const; int thumb_frame (int) const; std::string thumb_file (int) const; + std::list<std::pair<Position, std::string> > thumb_subtitles (int) const; void copy_from_dvd_post_gui (); void examine_content (); @@ -218,7 +238,10 @@ public: FRAMES_PER_SECOND, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, - STILL_DURATION + STILL_DURATION, + WITH_SUBTITLES, + SUBTITLE_OFFSET, + SUBTITLE_SCALE }; boost::shared_ptr<FilmState> state_copy () const; diff --git a/src/lib/film_state.cc b/src/lib/film_state.cc index 3d58a4fec..fed506863 100644 --- a/src/lib/film_state.cc +++ b/src/lib/film_state.cc @@ -29,6 +29,7 @@ #include <iomanip> #include <sstream> #include <boost/filesystem.hpp> +#include <boost/date_time.hpp> #include "film_state.h" #include "scaler.h" #include "filter.h" @@ -80,6 +81,9 @@ FilmState::write_metadata (ofstream& f) const f << "audio_gain " << audio_gain << "\n"; f << "audio_delay " << audio_delay << "\n"; f << "still_duration " << still_duration << "\n"; + f << "with_subtitles " << with_subtitles << "\n"; + f << "subtitle_offset " << subtitle_offset << "\n"; + f << "subtitle_scale " << subtitle_scale << "\n"; /* Cached stuff; this is information about our content; we could look it up each time, but that's slow. @@ -94,6 +98,7 @@ FilmState::write_metadata (ofstream& f) const f << "audio_sample_rate " << audio_sample_rate << "\n"; f << "audio_sample_format " << audio_sample_format_to_string (audio_sample_format) << "\n"; f << "content_digest " << content_digest << "\n"; + f << "has_subtitles " << has_subtitles << "\n"; } /** Read state from a key / value pair. @@ -142,6 +147,12 @@ FilmState::read_metadata (string k, string v) audio_delay = atoi (v.c_str ()); } else if (k == "still_duration") { still_duration = atoi (v.c_str ()); + } else if (k == "with_subtitles") { + with_subtitles = (v == "1"); + } else if (k == "subtitle_offset") { + subtitle_offset = atoi (v.c_str ()); + } else if (k == "subtitle_scale") { + subtitle_scale = atof (v.c_str ()); } /* Cached stuff */ @@ -165,6 +176,8 @@ FilmState::read_metadata (string k, string v) audio_sample_format = audio_sample_format_from_string (v); } else if (k == "content_digest") { content_digest = v; + } else if (k == "has_subtitles") { + has_subtitles = (v == "1"); } } @@ -185,9 +198,21 @@ FilmState::thumb_file (int n) const string FilmState::thumb_file_for_frame (int n) const { + return thumb_base_for_frame(n) + ".png"; +} + +string +FilmState::thumb_base (int n) const +{ + return thumb_base_for_frame (thumb_frame (n)); +} + +string +FilmState::thumb_base_for_frame (int n) const +{ stringstream s; s.width (8); - s << setfill('0') << n << ".tiff"; + s << setfill('0') << n; filesystem::path p; p /= dir ("thumbs"); @@ -306,4 +331,64 @@ FilmState::dcp_length () const return length; } - +string +FilmState::dci_name () const +{ + stringstream d; + d << dci_name_prefix << "_"; + + if (dcp_content_type) { + d << dcp_content_type->dci_name() << "_"; + } + + if (format) { + d << format->dci_name() << "_"; + } + + if (!audio_language.empty ()) { + d << audio_language; + if (!subtitle_language.empty ()) { + d << "-" << subtitle_language; + } + d << "_"; + } + + if (!territory.empty ()) { + d << territory; + if (!rating.empty ()) { + d << "-" << rating; + } + d << "_"; + } + + switch (audio_channels) { + case 1: + d << "1_"; + break; + case 2: + d << "2_"; + break; + case 6: + d << "51_"; + break; + } + + d << "2K_"; + + if (!studio.empty ()) { + d << studio << "_"; + } + + gregorian::date today = gregorian::day_clock::local_day (); + d << gregorian::to_iso_extended_string (today) << "_"; + + if (!facility.empty ()) { + d << facility << "_"; + } + + if (!package_type.empty ()) { + d << package_type; + } + + return d.str (); +} diff --git a/src/lib/film_state.h b/src/lib/film_state.h index 16a1b0508..e58d46b0f 100644 --- a/src/lib/film_state.h +++ b/src/lib/film_state.h @@ -62,10 +62,14 @@ public: , audio_gain (0) , audio_delay (0) , still_duration (10) + , with_subtitles (false) + , subtitle_offset (0) + , subtitle_scale (1) , length (0) , audio_channels (0) , audio_sample_rate (0) , audio_sample_format (AV_SAMPLE_FMT_NONE) + , has_subtitles (false) {} std::string file (std::string f) const; @@ -77,6 +81,7 @@ public: bool content_is_dvd () const; std::string thumb_file (int) const; + std::string thumb_base (int) const; int thumb_frame (int) const; int bytes_per_sample () const; @@ -86,8 +91,8 @@ public: void read_metadata (std::string, std::string); Size cropped_size (Size) const; - int dcp_length () const; + std::string dci_name () const; /** Complete path to directory containing the film metadata; must not be relative. @@ -126,6 +131,22 @@ public: int audio_delay; /** Duration to make still-sourced films (in seconds) */ int still_duration; + bool with_subtitles; + /** y offset for placing subtitles, in source pixels; +ve is further down + the frame, -ve is further up. + */ + int subtitle_offset; + float subtitle_scale; + + /* DCI naming stuff */ + std::string dci_name_prefix; + std::string audio_language; + std::string subtitle_language; + std::string territory; + std::string rating; + std::string studio; + std::string facility; + std::string package_type; /* Data which is cached to speed things up */ @@ -143,9 +164,12 @@ public: AVSampleFormat audio_sample_format; /** MD5 digest of our content file */ std::string content_digest; + /** true if the source has subtitles */ + bool has_subtitles; private: std::string thumb_file_for_frame (int) const; + std::string thumb_base_for_frame (int) const; }; #endif diff --git a/src/lib/filter.cc b/src/lib/filter.cc index ab5a6316f..446cc111d 100644 --- a/src/lib/filter.cc +++ b/src/lib/filter.cc @@ -72,6 +72,7 @@ Filter::setup_filters () _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5")); _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", "")); _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", "")); + _filters.push_back (new Filter ("yadif", "Yet Another Deinterlacing Filter", "yadif", "")); _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn")); _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq")); _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", "")); diff --git a/src/lib/format.cc b/src/lib/format.cc index aaf5211f9..2eb4990da 100644 --- a/src/lib/format.cc +++ b/src/lib/format.cc @@ -63,18 +63,18 @@ Format::as_metadata () const void Format::setup_formats () { - _formats.push_back (new FixedFormat (119, Size (1285, 1080), "119", "1.19")); - _formats.push_back (new FixedFormat (133, Size (1436, 1080), "133", "1.33")); - _formats.push_back (new FixedFormat (138, Size (1485, 1080), "138", "1.375")); - _formats.push_back (new FixedFormat (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat")); - _formats.push_back (new FixedFormat (137, Size (1480, 1080), "137", "Academy")); - _formats.push_back (new FixedFormat (166, Size (1793, 1080), "166", "1.66")); - _formats.push_back (new FixedFormat (166, Size (1998, 1080), "166-in-flat", "1.66 within Flat")); - _formats.push_back (new FixedFormat (178, Size (1998, 1080), "178-in-flat", "16:9 within Flat")); - _formats.push_back (new FixedFormat (185, Size (1998, 1080), "185", "Flat")); - _formats.push_back (new FixedFormat (239, Size (2048, 858), "239", "Scope")); - _formats.push_back (new VariableFormat (Size (1998, 1080), "var-185", "Flat")); - _formats.push_back (new VariableFormat (Size (2048, 858), "var-239", "Scope")); + _formats.push_back (new FixedFormat (119, Size (1285, 1080), "119", "1.19", "F")); + _formats.push_back (new FixedFormat (133, Size (1436, 1080), "133", "1.33", "F")); + _formats.push_back (new FixedFormat (138, Size (1485, 1080), "138", "1.375", "F")); + _formats.push_back (new FixedFormat (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat", "F")); + _formats.push_back (new FixedFormat (137, Size (1480, 1080), "137", "Academy", "F")); + _formats.push_back (new FixedFormat (166, Size (1793, 1080), "166", "1.66", "F")); + _formats.push_back (new FixedFormat (166, Size (1998, 1080), "166-in-flat", "1.66 within Flat", "F")); + _formats.push_back (new FixedFormat (178, Size (1998, 1080), "178-in-flat", "16:9 within Flat", "F")); + _formats.push_back (new FixedFormat (185, Size (1998, 1080), "185", "Flat", "F")); + _formats.push_back (new FixedFormat (239, Size (2048, 858), "239", "Scope", "S")); + _formats.push_back (new VariableFormat (Size (1998, 1080), "var-185", "Flat", "F")); + _formats.push_back (new VariableFormat (Size (2048, 858), "var-239", "Scope", "S")); } /** @param n Nickname. @@ -135,8 +135,8 @@ Format::all () * @param id ID (e.g. 185) * @param n Nick name (e.g. Flat) */ -FixedFormat::FixedFormat (int r, Size dcp, string id, string n) - : Format (dcp, id, n) +FixedFormat::FixedFormat (int r, Size dcp, string id, string n, string d) + : Format (dcp, id, n, d) , _ratio (r) { @@ -155,8 +155,8 @@ Format::dcp_padding (Film const * f) const return p; } -VariableFormat::VariableFormat (Size dcp, string id, string n) - : Format (dcp, id, n) +VariableFormat::VariableFormat (Size dcp, string id, string n, string d) + : Format (dcp, id, n, d) { } diff --git a/src/lib/format.h b/src/lib/format.h index fd6cdbece..35dd4fb85 100644 --- a/src/lib/format.h +++ b/src/lib/format.h @@ -31,10 +31,11 @@ class Film; class Format { public: - Format (Size dcp, std::string id, std::string n) + Format (Size dcp, std::string id, std::string n, std::string d) : _dcp_size (dcp) , _id (id) , _nickname (n) + , _dci_name (d) {} /** @return the aspect ratio multiplied by 100 @@ -67,6 +68,10 @@ public: return _nickname; } + std::string dci_name () const { + return _dci_name; + } + std::string as_metadata () const; static Format const * from_nickname (std::string n); @@ -85,6 +90,7 @@ protected: std::string _id; /** nickname (e.g. Flat, Scope) */ std::string _nickname; + std::string _dci_name; private: /** all available formats */ @@ -98,7 +104,7 @@ private: class FixedFormat : public Format { public: - FixedFormat (int, Size, std::string, std::string); + FixedFormat (int, Size, std::string, std::string, std::string); int ratio_as_integer (Film const *) const { return _ratio; @@ -119,7 +125,7 @@ private: class VariableFormat : public Format { public: - VariableFormat (Size, std::string, std::string); + VariableFormat (Size, std::string, std::string, std::string); int ratio_as_integer (Film const * f) const; float ratio_as_float (Film const * f) const; diff --git a/src/lib/image.cc b/src/lib/image.cc index 2df7636af..f5aef8444 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -26,6 +26,7 @@ #include <iostream> #include <sys/time.h> #include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> #include <openjpeg.h> extern "C" { #include <libavcodec/avcodec.h> @@ -57,6 +58,7 @@ Image::lines (int n) const } break; case PIX_FMT_RGB24: + case PIX_FMT_RGBA: return size().height; default: assert (false); @@ -73,6 +75,7 @@ Image::components () const case PIX_FMT_YUV420P: return 3; case PIX_FMT_RGB24: + case PIX_FMT_RGBA: return 1; default: assert (false); @@ -81,11 +84,36 @@ Image::components () const return 0; } +shared_ptr<Image> +Image::scale (Size out_size, Scaler const * scaler) const +{ + assert (scaler); + + shared_ptr<Image> scaled (new AlignedImage (pixel_format(), out_size)); + + struct SwsContext* scale_context = sws_getContext ( + size().width, size().height, pixel_format(), + out_size.width, out_size.height, pixel_format(), + scaler->ffmpeg_id (), 0, 0, 0 + ); + + sws_scale ( + scale_context, + data(), stride(), + 0, size().height, + scaled->data(), scaled->stride() + ); + + sws_freeContext (scale_context); + + return scaled; +} + /** Scale this image to a given size and convert it to RGB. * @param out_size Output image size in pixels. * @param scaler Scaler to use. */ -shared_ptr<RGBFrameImage> +shared_ptr<Image> Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler) const { assert (scaler); @@ -93,7 +121,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal Size content_size = out_size; content_size.width -= (padding * 2); - shared_ptr<RGBFrameImage> rgb (new RGBFrameImage (content_size)); + shared_ptr<Image> rgb (new AlignedImage (PIX_FMT_RGB24, content_size)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), @@ -104,9 +132,9 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal /* Scale and convert to RGB from whatever its currently in (which may be RGB) */ sws_scale ( scale_context, - data(), line_size(), + data(), stride(), 0, size().height, - rgb->data (), rgb->line_size () + rgb->data(), rgb->stride() ); /* Put the image in the right place in a black frame if are padding; this is @@ -114,7 +142,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal scheme of things. */ if (padding > 0) { - shared_ptr<RGBFrameImage> padded_rgb (new RGBFrameImage (out_size)); + shared_ptr<Image> padded_rgb (new AlignedImage (PIX_FMT_RGB24, out_size)); padded_rgb->make_black (); /* XXX: we are cheating a bit here; we know the frame is RGB so we can @@ -124,8 +152,8 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal uint8_t* q = rgb->data()[0]; for (int j = 0; j < rgb->lines(0); ++j) { memcpy (p, q, rgb->line_size()[0]); - p += padded_rgb->line_size()[0]; - q += rgb->line_size()[0]; + p += padded_rgb->stride()[0]; + q += rgb->stride()[0]; } rgb = padded_rgb; @@ -140,17 +168,17 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal * @param pp Flags for the required set of post processes. * @return Post-processed image. */ -shared_ptr<PostProcessImage> +shared_ptr<Image> Image::post_process (string pp) const { - shared_ptr<PostProcessImage> out (new PostProcessImage (PIX_FMT_YUV420P, size ())); + shared_ptr<Image> out (new AlignedImage (PIX_FMT_YUV420P, size ())); pp_mode* mode = pp_get_mode_by_name_and_quality (pp.c_str (), PP_QUALITY_MAX); pp_context* context = pp_get_context (size().width, size().height, PP_FORMAT_420 | PP_CPU_CAPS_MMX2); pp_postprocess ( - (const uint8_t **) data(), line_size(), - out->data(), out->line_size(), + (const uint8_t **) data(), stride(), + out->data(), out->stride(), size().width, size().height, 0, 0, mode, context, 0 ); @@ -166,13 +194,13 @@ Image::make_black () { switch (_pixel_format) { case PIX_FMT_YUV420P: - memset (data()[0], 0, lines(0) * line_size()[0]); - memset (data()[1], 0x80, lines(1) * line_size()[1]); - memset (data()[2], 0x80, lines(2) * line_size()[2]); + memset (data()[0], 0, lines(0) * stride()[0]); + memset (data()[1], 0x80, lines(1) * stride()[1]); + memset (data()[2], 0x80, lines(2) * stride()[2]); break; case PIX_FMT_RGB24: - memset (data()[0], 0, lines(0) * line_size()[0]); + memset (data()[0], 0, lines(0) * stride()[0]); break; default: @@ -180,22 +208,80 @@ Image::make_black () } } +void +Image::alpha_blend (shared_ptr<Image> other, Position position) +{ + /* Only implemented for RGBA onto RGB24 so far */ + assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA); + + int start_tx = position.x; + int start_ox = 0; + + if (start_tx < 0) { + start_ox = -start_tx; + start_tx = 0; + } + + int start_ty = position.y; + int start_oy = 0; + + if (start_ty < 0) { + start_oy = -start_ty; + start_ty = 0; + } + + for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { + uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3; + uint8_t* op = other->data()[0] + oy * other->stride()[0]; + for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { + float const alpha = float (op[3]) / 255; + tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha; + tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha; + tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha; + tp += 3; + op += 4; + } + } +} + /** Construct a SimpleImage of a given size and format, allocating memory * as required. * * @param p Pixel format. * @param s Size in pixels. */ -SimpleImage::SimpleImage (PixelFormat p, Size s) +SimpleImage::SimpleImage (PixelFormat p, Size s, function<int (int)> rounder) : Image (p) , _size (s) { - _data = (uint8_t **) av_malloc (components() * sizeof (uint8_t *)); - _line_size = (int *) av_malloc (components() * sizeof (int)); + _data = (uint8_t **) av_malloc (4 * sizeof (uint8_t *)); + _data[0] = _data[1] = _data[2] = _data[3] = 0; + _line_size = (int *) av_malloc (4); + _line_size[0] = _line_size[1] = _line_size[2] = _line_size[3] = 0; + + _stride = (int *) av_malloc (4); + _stride[0] = _stride[1] = _stride[2] = _stride[3] = 0; + + switch (p) { + case PIX_FMT_RGB24: + _line_size[0] = s.width * 3; + break; + case PIX_FMT_RGBA: + _line_size[0] = s.width * 4; + break; + case PIX_FMT_YUV420P: + _line_size[0] = s.width; + _line_size[1] = s.width / 2; + _line_size[2] = s.width / 2; + break; + default: + assert (false); + } + for (int i = 0; i < components(); ++i) { - _data[i] = 0; - _line_size[i] = 0; + _stride[i] = rounder (_line_size[i]); + _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i)); } } @@ -208,17 +294,7 @@ SimpleImage::~SimpleImage () av_free (_data); av_free (_line_size); -} - -/** Set the size in bytes of each horizontal line of a given component. - * @param i Component index. - * @param s Size of line in bytes. - */ -void -SimpleImage::set_line_size (int i, int s) -{ - _line_size[i] = s; - _data[i] = (uint8_t *) av_malloc (s * lines (i)); + av_free (_stride); } uint8_t ** @@ -233,12 +309,49 @@ SimpleImage::line_size () const return _line_size; } +int * +SimpleImage::stride () const +{ + return _stride; +} + Size SimpleImage::size () const { return _size; } +AlignedImage::AlignedImage (PixelFormat f, Size s) + : SimpleImage (f, s, boost::bind (round_up, _1, 32)) +{ + +} + +CompactImage::CompactImage (PixelFormat f, Size s) + : SimpleImage (f, s, boost::bind (round_up, _1, 1)) +{ + +} + +CompactImage::CompactImage (shared_ptr<Image> im) + : SimpleImage (im->pixel_format(), im->size(), boost::bind (round_up, _1, 1)) +{ + assert (components() == im->components()); + + for (int c = 0; c < components(); ++c) { + + assert (line_size()[c] == im->line_size()[c]); + + uint8_t* t = data()[c]; + uint8_t* o = im->data()[c]; + + for (int y = 0; y < lines(c); ++y) { + memcpy (t, o, line_size()[c]); + t += stride()[c]; + o += im->stride()[c]; + } + } +} FilterBufferImage::FilterBufferImage (PixelFormat p, AVFilterBufferRef* b) : Image (p) @@ -264,6 +377,13 @@ FilterBufferImage::line_size () const return _buffer->linesize; } +int * +FilterBufferImage::stride () const +{ + /* XXX? */ + return _buffer->linesize; +} + Size FilterBufferImage::size () const { @@ -308,49 +428,15 @@ RGBFrameImage::line_size () const return _frame->linesize; } -Size -RGBFrameImage::size () const -{ - return _size; -} - -PostProcessImage::PostProcessImage (PixelFormat p, Size s) - : Image (p) - , _size (s) -{ - _data = new uint8_t*[4]; - _line_size = new int[4]; - - for (int i = 0; i < 4; ++i) { - _data[i] = (uint8_t *) av_malloc (s.width * s.height); - _line_size[i] = s.width; - } -} - -PostProcessImage::~PostProcessImage () -{ - for (int i = 0; i < 4; ++i) { - av_free (_data[i]); - } - - delete[] _data; - delete[] _line_size; -} - -uint8_t ** -PostProcessImage::data () const -{ - return _data; -} - int * -PostProcessImage::line_size () const +RGBFrameImage::stride () const { - return _line_size; + /* XXX? */ + return line_size (); } Size -PostProcessImage::size () const +RGBFrameImage::size () const { return _size; } diff --git a/src/lib/image.h b/src/lib/image.h index 0161d2b01..30c8519e7 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -26,6 +26,7 @@ #include <string> #include <boost/shared_ptr.hpp> +#include <boost/function.hpp> extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> @@ -34,7 +35,7 @@ extern "C" { class Scaler; class RGBFrameImage; -class PostProcessImage; +class SimpleImage; /** @class Image * @brief Parent class for wrappers of some image, in some format, that @@ -57,16 +58,21 @@ public: /** @return Array of pointers to arrays of the component data */ virtual uint8_t ** data () const = 0; - /** @return Array of sizes of each line, in pixels */ + /** @return Array of sizes of the data in each line, in bytes (without any alignment padding bytes) */ virtual int * line_size () const = 0; + /** @return Array of strides for each line (including any alignment padding bytes) */ + virtual int * stride () const = 0; + /** @return Size of the image, in pixels */ virtual Size size () const = 0; int components () const; int lines (int) const; - boost::shared_ptr<RGBFrameImage> scale_and_convert_to_rgb (Size, int, Scaler const *) const; - boost::shared_ptr<PostProcessImage> post_process (std::string) const; + boost::shared_ptr<Image> scale_and_convert_to_rgb (Size, int, Scaler const *) const; + boost::shared_ptr<Image> scale (Size, Scaler const *) const; + boost::shared_ptr<Image> post_process (std::string) const; + void alpha_blend (boost::shared_ptr<Image> image, Position pos); void make_black (); @@ -89,6 +95,7 @@ public: uint8_t ** data () const; int * line_size () const; + int * stride () const; Size size () const; private: @@ -101,19 +108,33 @@ private: class SimpleImage : public Image { public: - SimpleImage (PixelFormat, Size); + SimpleImage (PixelFormat, Size, boost::function<int (int)> rounder); ~SimpleImage (); uint8_t ** data () const; int * line_size () const; + int * stride () const; Size size () const; - void set_line_size (int, int); - private: + Size _size; ///< size in pixels uint8_t** _data; ///< array of pointers to components - int* _line_size; ///< array of widths of each line, in bytes + int* _line_size; ///< array of sizes of the data in each line, in pixels (without any alignment padding bytes) + int* _stride; ///< array of strides for each line (including any alignment padding bytes) +}; + +class AlignedImage : public SimpleImage +{ +public: + AlignedImage (PixelFormat, Size); +}; + +class CompactImage : public SimpleImage +{ +public: + CompactImage (PixelFormat, Size); + CompactImage (boost::shared_ptr<Image>); }; /** @class RGBFrameImage @@ -127,6 +148,7 @@ public: uint8_t ** data () const; int * line_size () const; + int * stride () const; Size size () const; AVFrame * frame () const { return _frame; @@ -138,23 +160,4 @@ private: uint8_t* _data; }; -/** @class PostProcessImage - * @brief An image that is the result of an FFmpeg post-processing run. - */ -class PostProcessImage : public Image -{ -public: - PostProcessImage (PixelFormat, Size); - ~PostProcessImage (); - - uint8_t ** data () const; - int * line_size () const; - Size size () const; - -private: - Size _size; - uint8_t** _data; - int* _line_size; -}; - #endif diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index 7cee01ec5..32c433d09 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -1,3 +1,22 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + #include <iostream> #include <Magick++/Image.h> #include "imagemagick_decoder.h" @@ -5,6 +24,7 @@ #include "image.h" using namespace std; +using namespace boost; ImageMagickDecoder::ImageMagickDecoder ( boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Job* j, Log* l, bool minimal, bool ignore_length) diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index aca91ef55..809f3aecd 100644 --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@ -1,3 +1,22 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + #include "decoder.h" namespace Magick { @@ -35,6 +54,10 @@ public: return 0; } + bool has_subtitles () const { + return false; + } + static float static_frames_per_second () { return 24; } diff --git a/src/lib/imagemagick_encoder.cc b/src/lib/imagemagick_encoder.cc new file mode 100644 index 000000000..9bd8162f8 --- /dev/null +++ b/src/lib/imagemagick_encoder.cc @@ -0,0 +1,101 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/imagemagick_encoder.cc + * @brief An encoder that writes image files using ImageMagick (and does nothing with audio). + */ + +#include <stdexcept> +#include <vector> +#include <sstream> +#include <iomanip> +#include <iostream> +#include <fstream> +#include <boost/filesystem.hpp> +#include <Magick++/Image.h> +#include "imagemagick_encoder.h" +#include "film.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "image.h" +#include "subtitle.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState of the film that we are encoding. + * @param o Options. + * @param l Log. + */ +ImageMagickEncoder::ImageMagickEncoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) + : Encoder (s, o, l) +{ + +} + +void +ImageMagickEncoder::process_video (shared_ptr<Image> image, int frame, shared_ptr<Subtitle> sub) +{ + shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _fs->scaler); + shared_ptr<Image> compact (new CompactImage (scaled)); + + string tmp_file = _opt->frame_out_path (frame, true); + Magick::Image thumb (compact->size().width, compact->size().height, "RGB", MagickCore::CharPixel, compact->data()[0]); + thumb.magick ("PNG"); + thumb.write (tmp_file); + filesystem::rename (tmp_file, _opt->frame_out_path (frame, false)); + + if (sub) { + float const x_scale = float (_opt->out_size.width) / _fs->size.width; + float const y_scale = float (_opt->out_size.height) / _fs->size.height; + + string tmp_metadata_file = _opt->frame_out_path (frame, false, ".sub"); + ofstream metadata (tmp_metadata_file.c_str ()); + + list<shared_ptr<SubtitleImage> > images = sub->images (); + int n = 0; + for (list<shared_ptr<SubtitleImage> >::iterator i = images.begin(); i != images.end(); ++i) { + stringstream ext; + ext << ".sub." << n << ".png"; + + Size new_size = (*i)->image()->size (); + new_size.width *= x_scale; + new_size.height *= y_scale; + shared_ptr<Image> scaled = (*i)->image()->scale (new_size, _fs->scaler); + shared_ptr<Image> compact (new CompactImage (scaled)); + + string tmp_sub_file = _opt->frame_out_path (frame, true, ext.str ()); + Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]); + sub_thumb.magick ("PNG"); + sub_thumb.write (tmp_sub_file); + filesystem::rename (tmp_sub_file, _opt->frame_out_path (frame, false, ext.str ())); + + metadata << "image " << n << "\n" + << "x " << (*i)->position().x << "\n" + << "y " << (*i)->position().y << "\n"; + + metadata.close (); + filesystem::rename (tmp_metadata_file, _opt->frame_out_path (frame, false, ".sub")); + } + + } + + frame_done (frame); +} diff --git a/src/lib/tiff_encoder.h b/src/lib/imagemagick_encoder.h index ef1ce25d2..ce6ca3e8f 100644 --- a/src/lib/tiff_encoder.h +++ b/src/lib/imagemagick_encoder.h @@ -17,8 +17,8 @@ */ -/** @file src/tiff_encoder.h - * @brief An encoder that writes TIFF files (and does nothing with audio). +/** @file src/imagemagick_encoder.h + * @brief An encoder that writes image files using ImageMagick (and does nothing with audio). */ #include <string> @@ -28,16 +28,16 @@ class FilmState; class Log; -/** @class TIFFEncoder - * @brief An encoder that writes TIFF files (and does nothing with audio). +/** @class ImageMagickEncoder + * @brief An encoder that writes image files using ImageMagick files (and does nothing with audio). */ -class TIFFEncoder : public Encoder +class ImageMagickEncoder : public Encoder { public: - TIFFEncoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); + ImageMagickEncoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); void process_begin (int64_t audio_channel_layout, AVSampleFormat audio_sample_format) {} - void process_video (boost::shared_ptr<Image>, int); + void process_video (boost::shared_ptr<Image>, int, boost::shared_ptr<Subtitle>); void process_audio (uint8_t *, int) {} void process_end () {} }; diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc index d218d08fe..2b8aca649 100644 --- a/src/lib/j2k_still_encoder.cc +++ b/src/lib/j2k_still_encoder.cc @@ -48,11 +48,11 @@ J2KStillEncoder::J2KStillEncoder (shared_ptr<const FilmState> s, shared_ptr<cons } void -J2KStillEncoder::process_video (shared_ptr<Image> yuv, int frame) +J2KStillEncoder::process_video (shared_ptr<Image> yuv, int frame, shared_ptr<Subtitle> sub) { pair<string, string> const s = Filter::ffmpeg_strings (_fs->filters); DCPVideoFrame* f = new DCPVideoFrame ( - yuv, _opt->out_size, _opt->padding, _fs->scaler, 0, _fs->frames_per_second, s.second, + yuv, sub, _opt->out_size, _opt->padding, _fs->subtitle_offset, _fs->subtitle_scale, _fs->scaler, 0, _fs->frames_per_second, s.second, Config::instance()->colour_lut_index(), Config::instance()->j2k_bandwidth(), _log ); diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h index 7a03e1195..c48b9e69c 100644 --- a/src/lib/j2k_still_encoder.h +++ b/src/lib/j2k_still_encoder.h @@ -37,7 +37,7 @@ public: J2KStillEncoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *); void process_begin (int64_t audio_channel_layout, AVSampleFormat audio_sample_format) {} - void process_video (boost::shared_ptr<Image>, int); + void process_video (boost::shared_ptr<Image>, int, boost::shared_ptr<Subtitle>); void process_audio (uint8_t *, int) {} void process_end () {} }; diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc index a1f70a08a..e2a3a5ed7 100644 --- a/src/lib/j2k_wav_encoder.cc +++ b/src/lib/j2k_wav_encoder.cc @@ -105,7 +105,7 @@ J2KWAVEncoder::close_sound_files () } void -J2KWAVEncoder::process_video (shared_ptr<Image> yuv, int frame) +J2KWAVEncoder::process_video (shared_ptr<Image> yuv, int frame, shared_ptr<Subtitle> sub) { boost::mutex::scoped_lock lock (_worker_mutex); @@ -126,7 +126,8 @@ J2KWAVEncoder::process_video (shared_ptr<Image> yuv, int frame) TIMING ("adding to queue of %1", _queue.size ()); _queue.push_back (boost::shared_ptr<DCPVideoFrame> ( new DCPVideoFrame ( - yuv, _opt->out_size, _opt->padding, _fs->scaler, frame, _fs->frames_per_second, s.second, + yuv, sub, _opt->out_size, _opt->padding, _fs->subtitle_offset, _fs->subtitle_scale, + _fs->scaler, frame, _fs->frames_per_second, s.second, Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (), _log ) diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h index e11358c2c..87068ad3d 100644 --- a/src/lib/j2k_wav_encoder.h +++ b/src/lib/j2k_wav_encoder.h @@ -38,6 +38,7 @@ class ServerDescription; class DCPVideoFrame; class Image; class Log; +class Subtitle; /** @class J2KWAVEncoder * @brief An encoder which writes JPEG2000 and WAV files. @@ -49,7 +50,7 @@ public: ~J2KWAVEncoder (); void process_begin (int64_t audio_channel_layout, AVSampleFormat audio_sample_format); - void process_video (boost::shared_ptr<Image>, int); + void process_video (boost::shared_ptr<Image>, int, boost::shared_ptr<Subtitle>); void process_audio (uint8_t *, int); void process_end (); diff --git a/src/lib/job.cc b/src/lib/job.cc index 39ce4173a..d3871bf72 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -34,10 +34,11 @@ using namespace boost; * @param o Options. * @param l A log that we can write to. */ -Job::Job (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) +Job::Job (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, shared_ptr<Job> req) : _fs (s) , _opt (o) , _log (l) + , _required (req) , _state (NEW) , _start_time (0) , _progress_unknown (false) @@ -80,6 +81,13 @@ Job::run_wrapper () } } +bool +Job::is_new () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _state == NEW; +} + /** @return true if the job is running */ bool Job::running () const diff --git a/src/lib/job.h b/src/lib/job.h index 802bf468d..f50ed0784 100644 --- a/src/lib/job.h +++ b/src/lib/job.h @@ -39,7 +39,7 @@ class Options; class Job : public boost::enable_shared_from_this<Job> { public: - Job (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); + Job (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l, boost::shared_ptr<Job> req); /** @return user-readable name of this job */ virtual std::string name () const = 0; @@ -48,6 +48,7 @@ public: void start (); + bool is_new () const; bool running () const; bool finished () const; bool finished_ok () const; @@ -66,6 +67,10 @@ public: void emit_finished (); + boost::shared_ptr<Job> required () const { + return _required; + } + /** Emitted from the GUI thread */ sigc::signal0<void> Finished; @@ -95,6 +100,8 @@ private: void run_wrapper (); + boost::shared_ptr<Job> _required; + /** mutex for _state and _error */ mutable boost::mutex _state_mutex; /** current state of the job */ diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc index 76fcc6c5d..2db91a177 100644 --- a/src/lib/job_manager.cc +++ b/src/lib/job_manager.cc @@ -37,11 +37,12 @@ JobManager::JobManager () boost::thread (boost::bind (&JobManager::scheduler, this)); } -void +shared_ptr<Job> JobManager::add (shared_ptr<Job> j) { boost::mutex::scoped_lock lm (_mutex); _jobs.push_back (j); + return j; } void @@ -93,19 +94,21 @@ JobManager::scheduler () while (1) { { boost::mutex::scoped_lock lm (_mutex); - int running = 0; - shared_ptr<Job> first_new; for (list<shared_ptr<Job> >::iterator i = _jobs.begin(); i != _jobs.end(); ++i) { if ((*i)->running ()) { - ++running; - } else if (!(*i)->finished () && first_new == 0) { - first_new = *i; - } - - if (running == 0 && first_new) { - first_new->start (); + /* Something is already happening */ break; } + + if ((*i)->is_new()) { + shared_ptr<Job> r = (*i)->required (); + if (!r || r->finished_ok ()) { + (*i)->start (); + + /* Only start one job at once */ + break; + } + } } } diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h index 8b79fd67d..4b70738f0 100644 --- a/src/lib/job_manager.h +++ b/src/lib/job_manager.h @@ -28,16 +28,12 @@ class Job; /** @class JobManager * @brief A simple scheduler for jobs. - * - * JobManager simply keeps a list of pending jobs, and assumes that all the jobs - * are sufficiently CPU intensive that there is no point running them in parallel; - * so jobs are just run one after the other. */ class JobManager { public: - void add (boost::shared_ptr<Job>); + boost::shared_ptr<Job> add (boost::shared_ptr<Job>); void add_after (boost::shared_ptr<Job> after, boost::shared_ptr<Job> j); std::list<boost::shared_ptr<Job> > get () const; bool work_to_do () const; diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc index ae4bb4fbe..b42a38429 100644 --- a/src/lib/make_dcp_job.cc +++ b/src/lib/make_dcp_job.cc @@ -43,8 +43,8 @@ using namespace boost; * @param o Options. * @param l Log. */ -MakeDCPJob::MakeDCPJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) - : Job (s, o, l) +MakeDCPJob::MakeDCPJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, shared_ptr<Job> req) + : Job (s, o, l, req) { } diff --git a/src/lib/make_dcp_job.h b/src/lib/make_dcp_job.h index 677bed424..c350a819c 100644 --- a/src/lib/make_dcp_job.h +++ b/src/lib/make_dcp_job.h @@ -29,7 +29,7 @@ class MakeDCPJob : public Job { public: - MakeDCPJob (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *); + MakeDCPJob (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *, boost::shared_ptr<Job> req); std::string name () const; void run (); diff --git a/src/lib/options.h b/src/lib/options.h index 39068c24f..86db35210 100644 --- a/src/lib/options.h +++ b/src/lib/options.h @@ -42,6 +42,7 @@ public: , black_after (0) , decode_video_frequency (0) , decode_audio (true) + , decode_subtitles (false) , _frame_out_path (f) , _frame_out_extension (e) , _multichannel_audio_out_path (m) @@ -56,11 +57,15 @@ public: * @param t true to return a temporary file path, otherwise a permanent one. * @return The path to write this video frame to. */ - std::string frame_out_path (int f, bool t) const { + std::string frame_out_path (int f, bool t, std::string e = "") const { + if (e.empty ()) { + e = _frame_out_extension; + } + std::stringstream s; s << _frame_out_path << "/"; s.width (8); - s << std::setfill('0') << f << _frame_out_extension; + s << std::setfill('0') << f << e; if (t) { s << ".tmp"; @@ -95,6 +100,7 @@ public: int black_after; ///< first frame for which to output a black frame, rather than the actual video content, or 0 for none int decode_video_frequency; ///< skip frames so that this many are decoded in all (or 0) (for generating thumbnails) bool decode_audio; ///< true to decode audio, otherwise false + bool decode_subtitles; private: /** Path of the directory to write video frames to */ diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc index dac4a602c..90122cea7 100644 --- a/src/lib/scp_dcp_job.cc +++ b/src/lib/scp_dcp_job.cc @@ -91,8 +91,8 @@ public: }; -SCPDCPJob::SCPDCPJob (shared_ptr<const FilmState> s, Log* l) - : Job (s, shared_ptr<const Options> (), l) +SCPDCPJob::SCPDCPJob (shared_ptr<const FilmState> s, Log* l, shared_ptr<Job> req) + : Job (s, shared_ptr<const Options> (), l, req) , _status ("Waiting") { diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h index 1c795be47..b457fdf5b 100644 --- a/src/lib/scp_dcp_job.h +++ b/src/lib/scp_dcp_job.h @@ -26,7 +26,7 @@ class SCPDCPJob : public Job { public: - SCPDCPJob (boost::shared_ptr<const FilmState>, Log *); + SCPDCPJob (boost::shared_ptr<const FilmState>, Log *, boost::shared_ptr<Job> req); std::string name () const; void run (); diff --git a/src/lib/server.cc b/src/lib/server.cc index 28236e3e0..b5eda2eb8 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -88,6 +88,8 @@ Server::process (shared_ptr<Socket> socket) int pixel_format_int; Size out_size; int padding; + int subtitle_offset; + int subtitle_scale; string scaler_id; int frame; float frames_per_second; @@ -99,6 +101,8 @@ Server::process (shared_ptr<Socket> socket) >> pixel_format_int >> out_size.width >> out_size.height >> padding + >> subtitle_offset + >> subtitle_scale >> scaler_id >> frame >> frames_per_second @@ -112,19 +116,18 @@ Server::process (shared_ptr<Socket> socket) post_process = ""; } - shared_ptr<SimpleImage> image (new SimpleImage (pixel_format, in_size)); + shared_ptr<Image> image (new AlignedImage (pixel_format, in_size)); for (int i = 0; i < image->components(); ++i) { - int line_size; - s >> line_size; - image->set_line_size (i, line_size); - } - - for (int i = 0; i < image->components(); ++i) { - socket->read_definite_and_consume (image->data()[i], image->line_size()[i] * image->lines(i), 30); + socket->read_definite_and_consume (image->data()[i], image->stride()[i] * image->lines(i), 30); } + + /* XXX: subtitle */ + DCPVideoFrame dcp_video_frame ( + image, shared_ptr<Subtitle> (), out_size, padding, subtitle_offset, subtitle_scale, + scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log + ); - DCPVideoFrame dcp_video_frame (image, out_size, padding, scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log); shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally (); encoded->send (socket); diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc new file mode 100644 index 000000000..0eb40b14e --- /dev/null +++ b/src/lib/subtitle.cc @@ -0,0 +1,113 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "subtitle.h" +#include "image.h" +#include "exceptions.h" +#include "film_state.h" + +using namespace std; +using namespace boost; + +Subtitle::Subtitle (AVSubtitle const & sub) +{ + /* subtitle PTS in seconds */ + float const packet_time = (sub.pts / AV_TIME_BASE) + float (sub.pts % AV_TIME_BASE) / 1e6; + + /* hence start time for this sub */ + _from = packet_time + (double (sub.start_display_time) / 1e3); + _to = packet_time + (double (sub.end_display_time) / 1e3); + + for (unsigned int i = 0; i < sub.num_rects; ++i) { + _images.push_back (shared_ptr<SubtitleImage> (new SubtitleImage (sub.rects[i]))); + } +} + +/** @param t Time in seconds from the start of the film */ +bool +Subtitle::displayed_at (double t) +{ + return t >= _from && t <= _to; +} + +SubtitleImage::SubtitleImage (AVSubtitleRect const * rect) + : _position (rect->x, rect->y) + , _image (new AlignedImage (PIX_FMT_RGBA, Size (rect->w, rect->h))) +{ + if (rect->type != SUBTITLE_BITMAP) { + throw DecodeError ("non-bitmap subtitles not yet supported"); + } + + /* Start of the first line in the subtitle */ + uint8_t* sub_p = rect->pict.data[0]; + /* sub_p looks up into a RGB palette which is here */ + uint32_t const * palette = (uint32_t *) rect->pict.data[1]; + /* Start of the output data */ + uint32_t* out_p = (uint32_t *) _image->data()[0]; + + for (int y = 0; y < rect->h; ++y) { + uint8_t* sub_line_p = sub_p; + uint32_t* out_line_p = out_p; + for (int x = 0; x < rect->w; ++x) { + *out_line_p++ = palette[*sub_line_p++]; + } + sub_p += rect->pict.linesize[0]; + out_p += _image->stride()[0] / sizeof (uint32_t); + } +} + +Rectangle +transformed_subtitle_area ( + float target_x_scale, float target_y_scale, + Rectangle sub_area, int subtitle_offset, float subtitle_scale + ) +{ + Rectangle tx; + + sub_area.y += subtitle_offset; + + /* We will scale the subtitle by the same amount as the video frame, and also by the additional + subtitle_scale + */ + tx.w = sub_area.w * target_x_scale * subtitle_scale; + tx.h = sub_area.h * target_y_scale * 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 target_x_scale and target_y_scale; this will be + * sub_area.x * target_x_scale and sub_area.y * target_y_scale. + * + * 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. + */ + + tx.x = target_x_scale * (sub_area.x + (sub_area.w * (1 - subtitle_scale) / 2)); + tx.y = target_y_scale * (sub_area.y + (sub_area.h * (1 - subtitle_scale) / 2)); + + return tx; +} + +Rectangle +SubtitleImage::area () const +{ + return Rectangle (_position.x, _position.y, _image->size().width, _image->size().height); +} diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h new file mode 100644 index 000000000..6fd0d8772 --- /dev/null +++ b/src/lib/subtitle.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <list> +#include <boost/shared_ptr.hpp> +#include "util.h" + +struct AVSubtitle; +class SubtitleImage; +class Image; +class FilmState; + +class Subtitle +{ +public: + Subtitle (AVSubtitle const &); + + bool displayed_at (double t); + + std::list<boost::shared_ptr<SubtitleImage> > images () const { + return _images; + } + +private: + /** display from time in seconds from the start of the film */ + double _from; + /** display to time in seconds from the start of the film */ + double _to; + std::list<boost::shared_ptr<SubtitleImage> > _images; +}; + +extern Rectangle transformed_subtitle_area ( + float target_x_scale, float target_y_scale, + Rectangle sub_area, int subtitle_offset, float subtitle_scale + ); + +class SubtitleImage +{ +public: + SubtitleImage (AVSubtitleRect const *); + + void set_position (Position p) { + _position = p; + } + + Position position () const { + return _position; + } + + boost::shared_ptr<Image> image () const { + return _image; + } + + Rectangle area () const; + +private: + Position _position; + boost::shared_ptr<Image> _image; +}; diff --git a/src/lib/thumbs_job.cc b/src/lib/thumbs_job.cc index f6ed75ff7..16a8a7b01 100644 --- a/src/lib/thumbs_job.cc +++ b/src/lib/thumbs_job.cc @@ -24,7 +24,7 @@ #include <exception> #include "thumbs_job.h" #include "film_state.h" -#include "tiff_encoder.h" +#include "imagemagick_encoder.h" #include "transcoder.h" #include "options.h" @@ -35,8 +35,8 @@ using namespace boost; * @param o Options. * @param l A log that we can write to. */ -ThumbsJob::ThumbsJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) - : Job (s, o, l) +ThumbsJob::ThumbsJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, shared_ptr<Job> req) + : Job (s, o, l, req) { } @@ -51,7 +51,7 @@ void ThumbsJob::run () { try { - shared_ptr<TIFFEncoder> e (new TIFFEncoder (_fs, _opt, _log)); + shared_ptr<ImageMagickEncoder> e (new ImageMagickEncoder (_fs, _opt, _log)); Transcoder w (_fs, _opt, this, _log, e); w.go (); set_progress (1); diff --git a/src/lib/thumbs_job.h b/src/lib/thumbs_job.h index 1dd69a0f9..f7e30d576 100644 --- a/src/lib/thumbs_job.h +++ b/src/lib/thumbs_job.h @@ -31,7 +31,7 @@ class FilmState; class ThumbsJob : public Job { public: - ThumbsJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); + ThumbsJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l, boost::shared_ptr<Job> req); std::string name () const; void run (); }; diff --git a/src/lib/tiff_decoder.h b/src/lib/tiff_decoder.h index d9b5b3969..b9849a259 100644 --- a/src/lib/tiff_decoder.h +++ b/src/lib/tiff_decoder.h @@ -53,6 +53,9 @@ public: int audio_sample_rate () const; AVSampleFormat audio_sample_format () const; int64_t audio_channel_layout () const; + bool has_subtitles () const { + return false; + } private: bool do_pass (); diff --git a/src/lib/tiff_encoder.cc b/src/lib/tiff_encoder.cc deleted file mode 100644 index 19e34741d..000000000 --- a/src/lib/tiff_encoder.cc +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file src/tiff_encoder.h - * @brief An encoder that writes TIFF files (and does nothing with audio). - */ - -#include <stdexcept> -#include <vector> -#include <sstream> -#include <iomanip> -#include <iostream> -#include <boost/filesystem.hpp> -#include <tiffio.h> -#include "tiff_encoder.h" -#include "film.h" -#include "film_state.h" -#include "options.h" -#include "exceptions.h" -#include "image.h" - -using namespace std; -using namespace boost; - -/** @param s FilmState of the film that we are encoding. - * @param o Options. - * @param l Log. - */ -TIFFEncoder::TIFFEncoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) - : Encoder (s, o, l) -{ - -} - -void -TIFFEncoder::process_video (shared_ptr<Image> image, int frame) -{ - shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _fs->scaler); - string tmp_file = _opt->frame_out_path (frame, true); - TIFF* output = TIFFOpen (tmp_file.c_str (), "w"); - if (output == 0) { - throw CreateFileError (tmp_file); - } - - TIFFSetField (output, TIFFTAG_IMAGEWIDTH, _opt->out_size.width); - TIFFSetField (output, TIFFTAG_IMAGELENGTH, _opt->out_size.height); - TIFFSetField (output, TIFFTAG_COMPRESSION, COMPRESSION_NONE); - TIFFSetField (output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - TIFFSetField (output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); - TIFFSetField (output, TIFFTAG_BITSPERSAMPLE, 8); - TIFFSetField (output, TIFFTAG_SAMPLESPERPIXEL, 3); - - if (TIFFWriteEncodedStrip (output, 0, scaled->data()[0], _opt->out_size.width * _opt->out_size.height * 3) == 0) { - throw WriteFileError (tmp_file, 0); - } - - TIFFClose (output); - - boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false)); - frame_done (frame); -} diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index e1ba82359..a53a4b6ad 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -39,8 +39,8 @@ using namespace boost; * @param o Options. * @param l A log that we can write to. */ -TranscodeJob::TranscodeJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l) - : Job (s, o, l) +TranscodeJob::TranscodeJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, shared_ptr<Job> req) + : Job (s, o, l, req) { } diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h index 737f10de9..fe68a4910 100644 --- a/src/lib/transcode_job.h +++ b/src/lib/transcode_job.h @@ -32,7 +32,7 @@ class Encoder; class TranscodeJob : public Job { public: - TranscodeJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l); + TranscodeJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l, boost::shared_ptr<Job> req); std::string name () const; void run (); diff --git a/src/lib/util.cc b/src/lib/util.cc index 935566440..fbe77461e 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -600,3 +600,24 @@ Socket::read_indefinite (uint8_t* data, int size, int timeout) assert (size >= _buffer_data); memcpy (data, _buffer, size); } + +Rectangle +Rectangle::intersection (Rectangle const & other) const +{ + int const tx = max (x, other.x); + int const ty = max (y, other.y); + + return Rectangle ( + tx, ty, + min (x + w, other.x + other.w) - tx, + min (y + h, other.y + other.h) - ty + ); +} + +int +round_up (int a, int t) +{ + a += (t - 1); + return a - (a % t); +} + diff --git a/src/lib/util.h b/src/lib/util.h index 3eac06e97..bd7675a8a 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -121,9 +121,35 @@ struct Position int y; }; +/** A rectangle */ +struct Rectangle +{ + Rectangle () + : x (0) + , y (0) + , w (0) + , h (0) + {} + + Rectangle (int x_, int y_, int w_, int h_) + : x (x_) + , y (y_) + , w (w_) + , h (h_) + {} + + int x; + int y; + int w; + int h; + + Rectangle intersection (Rectangle const & other) const; +}; + extern std::string crop_string (Position, Size); extern int dcp_audio_sample_rate (int); extern std::string colour_lut_index_to_name (int index); +extern int round_up (int, int); /** @class Socket * @brief A class to wrap a boost::asio::ip::tcp::socket with some things @@ -166,3 +192,4 @@ private: }; #endif + diff --git a/src/lib/wscript b/src/lib/wscript index c809226ce..63847224c 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -2,7 +2,7 @@ def build(bld): obj = bld(features = 'cxx cxxshlib') obj.name = 'libdvdomatic' obj.export_includes = ['.'] - obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD OPENJPEG POSTPROC TIFF SIGC++ MAGICK SSH DCP GLIB' + obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME OPENJPEG POSTPROC TIFF SIGC++ MAGICK SSH DCP GLIB' if bld.env.TARGET_WINDOWS: obj.uselib += ' WINSOCK2' obj.source = """ @@ -30,6 +30,7 @@ def build(bld): format.cc image.cc imagemagick_decoder.cc + imagemagick_encoder.cc j2k_still_encoder.cc j2k_wav_encoder.cc job.cc @@ -42,9 +43,9 @@ def build(bld): screen.cc server.cc sound_processor.cc + subtitle.cc thumbs_job.cc tiff_decoder.cc - tiff_encoder.cc timer.cc transcode_job.cc transcoder.cc diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 3b26a5537..7fd2eb9fc 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -60,6 +60,11 @@ FilmEditor::FilmEditor (Film* f, wxWindow* parent) _name = new wxTextCtrl (this, wxID_ANY); _sizer->Add (_name, 1, wxEXPAND); + _use_dci_name = new wxCheckBox (this, wxID_ANY, wxT ("Use DCI name")); + _sizer->Add (_use_dci_name, 1, wxEXPAND); + _edit_dci_button = new wxButton (this, wxID_ANY, wxT ("Edit...")); + _sizer->Add (_edit_dci_button, 0); + add_label_to_sizer (_sizer, this, "Content"); _content = new wxFilePickerCtrl (this, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*")); _sizer->Add (_content, 1, wxEXPAND); @@ -130,6 +135,24 @@ FilmEditor::FilmEditor (Film* f, wxWindow* parent) _sizer->Add (s); } + _with_subtitles = new wxCheckBox (this, wxID_ANY, wxT("With Subtitles")); + video_control (_with_subtitles); + _sizer->Add (_with_subtitles, 1); + _sizer->AddSpacer (0); + + video_control (add_label_to_sizer (_sizer, this, "Subtitle Offset")); + _subtitle_offset = new wxSpinCtrl (this); + _sizer->Add (video_control (_subtitle_offset), 1); + + { + video_control (add_label_to_sizer (_sizer, this, "Subtitle Scale")); + wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _subtitle_scale = new wxSpinCtrl (this); + s->Add (video_control (_subtitle_scale)); + video_control (add_label_to_sizer (s, this, "%")); + _sizer->Add (s); + } + video_control (add_label_to_sizer (_sizer, this, "Frames Per Second")); _frames_per_second = new wxStaticText (this, wxID_ANY, wxT ("")); _sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL); @@ -181,6 +204,8 @@ FilmEditor::FilmEditor (Film* f, wxWindow* parent) _audio_gain->SetRange (-60, 60); _audio_delay->SetRange (-1000, 1000); _still_duration->SetRange (0, 60 * 60); + _subtitle_offset->SetRange (-1024, 1024); + _subtitle_scale->SetRange (1, 1000); vector<DCPContentType const *> const ct = DCPContentType::all (); for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) { @@ -214,6 +239,9 @@ FilmEditor::FilmEditor (Film* f, wxWindow* parent) _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this); _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this); _change_dcp_range_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::change_dcp_range_clicked), 0, this); + _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this); + _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this); + _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this); setup_visibility (); setup_formats (); @@ -292,6 +320,7 @@ FilmEditor::content_changed (wxCommandEvent &) setup_visibility (); setup_formats (); + setup_subtitle_button (); } /** Called when the DCP A/B switch has been toggled */ @@ -320,6 +349,31 @@ FilmEditor::name_changed (wxCommandEvent &) _ignore_changes = Film::NONE; } +void +FilmEditor::subtitle_offset_changed (wxCommandEvent &) +{ + if (!_film) { + return; + } + + _ignore_changes = Film::SUBTITLE_OFFSET; + _film->set_subtitle_offset (_subtitle_offset->GetValue ()); + _ignore_changes = Film::NONE; +} + +void +FilmEditor::subtitle_scale_changed (wxCommandEvent &) +{ + if (!_film) { + return; + } + + _ignore_changes = Film::SUBTITLE_OFFSET; + _film->set_subtitle_scale (_subtitle_scale->GetValue() / 100.0); + _ignore_changes = Film::NONE; +} + + /** Called when the metadata stored in the Film object has changed; * so that we can update the GUI. * @param p Property of the Film that has changed. @@ -340,6 +394,7 @@ FilmEditor::film_changed (Film::Property p) _content->SetPath (std_to_wx (_film->content ())); setup_visibility (); setup_formats (); + setup_subtitle_button (); break; case Film::FORMAT: { @@ -437,6 +492,17 @@ FilmEditor::film_changed (Film::Property p) case Film::STILL_DURATION: _still_duration->SetValue (_film->still_duration ()); break; + case Film::WITH_SUBTITLES: + _with_subtitles->SetValue (_film->with_subtitles ()); + _subtitle_scale->Enable (_film->with_subtitles ()); + _subtitle_offset->Enable (_film->with_subtitles ()); + break; + case Film::SUBTITLE_OFFSET: + _subtitle_offset->SetValue (_film->subtitle_offset ()); + break; + case Film::SUBTITLE_SCALE: + _subtitle_scale->SetValue (_film->subtitle_scale() * 100); + break; } } @@ -509,6 +575,9 @@ FilmEditor::set_film (Film* f) film_changed (Film::AUDIO_GAIN); film_changed (Film::AUDIO_DELAY); film_changed (Film::STILL_DURATION); + film_changed (Film::WITH_SUBTITLES); + film_changed (Film::SUBTITLE_OFFSET); + film_changed (Film::SUBTITLE_SCALE); } /** Updates the sensitivity of lots of widgets to a given value. @@ -535,6 +604,9 @@ FilmEditor::set_things_sensitive (bool s) _audio_gain_calculate_button->Enable (s); _audio_delay->Enable (s); _still_duration->Enable (s); + _with_subtitles->Enable (s); + _subtitle_offset->Enable (s); + _subtitle_scale->Enable (s); } /** Called when the `Edit filters' button has been clicked */ @@ -702,3 +774,28 @@ FilmEditor::setup_formats () _sizer->Layout (); } + +void +FilmEditor::with_subtitles_toggled (wxCommandEvent &) +{ + if (!_film) { + return; + } + + _ignore_changes = Film::WITH_SUBTITLES; + _film->set_with_subtitles (_with_subtitles->GetValue ()); + _ignore_changes = Film::NONE; + + _subtitle_scale->Enable (_film->with_subtitles ()); + _subtitle_offset->Enable (_film->with_subtitles ()); +} + +void +FilmEditor::setup_subtitle_button () +{ + _with_subtitles->Enable (_film->has_subtitles ()); + if (!_film->has_subtitles ()) { + _with_subtitles->SetValue (false); + } +} + diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index c599cd285..2a3be6d0c 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -24,6 +24,7 @@ #include <wx/wx.h> #include <wx/spinctrl.h> #include <wx/filepicker.h> +#include <wx/collpane.h> #include "lib/trim_action.h" #include "lib/film.h" @@ -58,6 +59,9 @@ private: void audio_gain_changed (wxCommandEvent &); void audio_gain_calculate_button_clicked (wxCommandEvent &); void audio_delay_changed (wxCommandEvent &); + void with_subtitles_toggled (wxCommandEvent &); + void subtitle_offset_changed (wxCommandEvent &); + void subtitle_scale_changed (wxCommandEvent &); void still_duration_changed (wxCommandEvent &); /* Handle changes to the model */ @@ -69,6 +73,7 @@ private: void set_things_sensitive (bool); void setup_formats (); + void setup_subtitle_button (); wxControl* video_control (wxControl *); wxControl* still_control (wxControl *); @@ -79,6 +84,8 @@ private: Film* _film; /** The Film's name */ wxTextCtrl* _name; + wxCheckBox* _use_dci_name; + wxButton* _edit_dci_button; /** The Film's format */ wxComboBox* _format; /** The Film's content file */ @@ -103,6 +110,9 @@ private: wxButton* _audio_gain_calculate_button; /** The Film's audio delay */ wxSpinCtrl* _audio_delay; + wxCheckBox* _with_subtitles; + wxSpinCtrl* _subtitle_offset; + wxSpinCtrl* _subtitle_scale; /** The Film's DCP content type */ wxComboBox* _dcp_content_type; /** The Film's frames per second */ diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 3c7d76bce..bf082adc2 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -30,6 +30,7 @@ #include "lib/job_manager.h" #include "lib/film_state.h" #include "lib/options.h" +#include "lib/subtitle.h" #include "film_viewer.h" #include "wx_util.h" @@ -42,30 +43,48 @@ public: ThumbPanel (wxPanel* parent, Film* film) : wxPanel (parent) , _film (film) - , _image (0) - , _bitmap (0) - { - } + , _frame_rebuild_needed (false) + , _composition_needed (false) + {} /** Handle a paint event */ void paint_event (wxPaintEvent& ev) { - if (_current_image != _pending_image) { - delete _image; - _image = new wxImage (std_to_wx (_pending_image)); - _current_image = _pending_image; - setup (); + if (!_film || _film->num_thumbs() == 0) { + wxPaintDC dc (this); + return; + } + + if (_frame_rebuild_needed) { + _image.reset (new wxImage (std_to_wx (_film->thumb_file (_index)))); + + _subtitles.clear (); + list<pair<Position, string> > s = _film->thumb_subtitles (_index); + for (list<pair<Position, string> >::iterator i = s.begin(); i != s.end(); ++i) { + _subtitles.push_back (SubtitleView (i->first, std_to_wx (i->second))); + } + + _frame_rebuild_needed = false; + + compose (); + _composition_needed = false; } - if (_current_crop != _pending_crop) { - _current_crop = _pending_crop; - setup (); + if (_composition_needed) { + compose (); + _composition_needed = false; } wxPaintDC dc (this); if (_bitmap) { dc.DrawBitmap (*_bitmap, 0, 0, false); } + + if (_film->with_subtitles ()) { + for (list<SubtitleView>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + dc.DrawBitmap (*i->bitmap, i->transformed_area.x, i->transformed_area.y, true); + } + } } /** Handle a size event */ @@ -75,19 +94,14 @@ public: return; } - setup (); - Refresh (); - } - - void set (string f) - { - _pending_image = f; - Refresh (); + recompose (); } - void set_crop (Crop c) + /** @param n Thumbnail index */ + void set (int n) { - _pending_crop = c; + _index = n; + _frame_rebuild_needed = true; Refresh (); } @@ -96,9 +110,10 @@ public: _film = f; if (!_film) { clear (); + _frame_rebuild_needed = true; Refresh (); } else { - setup (); + _frame_rebuild_needed = true; Refresh (); } } @@ -106,15 +121,14 @@ public: /** Clear our thumbnail image */ void clear () { - delete _bitmap; - _bitmap = 0; - delete _image; - _image = 0; + _bitmap.reset (); + _image.reset (); + _subtitles.clear (); } - void refresh () + void recompose () { - setup (); + _composition_needed = true; Refresh (); } @@ -122,46 +136,88 @@ public: private: - void setup () + void compose () { if (!_film || !_image) { return; } - + + /* Size of the view */ int vw, vh; GetSize (&vw, &vh); + /* Cropped rectangle */ + Rectangle cropped_area ( + _film->crop().left, + _film->crop().top, + _image->GetWidth() - (_film->crop().left + _film->crop().right), + _image->GetHeight() - (_film->crop().top + _film->crop().bottom) + ); + + /* Target ratio */ float const target = _film->format() ? _film->format()->ratio_as_float (_film) : 1.78; - _cropped_image = _image->GetSubImage ( - wxRect ( - _current_crop.left, - _current_crop.top, - _image->GetWidth() - (_current_crop.left + _current_crop.right), - _image->GetHeight() - (_current_crop.top + _current_crop.bottom) - ) - ); + _transformed_image = _image->GetSubImage (wxRect (cropped_area.x, cropped_area.y, cropped_area.w, cropped_area.h)); + + float x_scale = 1; + float y_scale = 1; if ((float (vw) / vh) > target) { /* view is longer (horizontally) than the ratio; fit height */ - _cropped_image.Rescale (vh * target, vh, wxIMAGE_QUALITY_HIGH); + _transformed_image.Rescale (vh * target, vh, wxIMAGE_QUALITY_HIGH); + x_scale = vh * target / cropped_area.w; + y_scale = float (vh) / cropped_area.h; } else { /* view is shorter (horizontally) than the ratio; fit width */ - _cropped_image.Rescale (vw, vw / target, wxIMAGE_QUALITY_HIGH); + _transformed_image.Rescale (vw, vw / target, wxIMAGE_QUALITY_HIGH); + x_scale = float (vw) / cropped_area.w; + y_scale = (vw / target) / cropped_area.h; } - delete _bitmap; - _bitmap = new wxBitmap (_cropped_image); + _bitmap.reset (new wxBitmap (_transformed_image)); + + for (list<SubtitleView>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + + i->transformed_area = transformed_subtitle_area ( + x_scale, y_scale, i->base_area, _film->subtitle_offset(), _film->subtitle_scale() + ); + + i->transformed_image = i->base_image; + i->transformed_image.Rescale (i->transformed_area.w, i->transformed_area.h, wxIMAGE_QUALITY_HIGH); + i->transformed_area.x -= _film->crop().left; + i->transformed_area.y -= _film->crop().top; + i->bitmap.reset (new wxBitmap (i->transformed_image)); + } } Film* _film; - wxImage* _image; - std::string _current_image; - std::string _pending_image; - wxImage _cropped_image; - wxBitmap* _bitmap; - Crop _current_crop; - Crop _pending_crop; + shared_ptr<wxImage> _image; + wxImage _transformed_image; + /** currently-displayed thumbnail index */ + int _index; + shared_ptr<wxBitmap> _bitmap; + bool _frame_rebuild_needed; + bool _composition_needed; + + struct SubtitleView + { + SubtitleView (Position p, wxString const & i) + : base_image (i) + { + base_area.x = p.x; + base_area.y = p.y; + base_area.w = base_image.GetWidth (); + base_area.h = base_image.GetHeight (); + } + + Rectangle base_area; + Rectangle transformed_area; + wxImage base_image; + wxImage transformed_image; + shared_ptr<wxBitmap> bitmap; + }; + + list<SubtitleView> _subtitles; }; BEGIN_EVENT_TABLE (ThumbPanel, wxPanel) @@ -196,7 +252,7 @@ FilmViewer::set_thumbnail (int n) return; } - _thumb_panel->set (_film->thumb_file(n)); + _thumb_panel->set (n); } void @@ -208,9 +264,8 @@ FilmViewer::slider_changed (wxCommandEvent &) void FilmViewer::film_changed (Film::Property p) { - if (p == Film::CROP) { - _thumb_panel->set_crop (_film->crop ()); - } else if (p == Film::THUMBS) { + switch (p) { + case Film::THUMBS: if (_film && _film->num_thumbs() > 1) { _slider->SetRange (0, _film->num_thumbs () - 1); } else { @@ -220,12 +275,21 @@ FilmViewer::film_changed (Film::Property p) _slider->SetValue (0); set_thumbnail (0); - } else if (p == Film::FORMAT) { - _thumb_panel->refresh (); - } else if (p == Film::CONTENT) { + break; + case Film::CONTENT: setup_visibility (); _film->examine_content (); update_thumbs (); + break; + case Film::CROP: + case Film::FORMAT: + case Film::WITH_SUBTITLES: + case Film::SUBTITLE_OFFSET: + case Film::SUBTITLE_SCALE: + _thumb_panel->recompose (); + break; + default: + break; } } @@ -246,7 +310,6 @@ FilmViewer::set_film (Film* f) _film->Changed.connect (sigc::mem_fun (*this, &FilmViewer::film_changed)); film_changed (Film::CROP); film_changed (Film::THUMBS); - _thumb_panel->refresh (); setup_visibility (); } @@ -260,13 +323,14 @@ FilmViewer::update_thumbs () _film->update_thumbs_pre_gui (); shared_ptr<const FilmState> s = _film->state_copy (); - shared_ptr<Options> o (new Options (s->dir ("thumbs"), ".tiff", "")); + shared_ptr<Options> o (new Options (s->dir ("thumbs"), ".png", "")); o->out_size = _film->size (); o->apply_crop = false; o->decode_audio = false; o->decode_video_frequency = 128; + o->decode_subtitles = true; - shared_ptr<Job> j (new ThumbsJob (s, o, _film->log ())); + shared_ptr<Job> j (new ThumbsJob (s, o, _film->log(), shared_ptr<Job> ())); j->Finished.connect (sigc::mem_fun (_film, &Film::update_thumbs_post_gui)); JobManager::instance()->add (j); } |
