diff options
Diffstat (limited to 'src/lib')
34 files changed, 846 insertions, 247 deletions
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/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/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 00d37c097..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,6 +535,7 @@ 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; @@ -660,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..a4d88d0e0 100644 --- a/src/lib/film_state.cc +++ b/src/lib/film_state.cc @@ -80,6 +80,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 +97,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 +146,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 +175,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 +197,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"); diff --git a/src/lib/film_state.h b/src/lib/film_state.h index 16a1b0508..d53c6a969 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; @@ -126,6 +131,12 @@ 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; /* Data which is cached to speed things up */ @@ -143,9 +154,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/image.cc b/src/lib/image.cc index 2df7636af..2c0338b53 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -57,6 +57,7 @@ Image::lines (int n) const } break; case PIX_FMT_RGB24: + case PIX_FMT_RGBA: return size().height; default: assert (false); @@ -73,6 +74,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 +83,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 SimpleImage (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 +120,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 SimpleImage (PIX_FMT_RGB24, content_size)); struct SwsContext* scale_context = sws_getContext ( size().width, size().height, pixel_format(), @@ -104,9 +131,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 +141,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 SimpleImage (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 +151,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 +167,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 SimpleImage (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 +193,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,6 +207,42 @@ 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. * @@ -190,12 +253,32 @@ SimpleImage::SimpleImage (PixelFormat p, Size s) : 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] = round_up (_line_size[i], 32); + _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i)); } } @@ -208,17 +291,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,6 +306,12 @@ SimpleImage::line_size () const return _line_size; } +int * +SimpleImage::stride () const +{ + return _stride; +} + Size SimpleImage::size () const { @@ -264,6 +343,13 @@ FilterBufferImage::line_size () const return _buffer->linesize; } +int * +FilterBufferImage::stride () const +{ + /* XXX? */ + return _buffer->linesize; +} + Size FilterBufferImage::size () const { @@ -308,49 +394,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..3e16d43bf 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -34,7 +34,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 +57,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 +94,7 @@ public: uint8_t ** data () const; int * line_size () const; + int * stride () const; Size size () const; private: @@ -106,14 +112,15 @@ public: 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 RGBFrameImage @@ -127,6 +134,7 @@ public: uint8_t ** data () const; int * line_size () const; + int * stride () const; Size size () const; AVFrame * frame () const { return _frame; @@ -138,23 +146,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..b7b79ed0c --- /dev/null +++ b/src/lib/imagemagick_encoder.cc @@ -0,0 +1,99 @@ +/* + 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); + + string tmp_file = _opt->frame_out_path (frame, true); + Magick::Image thumb (_opt->out_size.width, _opt->out_size.height, "RGB", MagickCore::CharPixel, scaled->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); + + string tmp_sub_file = _opt->frame_out_path (frame, true, ext.str ()); + Magick::Image sub_thumb (scaled->size().width, scaled->size().height, "RGBA", MagickCore::CharPixel, scaled->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/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/server.cc b/src/lib/server.cc index 28236e3e0..659418b8f 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 SimpleImage (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..f0d77c511 --- /dev/null +++ b/src/lib/subtitle.cc @@ -0,0 +1,111 @@ +/* + 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 SimpleImage (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; + for (int x = 0; x < rect->w; ++x) { + *out_p++ = palette[*sub_line_p++]; + } + sub_p += rect->pict.linesize[0]; + } +} + +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 779a1d5d1..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" @@ -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/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/util.cc b/src/lib/util.cc index 935566440..c3dd13d7c 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -600,3 +600,23 @@ 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..244c01855 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 diff --git a/src/lib/wscript b/src/lib/wscript index c809226ce..9539d57a1 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -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 |
