From: Carl Hetherington Date: Wed, 15 Jan 2014 21:23:33 +0000 (+0000) Subject: Merge 1.0-seek and subtitle-content. X-Git-Tag: v2.0.48~922 X-Git-Url: https://git.carlh.net/gitweb/?a=commitdiff_plain;h=8353a009aae1a604251c0160193c39741c2fa27c;hp=4e5d5c7dcc6470b8dc918d03a00e30c07df60efe;p=dcpomatic.git Merge 1.0-seek and subtitle-content. --- 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 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_factory (shared_ptr film, boost::filesystem::path path) { shared_ptr 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 #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 im, dcpomatic::Rect r, ContentTime f, ContentTime t) + DecodedImageSubtitle (boost::shared_ptr im, dcpomatic::Rect 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 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 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 (), dcpomatic::Rect (), 0, 0); + image_subtitle (shared_ptr (), dcpomatic::Rect (), 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 (sub.pts ) / AV_TIME_BASE) + _pts_offset; + double const packet_time = (static_cast (sub.pts) / AV_TIME_BASE) + _pts_offset; -======= - double const packet_time = (static_cast (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 ( static_cast (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 dv = dynamic_pointer_cast (earliest_decoded); shared_ptr da = dynamic_pointer_cast (earliest_decoded); - shared_ptr ds = dynamic_pointer_cast (earliest_decoded); + shared_ptr dis = dynamic_pointer_cast (earliest_decoded); + shared_ptr dts = dynamic_pointer_cast (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; optional 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 best_overlap; + for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { + shared_ptr vc = dynamic_pointer_cast (*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 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 fc = dynamic_pointer_cast (*i); if (fc) { decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio)); frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } - + + /* ImageContent */ shared_ptr ic = dynamic_pointer_cast (*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 sc = dynamic_pointer_cast (*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 best_overlap; - for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { - shared_ptr vc = dynamic_pointer_cast (*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 rc = dynamic_pointer_cast (*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 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 = _in_subtitle.piece.lock (); + shared_ptr 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 sc = dynamic_pointer_cast (piece->content); assert (sc); - dcpomatic::Rect in_rect = _in_subtitle.subtitle->rect; + dcpomatic::Rect 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 vc = dynamic_pointer_cast (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. @@ -698,13 +701,23 @@ Player::repeat_last_video () return true; } +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 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 #include #include +#include #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, boost::shared_ptr); void emit_audio (boost::weak_ptr, boost::shared_ptr); void step_video_position (boost::shared_ptr); @@ -178,9 +180,14 @@ private: struct { boost::weak_ptr piece; - boost::shared_ptr subtitle; - } _in_subtitle; + boost::shared_ptr subtitle; + } _image_subtitle; + struct { + boost::weak_ptr piece; + boost::shared_ptr subtitle; + } _text_subtitle; + struct { Position position; boost::shared_ptr image; 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 + + 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 +#include +#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 subtitles, libdcp::Size target, shared_ptr& image, Position& position) +{ + if (subtitles.empty ()) { + image.reset (); + return; + } + + /* Estimate height that the subtitle image needs to be */ + optional top; + optional bottom; + for (list::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 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 context = Cairo::Context::create (surface); + Glib::RefPtr 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::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 + + 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 +#include +#include "position.h" + +class Image; + +void +render_subtitles (std::list, libdcp::Size, boost::shared_ptr &, Position &); 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 + + 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 +#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 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 current; + list 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 (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 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 a; + boost::algorithm::split (a, t, boost::is_any_of (":")); + assert (a.size() == 3); + r += lexical_cast (a[0]) * 60 * 60 * TIME_HZ; + r += lexical_cast (a[1]) * 60 * TIME_HZ; + + vector b; + boost::algorithm::split (b, a[2], boost::is_any_of (",")); + r += lexical_cast (b[0]) * TIME_HZ; + r += lexical_cast (b[1]) * TIME_HZ / 1000; + + return r; +} + +int +SubRip::convert_coordinate (string t) +{ + vector a; + boost::algorithm::split (a, t, boost::is_any_of (":")); + assert (a.size() == 2); + return lexical_cast (a[1]); +} + +void +SubRip::maybe_content (list& pieces, SubRipSubtitlePiece& p) +{ + if (!p.text.empty ()) { + pieces.push_back (p); + p.text.clear (); + } +} + +list +SubRip::convert_content (list t) +{ + list pieces; + + SubRipSubtitlePiece p; + + enum { + TEXT, + TAG + } state = TEXT; + + string tag; + + /* XXX: missing support */ + /* XXX: nesting of tags e.g. foobarbazfredjim might + not work, I think. + */ + + for (list::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 + + 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); + + ContentTime length () const; + +protected: + std::vector _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 convert_content (std::list); + static void maybe_content (std::list &, 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 + + 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 film, boost::filesystem::path path) + : Content (film, path) + , SubtitleContent (film, path) +{ + +} + +SubRipContent::SubRipContent (shared_ptr film, shared_ptr node, int) + : Content (film, node) + , SubtitleContent (film, node) +{ + +} + +void +SubRipContent::examine (boost::shared_ptr 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 + + 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, boost::filesystem::path); + SubRipContent (boost::shared_ptr, boost::shared_ptr, int); + + boost::shared_ptr shared_from_this () { + return boost::dynamic_pointer_cast (Content::shared_from_this ()); + } + + void examine (boost::shared_ptr); + 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 + + 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 film, shared_ptr content) + : Decoder (film) + , SubtitleDecoder (film) + , SubRip (content) + , _next (0) +{ + +} + +bool +SubRipDecoder::pass () +{ + if (_next >= _subtitles.size ()) { + return true; + } + + list out; + for (list::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 + + 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, boost::shared_ptr); + + 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 + + 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 +#include +#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 x1; + boost::optional x2; + boost::optional y1; + boost::optional y2; + std::list 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 #include "subtitle_decoder.h" +using std::list; using boost::shared_ptr; using boost::optional; @@ -30,11 +31,17 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr 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, dcpomatic::Rect rect, ContentTime from, ContentTime to) +SubtitleDecoder::image_subtitle (shared_ptr image, dcpomatic::Rect rect, ContentTime from, ContentTime to) { - _pending.push_back (shared_ptr (new DecodedSubtitle (image, rect, from, to))); + _pending.push_back (shared_ptr (new DecodedImageSubtitle (image, rect, from, to))); +} + +void +SubtitleDecoder::text_subtitle (list s) +{ + _pending.push_back (shared_ptr (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 +#include #include "decoder.h" #include "rect.h" #include "types.h" @@ -33,5 +37,8 @@ public: SubtitleDecoder (boost::shared_ptr); protected: - void subtitle (boost::shared_ptr, dcpomatic::Rect, ContentTime, ContentTime); + void image_subtitle (boost::shared_ptr, dcpomatic::Rect, ContentTime, ContentTime); + void text_subtitle (std::list); }; + +#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 #include #include +#include #include #include #include @@ -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' diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 45c423355..f246e99e9 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -841,7 +841,7 @@ FilmEditor::setup_content_sensitivity () _video_panel->Enable (video_selection.size() > 0 && _generally_sensitive); _audio_panel->Enable (audio_selection.size() > 0 && _generally_sensitive); - _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast (selection.front()) && _generally_sensitive); + _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast (selection.front()) && _generally_sensitive); _timing_panel->Enable (selection.size() == 1 && _generally_sensitive); } diff --git a/test/ratio_test.cc b/test/ratio_test.cc index 082a6ff11..f3cbb504f 100644 --- a/test/ratio_test.cc +++ b/test/ratio_test.cc @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE (ratio_test) Ratio const * r = Ratio::from_id ("119"); BOOST_CHECK (r); - BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1285, 1080)); + BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1290, 1080)); r = Ratio::from_id ("133"); BOOST_CHECK (r); diff --git a/test/subrip_test.cc b/test/subrip_test.cc new file mode 100644 index 000000000..12a77c1e1 --- /dev/null +++ b/test/subrip_test.cc @@ -0,0 +1,196 @@ +/* + Copyright (C) 2014 Carl Hetherington + + 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 +#include +#include "lib/subrip.h" +#include "lib/subrip_content.h" +#include "lib/subrip_decoder.h" +#include "lib/render_subtitles.h" +#include "test.h" + +using std::list; +using std::vector; +using std::string; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +/** Test SubRip::convert_time */ +BOOST_AUTO_TEST_CASE (subrip_time_test) +{ + BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), rint (((3 * 60) + 10 + 0.5) * TIME_HZ)); + BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), rint (((4 * 3600) + (19 * 60) + 51 + 0.782) * TIME_HZ)); +} + +/** Test SubRip::convert_coordinate */ +BOOST_AUTO_TEST_CASE (subrip_coordinate_test) +{ + BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("foo:42"), 42); + BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("X1:999"), 999); +} + +/** Test SubRip::convert_content */ +BOOST_AUTO_TEST_CASE (subrip_content_test) +{ + list c; + list p; + + c.push_back ("Hello world"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + c.clear (); + + c.push_back ("Hello world"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + BOOST_CHECK_EQUAL (p.front().bold, true); + c.clear (); + + c.push_back ("Hello world"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + BOOST_CHECK_EQUAL (p.front().italic, true); + c.clear (); + + c.push_back ("Hello world"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + BOOST_CHECK_EQUAL (p.front().underline, true); + c.clear (); + + c.push_back ("{b}Hello world{/b}"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + BOOST_CHECK_EQUAL (p.front().bold, true); + c.clear (); + + c.push_back ("{i}Hello world{/i}"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + BOOST_CHECK_EQUAL (p.front().italic, true); + c.clear (); + + c.push_back ("{u}Hello world{/u}"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 1); + BOOST_CHECK_EQUAL (p.front().text, "Hello world"); + BOOST_CHECK_EQUAL (p.front().underline, true); + c.clear (); + + c.push_back ("This is nesting of subtitles"); + p = SubRip::convert_content (c); + BOOST_CHECK_EQUAL (p.size(), 3); + list::iterator i = p.begin (); + BOOST_CHECK_EQUAL (i->text, "This is "); + BOOST_CHECK_EQUAL (i->bold, true); + BOOST_CHECK_EQUAL (i->italic, false); + ++i; + BOOST_CHECK_EQUAL (i->text, "nesting"); + BOOST_CHECK_EQUAL (i->bold, true); + BOOST_CHECK_EQUAL (i->italic, true); + ++i; + BOOST_CHECK_EQUAL (i->text, " of subtitles"); + BOOST_CHECK_EQUAL (i->bold, true); + BOOST_CHECK_EQUAL (i->italic, false); + ++i; + c.clear (); +} + +/** Test parsing of full SubRip file content */ +BOOST_AUTO_TEST_CASE (subrip_parse_test) +{ + shared_ptr content (new SubRipContent (shared_ptr (), "test/data/subrip.srt")); + content->examine (shared_ptr ()); + BOOST_CHECK_EQUAL (content->full_length(), ((3 * 60) + 56.471) * TIME_HZ); + + SubRip s (content); + + vector::const_iterator i = s._subtitles.begin(); + + BOOST_CHECK (i != s._subtitles.end ()); + BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 49.200) * TIME_HZ); + BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 52.351) * TIME_HZ); + BOOST_CHECK_EQUAL (i->pieces.size(), 1); + BOOST_CHECK_EQUAL (i->pieces.front().text, "This is a subtitle, and it goes over two lines."); + + ++i; + BOOST_CHECK (i != s._subtitles.end ()); + BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 52.440) * TIME_HZ); + BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 54.351) * TIME_HZ); + BOOST_CHECK_EQUAL (i->pieces.size(), 1); + BOOST_CHECK_EQUAL (i->pieces.front().text, "We have emboldened this"); + BOOST_CHECK_EQUAL (i->pieces.front().bold, true); + + ++i; + BOOST_CHECK (i != s._subtitles.end ()); + BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 54.440) * TIME_HZ); + BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 56.590) * TIME_HZ); + BOOST_CHECK_EQUAL (i->pieces.size(), 1); + BOOST_CHECK_EQUAL (i->pieces.front().text, "And italicised this."); + BOOST_CHECK_EQUAL (i->pieces.front().italic, true); + + ++i; + BOOST_CHECK (i != s._subtitles.end ()); + BOOST_CHECK_EQUAL (i->from, ((1 * 60) + 56.680) * TIME_HZ); + BOOST_CHECK_EQUAL (i->to, ((1 * 60) + 58.955) * TIME_HZ); + BOOST_CHECK_EQUAL (i->pieces.size(), 1); + BOOST_CHECK_EQUAL (i->pieces.front().text, "Shall I compare thee to a summers' day?"); + + ++i; + BOOST_CHECK (i != s._subtitles.end ()); + BOOST_CHECK_EQUAL (i->from, ((2 * 60) + 0.840) * TIME_HZ); + BOOST_CHECK_EQUAL (i->to, ((2 * 60) + 3.400) * TIME_HZ); + BOOST_CHECK_EQUAL (i->pieces.size(), 1); + BOOST_CHECK_EQUAL (i->pieces.front().text, "Is this a dagger I see before me?"); + + ++i; + BOOST_CHECK (i != s._subtitles.end ()); + BOOST_CHECK_EQUAL (i->from, ((3 * 60) + 54.560) * TIME_HZ); + BOOST_CHECK_EQUAL (i->to, ((3 * 60) + 56.471) * TIME_HZ); + BOOST_CHECK_EQUAL (i->pieces.size(), 1); + BOOST_CHECK_EQUAL (i->pieces.front().text, "Hello world."); + + ++i; + BOOST_CHECK (i == s._subtitles.end ()); +} + +/** Test rendering of a SubRip subtitle */ +BOOST_AUTO_TEST_CASE (subrip_render_test) +{ + shared_ptr content (new SubRipContent (shared_ptr (), "test/data/subrip.srt")); + content->examine (shared_ptr ()); + BOOST_CHECK_EQUAL (content->full_length(), ((3 * 60) + 56.471) * TIME_HZ); + + shared_ptr film = new_test_film ("subrip_render_test"); + + shared_ptr decoder (new SubRipDecoder (film, content)); + shared_ptr dts = dynamic_pointer_cast (decoder->peek ()); + + shared_ptr image; + Position position; + render_subtitles (dts->subs, libdcp::Size (1998, 1080), image, position); + write_image (image, "build/test/subrip_render_test.png"); + check_file ("build/test/subrip_render_test.png", "test/data/subrip_render_test.png"); +} diff --git a/test/test.cc b/test/test.cc index c0f732c46..f9d761da3 100644 --- a/test/test.cc +++ b/test/test.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include #include "lib/config.h" @@ -29,6 +30,7 @@ #include "lib/job.h" #include "lib/cross.h" #include "lib/server_finder.h" +#include "lib/image.h" #define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE dcpomatic_test #include @@ -95,7 +97,7 @@ new_test_film (string name) return f; } -static void +void check_file (boost::filesystem::path ref, boost::filesystem::path check) { uintmax_t N = boost::filesystem::file_size (ref); @@ -231,3 +233,12 @@ wait_for_jobs () ui_signaller->ui_idle (); } + +void +write_image (shared_ptr image, boost::filesystem::path file) +{ + using namespace MagickCore; + + Magick::Image m (image->size().width, image->size().height, "ARGB", CharPixel, (void *) image->data()[0]); + m.write (file.string ()); +} diff --git a/test/test.h b/test/test.h index c58296665..b6347a5ca 100644 --- a/test/test.h +++ b/test/test.h @@ -20,9 +20,12 @@ #include class Film; +class Image; extern void wait_for_jobs (); extern boost::shared_ptr new_test_film (std::string); extern void check_dcp (boost::filesystem::path, boost::filesystem::path); +extern void check_file (boost::filesystem::path ref, boost::filesystem::path check); extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list); extern boost::filesystem::path test_film_dir (std::string); +extern void write_image (boost::shared_ptr image, boost::filesystem::path file); diff --git a/test/wscript b/test/wscript index a35a6cbf5..de9e9f25a 100644 --- a/test/wscript +++ b/test/wscript @@ -44,6 +44,7 @@ def build(bld): silence_padding_test.cc skip_frame_test.cc stream_test.cc + subrip_test.cc test.cc threed_test.cc util_test.cc diff --git a/wscript b/wscript index d72252459..772db9293 100644 --- a/wscript +++ b/wscript @@ -145,6 +145,8 @@ def configure(conf): conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True) conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True) conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True) + conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True) + conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True) conf.check_cxx(fragment=""" #include \n