diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/content_factory.cc | 8 | ||||
| -rw-r--r-- | src/lib/decoded.h | 33 | ||||
| -rw-r--r-- | src/lib/exceptions.cc | 8 | ||||
| -rw-r--r-- | src/lib/exceptions.h | 7 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.cc | 11 | ||||
| -rw-r--r-- | src/lib/player.cc | 127 | ||||
| -rw-r--r-- | src/lib/player.h | 13 | ||||
| -rw-r--r-- | src/lib/render_subtitles.cc | 150 | ||||
| -rw-r--r-- | src/lib/render_subtitles.h | 27 | ||||
| -rw-r--r-- | src/lib/subrip.cc | 236 | ||||
| -rw-r--r-- | src/lib/subrip.h | 53 | ||||
| -rw-r--r-- | src/lib/subrip_content.cc | 99 | ||||
| -rw-r--r-- | src/lib/subrip_content.h | 42 | ||||
| -rw-r--r-- | src/lib/subrip_decoder.cc | 65 | ||||
| -rw-r--r-- | src/lib/subrip_decoder.h | 39 | ||||
| -rw-r--r-- | src/lib/subrip_subtitle.h | 58 | ||||
| -rw-r--r-- | src/lib/subtitle_decoder.cc | 13 | ||||
| -rw-r--r-- | src/lib/subtitle_decoder.h | 9 | ||||
| -rw-r--r-- | src/lib/util.cc | 2 | ||||
| -rw-r--r-- | src/lib/wscript | 6 |
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' |
