summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/decoder.cc2
-rw-r--r--src/lib/decoder.h2
-rw-r--r--src/lib/ffmpeg_decoder.cc152
-rw-r--r--src/lib/ffmpeg_decoder.h14
-rw-r--r--src/lib/film.cc8
-rw-r--r--src/lib/film.h12
-rw-r--r--src/lib/film_state.cc6
-rw-r--r--src/lib/film_state.h5
-rw-r--r--src/lib/imagemagick_decoder.h4
-rw-r--r--src/lib/tiff_decoder.h3
-rw-r--r--src/lib/util.h16
11 files changed, 221 insertions, 3 deletions
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 324d1a296..15d74022c 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -303,6 +303,8 @@ Decoder::process_video (AVFrame* frame)
image->make_black ();
}
+ overlay (image);
+
TIMING ("Decoder emits %1", _video_frame);
Video (image, _video_frame);
++_video_frame;
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index 04ff512eb..9a4c7695e 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -66,6 +66,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 ();
@@ -97,6 +98,7 @@ protected:
virtual int time_base_denominator () const = 0;
virtual int sample_aspect_ratio_numerator () const = 0;
virtual int sample_aspect_ratio_denominator () const = 0;
+ virtual void overlay (boost::shared_ptr<Image> image) const {}
void process_video (AVFrame *);
void process_audio (uint8_t *, int);
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 767299ea6..ca35c6e81 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>
@@ -56,15 +57,20 @@ 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)
+ , _have_subtitle (false)
{
setup_general ();
setup_video ();
setup_audio ();
+ setup_subtitle ();
}
FFmpegDecoder::~FFmpegDecoder ()
@@ -76,6 +82,14 @@ FFmpegDecoder::~FFmpegDecoder ()
if (_video_codec_context) {
avcodec_close (_video_codec_context);
}
+
+ if (_have_subtitle) {
+ avsubtitle_free (&_subtitle);
+ }
+
+ if (_subtitle_codec_context) {
+ avcodec_close (_subtitle_codec_context);
+ }
av_free (_frame);
avformat_close_input (&_format_context);
@@ -101,6 +115,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 +172,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 +246,18 @@ 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 && _fs->with_subtitles) {
+
+ if (_have_subtitle) {
+ avsubtitle_free (&_subtitle);
+ _have_subtitle = false;
+ }
+
+ int got_subtitle;
+ if (avcodec_decode_subtitle2 (_subtitle_codec_context, &_subtitle, &got_subtitle, &_packet) && got_subtitle) {
+ _have_subtitle = true;
+ }
}
av_free_packet (&_packet);
@@ -310,3 +358,107 @@ FFmpegDecoder::sample_aspect_ratio_denominator () const
return _video_codec_context->sample_aspect_ratio.den;
}
+void
+FFmpegDecoder::overlay (shared_ptr<Image> image) const
+{
+ if (!_have_subtitle) {
+ return;
+ }
+
+ /* subtitle PTS in seconds */
+ float const packet_time = (_subtitle.pts / AV_TIME_BASE) + float (_subtitle.pts % AV_TIME_BASE) / 1e6;
+ /* hence start time for this sub */
+ float const from = packet_time + (float (_subtitle.start_display_time) / 1e3);
+ float const to = packet_time + (float (_subtitle.end_display_time) / 1e3);
+
+ float const video_frame_time = float (last_video_frame ()) / rint (_fs->frames_per_second);
+
+ if (from > video_frame_time || video_frame_time < to) {
+ return;
+ }
+
+ for (unsigned int i = 0; i < _subtitle.num_rects; ++i) {
+ AVSubtitleRect* rect = _subtitle.rects[i];
+ if (rect->type != SUBTITLE_BITMAP) {
+ throw DecodeError ("non-bitmap subtitles not yet supported");
+ }
+
+ /* XXX: all this assumes YUV420 in image */
+
+ assert (rect->pict.data[0]);
+
+ /* Start of the first line in the target image */
+ uint8_t* frame_y_p = image->data()[0] + rect->y * image->line_size()[0];
+ uint8_t* frame_u_p = image->data()[1] + (rect->y / 2) * image->line_size()[1];
+ uint8_t* frame_v_p = image->data()[2] + (rect->y / 2) * image->line_size()[2];
+
+ int const hlim = min (rect->y + rect->h, image->size().height) - rect->y;
+
+ /* 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];
+
+ for (int sub_y = 0; sub_y < hlim; ++sub_y) {
+ /* Pointers to the start of this line */
+ uint8_t* sub_line_p = sub_p;
+ uint8_t* frame_line_y_p = frame_y_p + rect->x;
+ uint8_t* frame_line_u_p = frame_u_p + (rect->x / 2);
+ uint8_t* frame_line_v_p = frame_v_p + (rect->x / 2);
+
+ /* U and V are subsampled */
+ uint8_t next_u = 0;
+ uint8_t next_v = 0;
+ int subsample_step = 0;
+
+ for (int sub_x = 0; sub_x < rect->w; ++sub_x) {
+
+ /* RGB value for this subtitle pixel */
+ uint32_t const val = palette[*sub_line_p++];
+
+ int const red = (val & 0xff);
+ int const green = (val & 0xff00) >> 8;
+ int const blue = (val & 0xff0000) >> 16;
+ float const alpha = ((val & 0xff000000) >> 24) / 255.0;
+
+ /* Alpha-blend Y */
+ int const cy = *frame_line_y_p;
+ *frame_line_y_p++ = int (cy * (1 - alpha)) + int (RGB_TO_Y_CCIR (red, green, blue) * alpha);
+
+ /* Store up U and V */
+ next_u |= ((RGB_TO_U_CCIR (red, green, blue, 0) & 0xf0) >> 4) << (4 * subsample_step);
+ next_v |= ((RGB_TO_V_CCIR (red, green, blue, 0) & 0xf0) >> 4) << (4 * subsample_step);
+
+ if (subsample_step == 1 && (sub_y % 2) == 0) {
+ int const cu = *frame_line_u_p;
+ int const cv = *frame_line_v_p;
+
+ *frame_line_u_p++ =
+ int (((cu & 0x0f) * (1 - alpha) + (next_u & 0x0f) * alpha)) |
+ int (((cu & 0xf0) * (1 - alpha) + (next_u & 0xf0) * alpha));
+
+ *frame_line_v_p++ =
+ int (((cv & 0x0f) * (1 - alpha) + (next_v & 0x0f) * alpha)) |
+ int (((cv & 0xf0) * (1 - alpha) + (next_v & 0xf0) * alpha));
+
+ next_u = next_v = 0;
+ }
+
+ subsample_step = (subsample_step + 1) % 2;
+ }
+
+ sub_p += rect->pict.linesize[0];
+ frame_y_p += image->line_size()[0];
+ if ((sub_y % 2) == 0) {
+ frame_u_p += image->line_size()[1];
+ frame_v_p += image->line_size()[2];
+ }
+ }
+ }
+}
+
+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..59ec7573d 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -63,6 +63,7 @@ public:
int audio_sample_rate () const;
AVSampleFormat audio_sample_format () const;
int64_t audio_channel_layout () const;
+ bool has_subtitles () const;
private:
@@ -72,20 +73,29 @@ private:
int time_base_denominator () const;
int sample_aspect_ratio_numerator () const;
int sample_aspect_ratio_denominator () const;
+ void overlay (boost::shared_ptr<Image> image) const;
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;
+ AVSubtitle _subtitle;
+ bool _have_subtitle;
};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 00d37c097..95dc7b825 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -217,6 +217,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;
@@ -660,3 +661,10 @@ Film::encoded_frames () const
return N;
}
+
+void
+Film::set_with_subtitles (bool w)
+{
+ _state.with_subtitles = w;
+ signal_changed (WITH_SUBTITLES);
+}
diff --git a/src/lib/film.h b/src/lib/film.h
index cd3b1b8a8..919cecc22 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -119,6 +119,10 @@ public:
int still_duration () const {
return _state.still_duration;
}
+
+ bool with_subtitles () const {
+ return _state.with_subtitles;
+ }
void set_filters (std::vector<Filter const *> const &);
@@ -144,6 +148,7 @@ public:
void set_audio_gain (float);
void set_audio_delay (int);
void set_still_duration (int);
+ void set_with_subtitles (bool);
/** @return size, in pixels, of the source (ignoring cropping) */
Size size () const {
@@ -174,6 +179,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;
@@ -218,7 +227,8 @@ public:
FRAMES_PER_SECOND,
AUDIO_CHANNELS,
AUDIO_SAMPLE_RATE,
- STILL_DURATION
+ STILL_DURATION,
+ WITH_SUBTITLES,
};
boost::shared_ptr<FilmState> state_copy () const;
diff --git a/src/lib/film_state.cc b/src/lib/film_state.cc
index 3d58a4fec..03d4cae83 100644
--- a/src/lib/film_state.cc
+++ b/src/lib/film_state.cc
@@ -80,6 +80,7 @@ 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";
/* Cached stuff; this is information about our content; we could
look it up each time, but that's slow.
@@ -94,6 +95,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 +144,8 @@ 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");
}
/* Cached stuff */
@@ -165,6 +169,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");
}
}
diff --git a/src/lib/film_state.h b/src/lib/film_state.h
index 16a1b0508..2b792694c 100644
--- a/src/lib/film_state.h
+++ b/src/lib/film_state.h
@@ -62,10 +62,12 @@ public:
, audio_gain (0)
, audio_delay (0)
, still_duration (10)
+ , with_subtitles (false)
, 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;
@@ -126,6 +128,7 @@ public:
int audio_delay;
/** Duration to make still-sourced films (in seconds) */
int still_duration;
+ bool with_subtitles;
/* Data which is cached to speed things up */
@@ -143,6 +146,8 @@ 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;
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
index aca91ef55..05dc7f113 100644
--- a/src/lib/imagemagick_decoder.h
+++ b/src/lib/imagemagick_decoder.h
@@ -35,6 +35,10 @@ public:
return 0;
}
+ bool has_subtitles () const {
+ return false;
+ }
+
static float static_frames_per_second () {
return 24;
}
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/util.h b/src/lib/util.h
index 3eac06e97..ed13cd43c 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -165,4 +165,20 @@ private:
int _buffer_data;
};
+#define SCALEBITS 10
+#define ONE_HALF (1 << (SCALEBITS - 1))
+#define FIX(x) ((int) ((x) * (1<<SCALEBITS) + 0.5))
+
+#define RGB_TO_Y_CCIR(r, g, b) \
+((FIX(0.29900*219.0/255.0) * (r) + FIX(0.58700*219.0/255.0) * (g) + \
+ FIX(0.11400*219.0/255.0) * (b) + (ONE_HALF + (16 << SCALEBITS))) >> SCALEBITS)
+
+#define RGB_TO_U_CCIR(r1, g1, b1, shift)\
+(((- FIX(0.16874*224.0/255.0) * r1 - FIX(0.33126*224.0/255.0) * g1 + \
+ FIX(0.50000*224.0/255.0) * b1 + (ONE_HALF << shift) - 1) >> (SCALEBITS + shift)) + 128)
+
+#define RGB_TO_V_CCIR(r1, g1, b1, shift)\
+(((FIX(0.50000*224.0/255.0) * r1 - FIX(0.41869*224.0/255.0) * g1 - \
+ FIX(0.08131*224.0/255.0) * b1 + (ONE_HALF << shift) - 1) >> (SCALEBITS + shift)) + 128)
+
#endif