summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ab_transcoder.cc9
-rw-r--r--src/lib/ab_transcoder.h3
-rw-r--r--src/lib/dcp_video_frame.cc95
-rw-r--r--src/lib/dcp_video_frame.h6
-rw-r--r--src/lib/decoder.cc21
-rw-r--r--src/lib/decoder.h8
-rw-r--r--src/lib/encoder.h4
-rw-r--r--src/lib/ffmpeg_decoder.cc46
-rw-r--r--src/lib/ffmpeg_decoder.h12
-rw-r--r--src/lib/film.cc72
-rw-r--r--src/lib/film.h25
-rw-r--r--src/lib/film_state.cc26
-rw-r--r--src/lib/film_state.h14
-rw-r--r--src/lib/filter.cc1
-rw-r--r--src/lib/image.cc190
-rw-r--r--src/lib/image.h41
-rw-r--r--src/lib/imagemagick_decoder.cc20
-rw-r--r--src/lib/imagemagick_decoder.h23
-rw-r--r--src/lib/imagemagick_encoder.cc99
-rw-r--r--src/lib/imagemagick_encoder.h (renamed from src/lib/tiff_encoder.h)14
-rw-r--r--src/lib/j2k_still_encoder.cc4
-rw-r--r--src/lib/j2k_still_encoder.h2
-rw-r--r--src/lib/j2k_wav_encoder.cc5
-rw-r--r--src/lib/j2k_wav_encoder.h3
-rw-r--r--src/lib/options.h10
-rw-r--r--src/lib/server.cc21
-rw-r--r--src/lib/subtitle.cc111
-rw-r--r--src/lib/subtitle.h75
-rw-r--r--src/lib/thumbs_job.cc4
-rw-r--r--src/lib/tiff_decoder.h3
-rw-r--r--src/lib/tiff_encoder.cc77
-rw-r--r--src/lib/util.cc20
-rw-r--r--src/lib/util.h26
-rw-r--r--src/lib/wscript3
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