summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/content_factory.cc8
-rw-r--r--src/lib/decoded.h33
-rw-r--r--src/lib/exceptions.cc8
-rw-r--r--src/lib/exceptions.h7
-rw-r--r--src/lib/ffmpeg_decoder.cc11
-rw-r--r--src/lib/player.cc127
-rw-r--r--src/lib/player.h13
-rw-r--r--src/lib/render_subtitles.cc150
-rw-r--r--src/lib/render_subtitles.h27
-rw-r--r--src/lib/subrip.cc236
-rw-r--r--src/lib/subrip.h53
-rw-r--r--src/lib/subrip_content.cc99
-rw-r--r--src/lib/subrip_content.h42
-rw-r--r--src/lib/subrip_decoder.cc65
-rw-r--r--src/lib/subrip_decoder.h39
-rw-r--r--src/lib/subrip_subtitle.h58
-rw-r--r--src/lib/subtitle_decoder.cc13
-rw-r--r--src/lib/subtitle_decoder.h9
-rw-r--r--src/lib/util.cc2
-rw-r--r--src/lib/wscript6
20 files changed, 929 insertions, 77 deletions
diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc
index bab22b8eb..825c80498 100644
--- a/src/lib/content_factory.cc
+++ b/src/lib/content_factory.cc
@@ -21,6 +21,7 @@
#include "ffmpeg_content.h"
#include "image_content.h"
#include "sndfile_content.h"
+#include "subrip_content.h"
#include "util.h"
using std::string;
@@ -39,6 +40,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version)
content.reset (new ImageContent (film, node, version));
} else if (type == "Sndfile") {
content.reset (new SndfileContent (film, node, version));
+ } else if (type == "SubRip") {
+ content.reset (new SubRipContent (film, node, version));
}
return content;
@@ -48,11 +51,16 @@ shared_ptr<Content>
content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
{
shared_ptr<Content> content;
+
+ string ext = path.extension().string ();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
if (valid_image_file (path)) {
content.reset (new ImageContent (film, path));
} else if (SndfileContent::valid_file (path)) {
content.reset (new SndfileContent (film, path));
+ } else if (ext == ".srt") {
+ content.reset (new SubRipContent (film, path));
} else {
content.reset (new FFmpegContent (film, path));
}
diff --git a/src/lib/decoded.h b/src/lib/decoded.h
index ff32e43f2..f4ebe0dbd 100644
--- a/src/lib/decoded.h
+++ b/src/lib/decoded.h
@@ -20,6 +20,7 @@
#ifndef DCPOMATIC_LIB_DECODED_H
#define DCPOMATIC_LIB_DECODED_H
+#include <libdcp/subtitle_asset.h>
#include "types.h"
#include "rect.h"
#include "util.h"
@@ -85,16 +86,16 @@ public:
AudioFrame frame;
};
-class DecodedSubtitle : public Decoded
+class DecodedImageSubtitle : public Decoded
{
public:
- DecodedSubtitle ()
+ DecodedImageSubtitle ()
: content_time (0)
, content_time_to (0)
, dcp_time_to (0)
{}
- DecodedSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t)
+ DecodedImageSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t)
: image (im)
, rect (r)
, content_time (f)
@@ -115,4 +116,30 @@ public:
DCPTime dcp_time_to;
};
+class DecodedTextSubtitle : public Decoded
+{
+public:
+ DecodedTextSubtitle ()
+ : dcp_time_to (0)
+ {}
+
+ DecodedTextSubtitle (std::list<libdcp::Subtitle> s)
+ : subs (s)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ if (subs.empty ()) {
+ return;
+ }
+
+ /* Assuming that all subs are at the same time */
+ dcp_time = rint (subs.front().in().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+ dcp_time_to = rint (subs.front().out().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+ }
+
+ std::list<libdcp::Subtitle> subs;
+ DCPTime dcp_time_to;
+};
+
#endif
diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc
index 8144f41b9..e05ac4ff0 100644
--- a/src/lib/exceptions.cc
+++ b/src/lib/exceptions.cc
@@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s)
}
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
: StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
{
}
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+ : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f)
+{
+
+}
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index c1240538f..61163c8d1 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -230,6 +230,13 @@ public:
PixelFormatError (std::string o, AVPixelFormat f);
};
+/** An error that occurs while parsing a SubRip file */
+class SubRipError : public FileError
+{
+public:
+ SubRipError (std::string, std::string, boost::filesystem::path);
+};
+
/** A parent class for classes which have a need to catch and
* re-throw exceptions. This is intended for classes
* which run their own thread; they should do something like
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 52afe2a27..55f4eb5c6 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -550,7 +550,7 @@ FFmpegDecoder::decode_subtitle_packet ()
indicate that the previous subtitle should stop.
*/
if (sub.num_rects <= 0) {
- subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+ image_subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
return;
} else if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
@@ -559,13 +559,8 @@ FFmpegDecoder::decode_subtitle_packet ()
/* Subtitle PTS in seconds (within the source, not taking into account any of the
source that we may have chopped off for the DCP)
*/
-<<<<<<< HEAD
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
+ double const packet_time = (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
-=======
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _video_pts_offset;
-
->>>>>>> master
/* hence start time for this sub */
ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
@@ -603,7 +598,7 @@ FFmpegDecoder::decode_subtitle_packet ()
libdcp::Size const vs = _ffmpeg_content->video_size ();
- subtitle (
+ image_subtitle (
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
diff --git a/src/lib/player.cc b/src/lib/player.cc
index 77630f0e3..cb6d51984 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -28,12 +28,15 @@
#include "sndfile_decoder.h"
#include "sndfile_content.h"
#include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
#include "playlist.h"
#include "job.h"
#include "image.h"
#include "ratio.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
using std::list;
using std::cout;
@@ -167,7 +170,8 @@ Player::pass ()
shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
- shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded);
+ shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
+ shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
/* Will be set to false if we shouldn't consume the peeked DecodedThing */
bool consume = true;
@@ -231,10 +235,14 @@ Player::pass ()
_statistics.audio.skip += da->data->frames();
}
- } else if (ds && _video) {
- _in_subtitle.piece = earliest_piece;
- _in_subtitle.subtitle = ds;
- update_subtitle ();
+ } else if (dis && _video) {
+ _image_subtitle.piece = earliest_piece;
+ _image_subtitle.subtitle = dis;
+ update_subtitle_from_image ();
+ } else if (dts && _video) {
+ _text_subtitle.piece = earliest_piece;
+ _text_subtitle.subtitle = dts;
+ update_subtitle_from_text ();
}
if (consume) {
@@ -445,12 +453,38 @@ Player::setup_pieces ()
shared_ptr<Decoder> decoder;
optional<FrameRateChange> frc;
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ DCPTime best_overlap_t = 0;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
+
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+ } else {
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
+
+ /* FFmpeg */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
-
+
+ /* ImageContent */
shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
if (ic) {
/* See if we can re-use an old ImageDecoder */
@@ -468,36 +502,18 @@ Player::setup_pieces ()
frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
+ /* SndfileContent */
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
decoder.reset (new SndfileDecoder (_film, sc));
+ frc = best_overlap_frc;
+ }
- /* Working out the frc for this content is a bit tricky: what if it overlaps
- two pieces of video content with different frame rates? For now, use
- the one with the best overlap.
- */
-
- DCPTime best_overlap_t = 0;
- shared_ptr<VideoContent> best_overlap;
- for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
- if (!vc) {
- continue;
- }
-
- DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
- if (overlap > best_overlap_t) {
- best_overlap = vc;
- best_overlap_t = overlap;
- }
- }
-
- if (best_overlap) {
- frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
- } else {
- /* No video overlap; e.g. if the DCP is just audio */
- frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
- }
+ /* SubRipContent */
+ shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+ if (rc) {
+ decoder.reset (new SubRipDecoder (_film, rc));
+ frc = best_overlap_frc;
}
ContentTime st = (*i)->trim_start() * frc->speed_up;
@@ -533,7 +549,8 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
} else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
- update_subtitle ();
+ update_subtitle_from_image ();
+ update_subtitle_from_text ();
Changed (frequent);
} else if (
@@ -616,14 +633,14 @@ Player::film_changed (Film::Property p)
}
void
-Player::update_subtitle ()
+Player::update_subtitle_from_image ()
{
- shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+ shared_ptr<Piece> piece = _image_subtitle.piece.lock ();
if (!piece) {
return;
}
- if (!_in_subtitle.subtitle->image) {
+ if (!_image_subtitle.subtitle->image) {
_out_subtitle.image.reset ();
return;
}
@@ -631,7 +648,7 @@ Player::update_subtitle ()
shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
assert (sc);
- dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
+ dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect;
libdcp::Size scaled_size;
in_rect.y += sc->subtitle_offset ();
@@ -654,30 +671,16 @@ Player::update_subtitle ()
_out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
_out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
-
- _out_subtitle.image = _in_subtitle.subtitle->image->scale (
+
+ _out_subtitle.image = _image_subtitle.subtitle->image->scale (
scaled_size,
Scaler::from_id ("bicubic"),
- PIX_FMT_RGBA,
+ _image_subtitle.subtitle->image->pixel_format (),
true
);
-
-<<<<<<< HEAD
- _out_subtitle.from = _in_subtitle.subtitle->dcp_time;
- _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to;
-=======
- /* XXX: hack */
- Time from = _in_subtitle.from;
- Time to = _in_subtitle.to;
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
- if (vc) {
- from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
- to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
- }
- _out_subtitle.from = from * piece->content->position ();
- _out_subtitle.to = to + piece->content->position ();
->>>>>>> master
+ _out_subtitle.from = _image_subtitle.subtitle->dcp_time;
+ _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to;
}
/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -699,12 +702,22 @@ Player::repeat_last_video ()
}
void
+Player::update_subtitle_from_text ()
+{
+ if (_text_subtitle.subtitle->subs.empty ()) {
+ _out_subtitle.image.reset ();
+ return;
+ }
+
+ render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position);
+}
+
+void
Player::set_approximate_size ()
{
_approximate_size = true;
}
-
PlayerImage::PlayerImage (
shared_ptr<const Image> in,
Crop crop,
diff --git a/src/lib/player.h b/src/lib/player.h
index 5f7c553a0..377e8bd18 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -23,6 +23,7 @@
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
+#include <libdcp/subtitle_asset.h>
#include "playlist.h"
#include "content.h"
#include "film.h"
@@ -151,7 +152,8 @@ private:
void emit_black ();
void emit_silence (AudioFrame);
void film_changed (Film::Property);
- void update_subtitle ();
+ void update_subtitle_from_image ();
+ void update_subtitle_from_text ();
void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>);
void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>);
void step_video_position (boost::shared_ptr<DecodedVideo>);
@@ -178,10 +180,15 @@ private:
struct {
boost::weak_ptr<Piece> piece;
- boost::shared_ptr<DecodedSubtitle> subtitle;
- } _in_subtitle;
+ boost::shared_ptr<DecodedImageSubtitle> subtitle;
+ } _image_subtitle;
struct {
+ boost::weak_ptr<Piece> piece;
+ boost::shared_ptr<DecodedTextSubtitle> subtitle;
+ } _text_subtitle;
+
+ struct {
Position<int> position;
boost::shared_ptr<Image> image;
DCPTime from;
diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc
new file mode 100644
index 000000000..630681761
--- /dev/null
+++ b/src/lib/render_subtitles.cc
@@ -0,0 +1,150 @@
+/*
+ Copyright (C) 2014 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 <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (libdcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+ switch (v_align) {
+ case libdcp::TOP:
+ return (v_position / 100) * target_height - offset;
+ case libdcp::CENTER:
+ return (0.5 + v_position / 100) * target_height - offset;
+ case libdcp::BOTTOM:
+ return (1.0 - v_position / 100) * target_height - offset;
+ }
+
+ return 0;
+}
+
+void
+render_subtitles (list<libdcp::Subtitle> subtitles, libdcp::Size target, shared_ptr<Image>& image, Position<int>& position)
+{
+ if (subtitles.empty ()) {
+ image.reset ();
+ return;
+ }
+
+ /* Estimate height that the subtitle image needs to be */
+ optional<int> top;
+ optional<int> bottom;
+ for (list<libdcp::Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+ int const t = b - i->size() * target.height / (11 * 72);
+
+ top = min (top.get_value_or (t), t);
+ bottom = max (bottom.get_value_or (b), b);
+ }
+
+ top = top.get() - 32;
+ bottom = bottom.get() + 32;
+
+ image.reset (new Image (PIX_FMT_RGBA, libdcp::Size (target.width, bottom.get() - top.get ()), false));
+ image->make_black ();
+
+ Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+ image->data()[0],
+ Cairo::FORMAT_ARGB32,
+ image->size().width,
+ image->size().height,
+ Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+ );
+
+ Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+ Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+ layout->set_width (image->size().width * PANGO_SCALE);
+ layout->set_alignment (Pango::ALIGN_CENTER);
+
+ context->set_line_width (1);
+
+ for (list<libdcp::Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ string f = i->font ();
+ if (f.empty ()) {
+ f = "Arial";
+ }
+ Pango::FontDescription font (f);
+ font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+ if (i->italic ()) {
+ font.set_style (Pango::STYLE_ITALIC);
+ }
+ layout->set_font_description (font);
+ layout->set_text (i->text ());
+
+ /* Compute fade factor */
+ /* XXX */
+ float fade_factor = 1;
+#if 0
+ libdcp::Time now (time * 1000 / (4 * TIME_HZ));
+ libdcp::Time end_fade_up = i->in() + i->fade_up_time ();
+ libdcp::Time start_fade_down = i->out() - i->fade_down_time ();
+ if (now < end_fade_up) {
+ fade_factor = (now - i->in()) / i->fade_up_time();
+ } else if (now > start_fade_down) {
+ fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+ }
+#endif
+
+ layout->update_from_cairo_context (context);
+
+ /* Work out position */
+
+ int const x = 0;
+ int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+ if (i->effect() == libdcp::SHADOW) {
+ /* Drop-shadow effect */
+ libdcp::Color const ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ context->move_to (x + 4, y + 4);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+ }
+
+ /* The actual subtitle */
+ context->move_to (x, y);
+ libdcp::Color const c = i->color ();
+ context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+
+ if (i->effect() == libdcp::BORDER) {
+ /* Border effect */
+ context->move_to (x, y);
+ libdcp::Color ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->stroke ();
+ }
+ }
+}
+
diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h
new file mode 100644
index 000000000..22648387e
--- /dev/null
+++ b/src/lib/render_subtitles.h
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2014 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 <libdcp/subtitle_asset.h>
+#include <libdcp/util.h>
+#include "position.h"
+
+class Image;
+
+void
+render_subtitles (std::list<libdcp::Subtitle>, libdcp::Size, boost::shared_ptr<Image> &, Position<int> &);
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644
index 000000000..380a2ce2c
--- /dev/null
+++ b/src/lib/subrip.cc
@@ -0,0 +1,236 @@
+/*
+ Copyright (C) 2014 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 <boost/algorithm/string.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+ FILE* f = fopen_boost (content->path (0), "r");
+ if (!f) {
+ throw OpenFileError (content->path (0));
+ }
+
+ enum {
+ COUNTER,
+ METADATA,
+ CONTENT
+ } state = COUNTER;
+
+ char buffer[256];
+ int next_count = 1;
+
+ boost::optional<SubRipSubtitle> current;
+ list<string> lines;
+
+ while (!feof (f)) {
+ fgets (buffer, sizeof (buffer), f);
+ if (feof (f)) {
+ break;
+ }
+
+ string line (buffer);
+ trim_right_if (line, boost::is_any_of ("\n\r"));
+
+ switch (state) {
+ case COUNTER:
+ {
+ int x = 0;
+ try {
+ x = lexical_cast<int> (line);
+ } catch (...) {
+
+ }
+
+ if (x == next_count) {
+ state = METADATA;
+ ++next_count;
+ current = SubRipSubtitle ();
+ } else {
+ throw SubRipError (line, _("a subtitle count"), content->path (0));
+ }
+ }
+ break;
+ case METADATA:
+ {
+ vector<string> p;
+ boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+ if (p.size() != 3 && p.size() != 7) {
+ throw SubRipError (line, _("a time/position line"), content->path (0));
+ }
+
+ current->from = convert_time (p[0]);
+ current->to = convert_time (p[2]);
+
+ if (p.size() > 3) {
+ current->x1 = convert_coordinate (p[3]);
+ current->x2 = convert_coordinate (p[4]);
+ current->y1 = convert_coordinate (p[5]);
+ current->y2 = convert_coordinate (p[6]);
+ }
+ state = CONTENT;
+ break;
+ }
+ case CONTENT:
+ if (line.empty ()) {
+ state = COUNTER;
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ current.reset ();
+ lines.clear ();
+ } else {
+ lines.push_back (line);
+ }
+ break;
+ }
+ }
+
+ if (state == CONTENT) {
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ }
+
+ fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+ ContentTime r = 0;
+
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 3);
+ r += lexical_cast<int> (a[0]) * 60 * 60 * TIME_HZ;
+ r += lexical_cast<int> (a[1]) * 60 * TIME_HZ;
+
+ vector<string> b;
+ boost::algorithm::split (b, a[2], boost::is_any_of (","));
+ r += lexical_cast<int> (b[0]) * TIME_HZ;
+ r += lexical_cast<int> (b[1]) * TIME_HZ / 1000;
+
+ return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 2);
+ return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+ if (!p.text.empty ()) {
+ pieces.push_back (p);
+ p.text.clear ();
+ }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+ list<SubRipSubtitlePiece> pieces;
+
+ SubRipSubtitlePiece p;
+
+ enum {
+ TEXT,
+ TAG
+ } state = TEXT;
+
+ string tag;
+
+ /* XXX: missing <font> support */
+ /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+ not work, I think.
+ */
+
+ for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+ for (size_t j = 0; j < i->size(); ++j) {
+ switch (state) {
+ case TEXT:
+ if ((*i)[j] == '<' || (*i)[j] == '{') {
+ state = TAG;
+ } else {
+ p.text += (*i)[j];
+ }
+ break;
+ case TAG:
+ if ((*i)[j] == '>' || (*i)[j] == '}') {
+ if (tag == "b") {
+ maybe_content (pieces, p);
+ p.bold = true;
+ } else if (tag == "/b") {
+ maybe_content (pieces, p);
+ p.bold = false;
+ } else if (tag == "i") {
+ maybe_content (pieces, p);
+ p.italic = true;
+ } else if (tag == "/i") {
+ maybe_content (pieces, p);
+ p.italic = false;
+ } else if (tag == "u") {
+ maybe_content (pieces, p);
+ p.underline = true;
+ } else if (tag == "/u") {
+ maybe_content (pieces, p);
+ p.underline = false;
+ }
+ tag.clear ();
+ state = TEXT;
+ } else {
+ tag += (*i)[j];
+ }
+ break;
+ }
+ }
+ }
+
+ maybe_content (pieces, p);
+
+ return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+ if (_subtitles.empty ()) {
+ return 0;
+ }
+
+ return _subtitles.back().to;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644
index 000000000..e7d21675f
--- /dev/null
+++ b/src/lib/subrip.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2014 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.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+ SubRip (boost::shared_ptr<const SubRipContent>);
+
+ ContentTime length () const;
+
+protected:
+ std::vector<SubRipSubtitle> _subtitles;
+
+private:
+ friend class subrip_time_test;
+ friend class subrip_coordinate_test;
+ friend class subrip_content_test;
+ friend class subrip_parse_test;
+
+ static ContentTime convert_time (std::string);
+ static int convert_coordinate (std::string);
+ static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+ static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644
index 000000000..48d3528e1
--- /dev/null
+++ b/src/lib/subrip_content.cc
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2014 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 "subrip_content.h"
+#include "util.h"
+#include "subrip.h"
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using boost::shared_ptr;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+ : Content (film, path)
+ , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int)
+ : Content (film, node)
+ , SubtitleContent (film, node)
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job> job)
+{
+ Content::examine (job);
+ SubRip s (shared_from_this ());
+ boost::mutex::scoped_lock lm (_mutex);
+ _length = s.length ();
+}
+
+string
+SubRipContent::summary () const
+{
+ return path_summary() + " " + _("[subtitles]");
+}
+
+string
+SubRipContent::technical_summary () const
+{
+ return Content::technical_summary() + " - " + _("SubRip subtitles");
+}
+
+string
+SubRipContent::information () const
+{
+
+}
+
+void
+SubRipContent::as_xml (xmlpp::Node* node)
+{
+ node->add_child("Type")->add_child_text ("SubRip");
+ Content::as_xml (node);
+ SubtitleContent::as_xml (node);
+}
+
+DCPTime
+SubRipContent::full_length () const
+{
+ /* XXX: this assumes that the timing of the SubRip file is appropriate
+ for the DCP's frame rate.
+ */
+ return _length;
+}
+
+string
+SubRipContent::identifier () const
+{
+ LocaleGuard lg;
+
+ stringstream s;
+ s << Content::identifier()
+ << "_" << subtitle_scale()
+ << "_" << subtitle_offset();
+
+ return s.str ();
+}
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
new file mode 100644
index 000000000..6138c047e
--- /dev/null
+++ b/src/lib/subrip_content.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2014 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_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+ SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+ boost::shared_ptr<SubRipContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *);
+ DCPTime full_length () const;
+ std::string identifier () const;
+
+private:
+ DCPTime _length;
+};
diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc
new file mode 100644
index 000000000..aecee4e3e
--- /dev/null
+++ b/src/lib/subrip_decoder.cc
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2014 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 "subrip_decoder.h"
+
+using std::list;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const Film> film, shared_ptr<const SubRipContent> content)
+ : Decoder (film)
+ , SubtitleDecoder (film)
+ , SubRip (content)
+ , _next (0)
+{
+
+}
+
+bool
+SubRipDecoder::pass ()
+{
+ if (_next >= _subtitles.size ()) {
+ return true;
+ }
+
+ list<libdcp::Subtitle> out;
+ for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+ out.push_back (
+ libdcp::Subtitle (
+ "Arial",
+ i->italic,
+ libdcp::Color (255, 255, 255),
+ 72,
+ _subtitles[_next].from,
+ _subtitles[_next].to,
+ 0.9,
+ libdcp::BOTTOM,
+ i->text,
+ libdcp::NONE,
+ libdcp::Color (255, 255, 255),
+ 0,
+ 0
+ )
+ );
+ }
+
+ text_subtitle (out);
+ _next++;
+ return false;
+}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
new file mode 100644
index 000000000..26d5d5010
--- /dev/null
+++ b/src/lib/subrip_decoder.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2014 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.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_DECODER_H
+#define DCPOMATIC_SUBRIP_DECODER_H
+
+#include "subtitle_decoder.h"
+#include "subrip.h"
+
+class SubRipContent;
+
+class SubRipDecoder : public SubtitleDecoder, public SubRip
+{
+public:
+ SubRipDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SubRipContent>);
+
+ bool pass ();
+
+private:
+ size_t _next;
+};
+
+#endif
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
new file mode 100644
index 000000000..f730ee492
--- /dev/null
+++ b/src/lib/subrip_subtitle.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2014 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.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <libdcp/types.h>
+#include "types.h"
+
+struct SubRipSubtitlePiece
+{
+ SubRipSubtitlePiece ()
+ : bold (false)
+ , italic (false)
+ , underline (false)
+ {}
+
+ std::string text;
+ bool bold;
+ bool italic;
+ bool underline;
+ libdcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+ SubRipSubtitle ()
+ : from (0)
+ , to (0)
+ {}
+
+ ContentTime from;
+ ContentTime to;
+ boost::optional<int> x1;
+ boost::optional<int> x2;
+ boost::optional<int> y1;
+ boost::optional<int> y2;
+ std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc
index 7ba969933..e5cadb7b4 100644
--- a/src/lib/subtitle_decoder.cc
+++ b/src/lib/subtitle_decoder.cc
@@ -20,6 +20,7 @@
#include <boost/shared_ptr.hpp>
#include "subtitle_decoder.h"
+using std::list;
using boost::shared_ptr;
using boost::optional;
@@ -30,11 +31,17 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
}
-/** Called by subclasses when a subtitle is ready.
+/** Called by subclasses when an image subtitle is ready.
* Image may be 0 to say that there is no current subtitle.
*/
void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, ContentTime from, ContentTime to)
+SubtitleDecoder::image_subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, ContentTime from, ContentTime to)
{
- _pending.push_back (shared_ptr<DecodedSubtitle> (new DecodedSubtitle (image, rect, from, to)));
+ _pending.push_back (shared_ptr<DecodedImageSubtitle> (new DecodedImageSubtitle (image, rect, from, to)));
+}
+
+void
+SubtitleDecoder::text_subtitle (list<libdcp::Subtitle> s)
+{
+ _pending.push_back (shared_ptr<DecodedTextSubtitle> (new DecodedTextSubtitle (s)));
}
diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h
index fd1d71f33..82662d192 100644
--- a/src/lib/subtitle_decoder.h
+++ b/src/lib/subtitle_decoder.h
@@ -17,7 +17,11 @@
*/
+#ifndef DCPOMATIC_SUBTITLE_DECODER_H
+#define DCPOMATIC_SUBTITLE_DECODER_H
+
#include <boost/signals2.hpp>
+#include <libdcp/subtitle_asset.h>
#include "decoder.h"
#include "rect.h"
#include "types.h"
@@ -33,5 +37,8 @@ public:
SubtitleDecoder (boost::shared_ptr<const Film>);
protected:
- void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, ContentTime, ContentTime);
+ void image_subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, ContentTime, ContentTime);
+ void text_subtitle (std::list<libdcp::Subtitle>);
};
+
+#endif
diff --git a/src/lib/util.cc b/src/lib/util.cc
index a7dac9013..ef203c2bd 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -47,6 +47,7 @@
#include <openssl/md5.h>
#include <magick/MagickCore.h>
#include <magick/version.h>
+#include <pangomm/init.h>
#include <libdcp/version.h>
#include <libdcp/util.h>
#include <libdcp/signer_chain.h>
@@ -309,6 +310,7 @@ dcpomatic_setup ()
setenv ("LTDL_LIBRARY_PATH", lib.c_str (), 1);
#endif
+ Pango::init ();
libdcp::init ();
Ratio::setup_ratios ();
diff --git a/src/lib/wscript b/src/lib/wscript
index 81a55a160..8a20618c0 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -41,6 +41,7 @@ sources = """
player.cc
playlist.cc
ratio.cc
+ render_subtitles.cc
resampler.cc
scp_dcp_job.cc
scaler.cc
@@ -50,6 +51,9 @@ sources = """
sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
+ subrip.cc
+ subrip_content.cc
+ subrip_decoder.cc
subtitle_content.cc
subtitle_decoder.cc
timer.cc
@@ -76,7 +80,7 @@ def build(bld):
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
- CURL ZIP QUICKMAIL
+ CURL ZIP QUICKMAIL PANGOMM CAIROMM
"""
obj.source = sources + ' version.cc'