/*
- Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
#include "image.h"
#include "util.h"
#include "log.h"
+#include "dcpomatic_log.h"
#include "ffmpeg_decoder.h"
-#include "subtitle_decoder.h"
+#include "text_decoder.h"
#include "ffmpeg_audio_stream.h"
#include "ffmpeg_subtitle_stream.h"
#include "video_filter_graph.h"
#include "film.h"
#include "audio_decoder.h"
#include "compose.hpp"
-#include "subtitle_content.h"
+#include "text_content.h"
#include "audio_content.h"
#include <dcp/subtitle_string.h>
#include <sub/ssa_reader.h>
#include "i18n.h"
-#define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
-#define LOG_ERROR(...) _log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_ERROR);
-#define LOG_WARNING_NC(...) _log->log (__VA_ARGS__, LogEntry::TYPE_WARNING);
-#define LOG_WARNING(...) _log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_WARNING);
-
using std::cout;
using std::string;
using std::vector;
using boost::optional;
using boost::dynamic_pointer_cast;
using dcp::Size;
+using namespace dcpomatic;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log, bool fast)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> film, shared_ptr<const FFmpegContent> c, bool fast)
: FFmpeg (c)
- , _log (log)
+ , Decoder (film)
, _have_current_subtitle (false)
{
- if (c->video) {
- video.reset (new VideoDecoder (this, c, log));
- _pts_offset = pts_offset (c->ffmpeg_audio_streams(), c->first_video(), c->active_video_frame_rate());
+ if (c->video && c->video->use()) {
+ video.reset (new VideoDecoder (this, c));
+ _pts_offset = pts_offset (c->ffmpeg_audio_streams(), c->first_video(), c->active_video_frame_rate(film));
/* It doesn't matter what size or pixel format this is, it just needs to be black */
_black_image.reset (new Image (AV_PIX_FMT_RGB24, dcp::Size (128, 128), true));
_black_image->make_black ();
}
if (c->audio) {
- audio.reset (new AudioDecoder (this, c->audio, log, fast));
+ audio.reset (new AudioDecoder (this, c->audio, fast));
}
- if (c->subtitle) {
+ if (c->only_text()) {
/* XXX: this time here should be the time of the first subtitle, not 0 */
- subtitle.reset (new SubtitleDecoder (this, c->subtitle, log, ContentTime()));
+ text.push_back (shared_ptr<TextDecoder> (new TextDecoder (this, c->only_text(), ContentTime())));
}
_next_time.resize (_format_context->nb_streams);
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
- while (video && decode_video_packet ()) {}
+ while (video && decode_video_packet()) {}
if (audio) {
decode_audio_packet ();
/* Make sure all streams are the same length and round up to the next video frame */
- FrameRateChange const frc = _ffmpeg_content->film()->active_frame_rate_change(_ffmpeg_content->position());
- ContentTime full_length (_ffmpeg_content->full_length(), frc);
+ FrameRateChange const frc = film()->active_frame_rate_change(_ffmpeg_content->position());
+ ContentTime full_length (_ffmpeg_content->full_length(film()), frc);
full_length = full_length.ceil (frc.source);
if (video) {
double const vfr = _ffmpeg_content->video_frame_rate().get();
Frame const f = full_length.frames_round (vfr);
- Frame v = video->position().frames_round (vfr);
+ Frame v = video->position(film()).get_value_or(ContentTime()).frames_round(vfr) + 1;
while (v < f) {
- video->emit (shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)), v);
+ video->emit (film(), shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)), v);
++v;
}
}
BOOST_FOREACH (shared_ptr<FFmpegAudioStream> i, _ffmpeg_content->ffmpeg_audio_streams ()) {
- ContentTime a = audio->stream_position(i);
- while (a < full_length) {
- ContentTime to_do = min (full_length - a, ContentTime::from_seconds (0.1));
- shared_ptr<AudioBuffers> silence (new AudioBuffers (i->channels(), to_do.frames_ceil (i->frame_rate())));
- silence->make_silent ();
- audio->emit (i, silence, a);
- a += to_do;
+ ContentTime a = audio->stream_position(film(), i);
+ /* Unfortunately if a is 0 that really means that we don't know the stream position since
+ there has been no data on it since the last seek. In this case we'll just do nothing
+ here. I'm not sure if that's the right idea.
+ */
+ if (a > ContentTime()) {
+ while (a < full_length) {
+ ContentTime to_do = min (full_length - a, ContentTime::from_seconds (0.1));
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (i->channels(), to_do.frames_ceil (i->frame_rate())));
+ silence->make_silent ();
+ audio->emit (film(), i, silence, a);
+ a += to_do;
+ }
}
}
bool
FFmpegDecoder::pass ()
{
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+ if (_ffmpeg_content->encrypted() && !_ffmpeg_content->kdm()) {
+ return true;
+ }
+#endif
+
int r = av_read_frame (_format_context, &_packet);
/* AVERROR_INVALIDDATA can apparently be returned sometimes even when av_read_frame
int const si = _packet.stream_index;
shared_ptr<const FFmpegContent> fc = _ffmpeg_content;
- if (_video_stream && si == _video_stream.get() && !video->ignore()) {
+ if (_video_stream && si == _video_stream.get() && video && !video->ignore()) {
decode_video_packet ();
- } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index(_format_context, si) && !subtitle->ignore()) {
+ } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index(_format_context, si) && !only_text()->ignore()) {
decode_subtitle_packet ();
} else {
decode_audio_packet ();
case AV_SAMPLE_FMT_FLTP:
{
float** p = reinterpret_cast<float**> (_frame->data);
+ DCPOMATIC_ASSERT (_frame->channels <= channels);
/* Sometimes there aren't as many channels in the _frame as in the stream */
for (int i = 0; i < _frame->channels; ++i) {
memcpy (data[i], p[i], frames * sizeof(float));
if (_video_stream) {
stream = _video_stream;
} else {
+ DCPOMATIC_ASSERT (_ffmpeg_content->audio);
shared_ptr<FFmpegAudioStream> s = dynamic_pointer_cast<FFmpegAudioStream> (_ffmpeg_content->audio->stream ());
if (s) {
stream = s->index (_format_context);
AVSEEK_FLAG_BACKWARD
);
+ {
+ /* Force re-creation of filter graphs to reset them and hence to make sure
+ they don't have any pre-seek frames knocking about.
+ */
+ boost::mutex::scoped_lock lm (_filter_graphs_mutex);
+ _filter_graphs.clear ();
+ }
+
if (video_codec_context ()) {
avcodec_flush_buffers (video_codec_context());
}
/* Give this data provided there is some, and its time is sane */
if (ct >= ContentTime() && data->frames() > 0) {
- audio->emit (*stream, data, ct);
+ audio->emit (film(), *stream, data, ct);
}
}
}
if (i == _filter_graphs.end ()) {
- graph.reset (new VideoFilterGraph (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ dcp::Fraction vfr (lrint(_ffmpeg_content->video_frame_rate().get() * 1000), 1000);
+ graph.reset (new VideoFilterGraph (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format, vfr));
graph->setup (_ffmpeg_content->filters ());
_filter_graphs.push_back (graph);
LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format);
if (i->second != AV_NOPTS_VALUE) {
double const pts = i->second * av_q2d (_format_context->streams[_video_stream.get()]->time_base) + _pts_offset.seconds ();
+
video->emit (
+ film(),
shared_ptr<ImageProxy> (new RawImageProxy (image)),
- llrint (pts * _ffmpeg_content->active_video_frame_rate ())
+ llrint(pts * _ffmpeg_content->active_video_frame_rate(film()))
);
} else {
LOG_WARNING_NC ("Dropping frame without PTS");
/* Stop any current subtitle, either at the time it was supposed to stop, or now if now is sooner */
if (_have_current_subtitle) {
if (_current_subtitle_to) {
- subtitle->emit_stop (min(*_current_subtitle_to, subtitle_period(sub).from + _pts_offset));
+ only_text()->emit_stop (min(*_current_subtitle_to, subtitle_period(sub).from + _pts_offset));
} else {
- subtitle->emit_stop (subtitle_period(sub).from + _pts_offset);
+ only_text()->emit_stop (subtitle_period(sub).from + _pts_offset);
}
_have_current_subtitle = false;
}
FFmpegSubtitlePeriod sub_period = subtitle_period (sub);
ContentTime from;
from = sub_period.from + _pts_offset;
- _have_current_subtitle = true;
if (sub_period.to) {
_current_subtitle_to = *sub_period.to + _pts_offset;
+ } else {
+ _current_subtitle_to = optional<ContentTime>();
+ _have_current_subtitle = true;
}
for (unsigned int i = 0; i < sub.num_rects; ++i) {
}
}
+ if (_current_subtitle_to) {
+ only_text()->emit_stop (*_current_subtitle_to);
+ }
+
avsubtitle_free (&sub);
}
void
FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime from)
{
- /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
- G, third B, fourth A.
+ /* Note BGRA is expressed little-endian, so the first byte in the word is B, second
+ G, third R, fourth A.
*/
- shared_ptr<Image> image (new Image (AV_PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
+ shared_ptr<Image> image (new Image (AV_PIX_FMT_BGRA, dcp::Size (rect->w, rect->h), true));
#ifdef DCPOMATIC_HAVE_AVSUBTITLERECT_PICT
/* Start of the first line in the subtitle */
#else
/* Start of the first line in the subtitle */
uint8_t* sub_p = rect->data[0];
- /* sub_p looks up into a BGRA palette which is here
- (i.e. first byte B, second G, third R, fourth A)
+ /* sub_p looks up into a BGRA palette which is at rect->data[1].
+ (first byte B, second G, third R, fourth A)
*/
- uint32_t const * palette = (uint32_t *) rect->data[1];
#endif
/* And the stream has a map of those palette colours to colours
chosen by the user; created a `mapped' palette from those settings.
*/
map<RGBA, RGBA> colour_map = ffmpeg_content()->subtitle_stream()->colours ();
vector<RGBA> mapped_palette (rect->nb_colors);
+ uint8_t const * palette = rect->data[1];
for (int i = 0; i < rect->nb_colors; ++i) {
- RGBA c ((palette[i] & 0xff0000) >> 16, (palette[i] & 0xff00) >> 8, palette[i] & 0xff, (palette[i] & 0xff000000) >> 24);
+ RGBA c (palette[2], palette[1], palette[0], palette[3]);
map<RGBA, RGBA>::const_iterator j = colour_map.find (c);
if (j != colour_map.end ()) {
mapped_palette[i] = j->second;
*/
mapped_palette[i] = c;
}
+ palette += 4;
}
/* Start of the output data */
- uint32_t* out_p = (uint32_t *) image->data()[0];
+ uint8_t* out_p = image->data()[0];
for (int y = 0; y < rect->h; ++y) {
uint8_t* sub_line_p = sub_p;
- uint32_t* out_line_p = out_p;
+ uint8_t* out_line_p = out_p;
for (int x = 0; x < rect->w; ++x) {
RGBA const p = mapped_palette[*sub_line_p++];
- /* XXX: this seems to be wrong to me (isn't the output image RGBA?) but it looks right on screen */
- *out_line_p++ = (p.a << 24) | (p.r << 16) | (p.g << 8) | p.b;
+ *out_line_p++ = p.b;
+ *out_line_p++ = p.g;
+ *out_line_p++ = p.r;
+ *out_line_p++ = p.a;
}
#ifdef DCPOMATIC_HAVE_AVSUBTITLERECT_PICT
sub_p += rect->pict.linesize[0];
#else
sub_p += rect->linesize[0];
#endif
- out_p += image->stride()[0] / sizeof (uint32_t);
+ out_p += image->stride()[0];
}
- int const target_width = subtitle_codec_context()->width;
- int const target_height = subtitle_codec_context()->height;
+ int target_width = subtitle_codec_context()->width;
+ if (target_width == 0 && video_codec_context()) {
+ /* subtitle_codec_context()->width == 0 has been seen in the wild but I don't
+ know if it's supposed to mean something from FFmpeg's point of view.
+ */
+ target_width = video_codec_context()->width;
+ }
+ int target_height = subtitle_codec_context()->height;
+ if (target_height == 0 && video_codec_context()) {
+ target_height = video_codec_context()->height;
+ }
+ DCPOMATIC_ASSERT (target_width);
+ DCPOMATIC_ASSERT (target_height);
dcpomatic::Rect<double> const scaled_rect (
static_cast<double> (rect->x) / target_width,
static_cast<double> (rect->y) / target_height,
static_cast<double> (rect->h) / target_height
);
- subtitle->emit_image_start (from, image, scaled_rect);
+ only_text()->emit_bitmap_start (from, image, scaled_rect);
}
void
produces a single format of Dialogue: lines...
*/
- vector<string> bits;
- split (bits, ass, is_any_of (","));
- if (bits.size() < 10) {
+ int commas = 0;
+ string text;
+ for (size_t i = 0; i < ass.length(); ++i) {
+ if (commas < 9 && ass[i] == ',') {
+ ++commas;
+ } else if (commas == 9) {
+ text += ass[i];
+ }
+ }
+
+ if (text.empty ()) {
return;
}
sub::RawSubtitle base;
list<sub::RawSubtitle> raw = sub::SSAReader::parse_line (
base,
- bits[9],
+ text,
_ffmpeg_content->video->size().width,
_ffmpeg_content->video->size().height
);
BOOST_FOREACH (sub::Subtitle const & i, sub::collect<list<sub::Subtitle> > (raw)) {
- subtitle->emit_text_start (from, i);
+ only_text()->emit_plain_start (from, i);
}
}