From 42a65cba0d8da23c12af52015e66cd9dc0b5a5fa Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 4 Jun 2015 12:25:48 +0100 Subject: Initial work on SMPTE subtitles. --- src/dcp_time.cc | 1 + src/interop_subtitle_asset.cc | 127 ++++++------------------------------ src/interop_subtitle_asset.h | 33 ++++++++++ src/mxf.cc | 2 +- src/mxf.h | 2 +- src/smpte_subtitle_asset.cc | 109 ++++++++++++++++++++++++++++++- src/smpte_subtitle_asset.h | 33 +++++++++- src/subtitle_asset.cc | 147 ++++++++++++++++++++++++++++++++---------- src/subtitle_asset.h | 25 +++---- src/types.cc | 7 ++ src/types.h | 2 + 11 files changed, 326 insertions(+), 162 deletions(-) (limited to 'src') diff --git a/src/dcp_time.cc b/src/dcp_time.cc index d34804e9..5d9bed81 100644 --- a/src/dcp_time.cc +++ b/src/dcp_time.cc @@ -66,6 +66,7 @@ Time::set (double seconds, int tcr_) } } +/** @param time String of the form HH:MM:SS:EE */ Time::Time (string time, int tcr_) : tcr (tcr_) { diff --git a/src/interop_subtitle_asset.cc b/src/interop_subtitle_asset.cc index a6bdfcaf..528b7d90 100644 --- a/src/interop_subtitle_asset.cc +++ b/src/interop_subtitle_asset.cc @@ -22,9 +22,11 @@ #include "xml.h" #include "raw_convert.h" #include "font_node.h" +#include "util.h" #include #include #include +#include using std::list; using std::string; @@ -40,7 +42,8 @@ InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file) shared_ptr xml (new cxml::Document ("DCSubtitle")); xml->read_file (file); _id = xml->string_child ("SubtitleID"); - + _reel_number = xml->string_child ("ReelNumber"); + _language = xml->string_child ("Language"); _movie_title = xml->string_child ("MovieTitle"); _load_font_nodes = type_children (xml, "LoadFont"); @@ -59,15 +62,6 @@ InteropSubtitleAsset::InteropSubtitleAsset (string movie_title, string language) _language = language; } -struct SubtitleSorter { - bool operator() (SubtitleString const & a, SubtitleString const & b) { - if (a.in() != b.in()) { - return a.in() < b.in(); - } - return a.v_position() < b.v_position(); - } -}; - Glib::ustring InteropSubtitleAsset::xml_as_string () const { @@ -86,102 +80,7 @@ InteropSubtitleAsset::xml_as_string () const load_font->set_attribute ("URI", (*i)->uri); } - list sorted = _subtitles; - sorted.sort (SubtitleSorter ()); - - /* XXX: script, underlined, weight not supported */ - - optional font; - bool italic = false; - Colour colour; - int size = 0; - float aspect_adjust = 1.0; - Effect effect = NONE; - Colour effect_colour; - int spot_number = 1; - Time last_in; - Time last_out; - Time last_fade_up_time; - Time last_fade_down_time; - - xmlpp::Element* font_element = 0; - xmlpp::Element* subtitle_element = 0; - - for (list::iterator i = sorted.begin(); i != sorted.end(); ++i) { - - /* We will start a new ... whenever some font property changes. - I suppose we should really make an optimal hierarchy of tags, but - that seems hard. - */ - - bool const font_changed = - font != i->font() || - italic != i->italic() || - colour != i->colour() || - size != i->size() || - fabs (aspect_adjust - i->aspect_adjust()) > ASPECT_ADJUST_EPSILON || - effect != i->effect() || - effect_colour != i->effect_colour(); - - if (font_changed) { - font = i->font (); - italic = i->italic (); - colour = i->colour (); - size = i->size (); - aspect_adjust = i->aspect_adjust (); - effect = i->effect (); - effect_colour = i->effect_colour (); - } - - if (!font_element || font_changed) { - font_element = root->add_child ("Font"); - if (font) { - font_element->set_attribute ("Id", font.get ()); - } - font_element->set_attribute ("Italic", italic ? "yes" : "no"); - font_element->set_attribute ("Color", colour.to_argb_string()); - font_element->set_attribute ("Size", raw_convert (size)); - if (fabs (aspect_adjust - 1.0) > ASPECT_ADJUST_EPSILON) { - font_element->set_attribute ("AspectAdjust", raw_convert (aspect_adjust)); - } - font_element->set_attribute ("Effect", effect_to_string (effect)); - font_element->set_attribute ("EffectColor", effect_colour.to_argb_string()); - font_element->set_attribute ("Script", "normal"); - font_element->set_attribute ("Underlined", "no"); - font_element->set_attribute ("Weight", "normal"); - } - - if (!subtitle_element || font_changed || - (last_in != i->in() || - last_out != i->out() || - last_fade_up_time != i->fade_up_time() || - last_fade_down_time != i->fade_down_time() - )) { - - subtitle_element = font_element->add_child ("Subtitle"); - subtitle_element->set_attribute ("SpotNumber", raw_convert (spot_number++)); - subtitle_element->set_attribute ("TimeIn", i->in().as_string()); - subtitle_element->set_attribute ("TimeOut", i->out().as_string()); - subtitle_element->set_attribute ("FadeUpTime", raw_convert (i->fade_up_time().as_editable_units(250))); - subtitle_element->set_attribute ("FadeDownTime", raw_convert (i->fade_down_time().as_editable_units(250))); - - last_in = i->in (); - last_out = i->out (); - last_fade_up_time = i->fade_up_time (); - last_fade_down_time = i->fade_down_time (); - } - - xmlpp::Element* text = subtitle_element->add_child ("Text"); - if (i->h_align() != HALIGN_CENTER) { - text->set_attribute ("HAlign", halign_to_string (i->h_align ())); - } - if (i->h_position() > ALIGN_EPSILON) { - text->set_attribute ("HPosition", raw_convert (i->h_position() * 100, 6)); - } - text->set_attribute ("VAlign", valign_to_string (i->v_align())); - text->set_attribute ("VPosition", raw_convert (i->v_position() * 100, 6)); - text->add_child_text (i->text()); - } + subtitles_as_xml (root, 250, ""); return doc.write_to_string_formatted ("UTF-8"); } @@ -237,3 +136,19 @@ InteropSubtitleAsset::load_font_nodes () const copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf)); return lf; } + +/** Write this content to an XML file */ +void +InteropSubtitleAsset::write (boost::filesystem::path p) const +{ + FILE* f = fopen_boost (p, "w"); + if (!f) { + throw FileError ("Could not open file for writing", p, -1); + } + + Glib::ustring const s = xml_as_string (); + fwrite (s.c_str(), 1, s.bytes(), f); + fclose (f); + + _file = p; +} diff --git a/src/interop_subtitle_asset.h b/src/interop_subtitle_asset.h index 586bb67c..2a1fae7f 100644 --- a/src/interop_subtitle_asset.h +++ b/src/interop_subtitle_asset.h @@ -41,8 +41,41 @@ public: void add_font (std::string id, std::string uri); Glib::ustring xml_as_string () const; + void write (boost::filesystem::path path) const; + + void set_reel_number (std::string n) { + _reel_number = n; + } + + void set_language (std::string l) { + _language = l; + } + + void set_movie_title (std::string m) { + _movie_title = m; + } + + std::string reel_number () const { + return _reel_number; + } + + std::string language () const { + return _language; + } + + std::string movie_title () const { + return _movie_title; + } + +protected: + + std::string pkl_type (Standard) const { + return "text/xml"; + } private: + std::string _reel_number; + std::string _language; std::string _movie_title; std::list > _load_font_nodes; }; diff --git a/src/mxf.cc b/src/mxf.cc index 80a351e6..0cde395c 100644 --- a/src/mxf.cc +++ b/src/mxf.cc @@ -54,7 +54,7 @@ MXF::~MXF () } void -MXF::fill_writer_info (ASDCP::WriterInfo* writer_info, string id, Standard standard) +MXF::fill_writer_info (ASDCP::WriterInfo* writer_info, string id, Standard standard) const { writer_info->ProductVersion = _metadata.product_version; writer_info->CompanyName = _metadata.company_name; diff --git a/src/mxf.h b/src/mxf.h index a9f1bfa3..60c635be 100644 --- a/src/mxf.h +++ b/src/mxf.h @@ -95,7 +95,7 @@ protected: * @param w struct to fill in. * @param standard INTEROP or SMPTE. */ - void fill_writer_info (ASDCP::WriterInfo* w, std::string id, Standard standard); + void fill_writer_info (ASDCP::WriterInfo* w, std::string id, Standard standard) const; ASDCP::AESDecContext* _decryption_context; /** ID of the key used for encryption/decryption, if there is one */ diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index e5a083b1..40afc76d 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -24,13 +24,19 @@ #include "xml.h" #include "AS_DCP.h" #include "KM_util.h" +#include "raw_convert.h" +#include #include +#include using std::string; using std::list; using std::stringstream; using std::cout; +using std::vector; using boost::shared_ptr; +using boost::split; +using boost::is_any_of; using namespace dcp; SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file, bool mxf) @@ -64,16 +70,37 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file, bool mxf) _load_font_nodes = type_children (xml, "LoadFont"); - int tcr = xml->number_child ("TimeCodeRate"); + _content_title_text = xml->string_child ("ContentTitleText"); + _annotation_text = xml->optional_string_child ("AnnotationText"); + _issue_date = LocalTime (xml->string_child ("IssueDate")); + _reel_number = xml->optional_number_child ("ReelNumber"); + _language = xml->optional_string_child ("Language"); + + /* This is supposed to be two numbers, but a single number has been seen in the wild */ + string const er = xml->string_child ("EditRate"); + vector er_parts; + split (er_parts, er, is_any_of (" ")); + if (er_parts.size() == 1) { + _edit_rate = Fraction (raw_convert (er_parts[0]), 1); + } else if (er_parts.size() == 2) { + _edit_rate = Fraction (raw_convert (er_parts[0]), raw_convert (er_parts[1])); + } else { + throw XMLError ("malformed EditRate " + er); + } + + _time_code_rate = xml->number_child ("TimeCodeRate"); + if (xml->optional_string_child ("StartTime")) { + _start_time = Time (xml->string_child ("StartTime"), _time_code_rate); + } shared_ptr subtitle_list = xml->optional_node_child ("SubtitleList"); list f = subtitle_list->node_children ("Font"); list > font_nodes; BOOST_FOREACH (cxml::NodePtr& i, f) { - font_nodes.push_back (shared_ptr (new FontNode (i, tcr))); + font_nodes.push_back (shared_ptr (new FontNode (i, _time_code_rate))); } - + parse_common (xml, font_nodes); } @@ -92,3 +119,79 @@ SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file) Kumu::Result_t r = reader.OpenRead (file.string().c_str ()); return !ASDCP_FAILURE (r); } + +Glib::ustring +SMPTESubtitleAsset::xml_as_string () const +{ + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel"); + root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst"); + root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs"); + + root->add_child("ID", "dcst")->add_child_text (_id); + root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text); + if (_annotation_text) { + root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ()); + } + root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true)); + if (_reel_number) { + root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert (_reel_number.get ())); + } + if (_language) { + root->add_child("Language", "dcst")->add_child_text (_language.get ()); + } + root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ()); + root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert (_time_code_rate)); + if (_start_time) { + root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string ()); + } + + BOOST_FOREACH (shared_ptr i, _load_font_nodes) { + xmlpp::Element* load_font = root->add_child("LoadFont", "dcst"); + load_font->add_child_text (i->urn); + load_font->set_attribute ("ID", i->id); + } + + subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, "dcst"); + + return doc.write_to_string_formatted ("UTF-8"); +} + +/** Write this content to a MXF file */ +void +SMPTESubtitleAsset::write (boost::filesystem::path p) const +{ + ASDCP::WriterInfo writer_info; + fill_writer_info (&writer_info, _id, SMPTE); + + ASDCP::TimedText::TimedTextDescriptor descriptor; + descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator); + descriptor.EncodingName = "UTF-8"; + descriptor.ResourceList.clear (); + descriptor.NamespaceName = "dcst"; + memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen); + descriptor.ContainerDuration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); + + /* XXX: fonts into descriptor? */ + + ASDCP::TimedText::MXFWriter writer; + Kumu::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor); + if (ASDCP_FAILURE (r)) { + boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r)); + } + + /* XXX: no encryption */ + writer.WriteTimedTextResource (xml_as_string ()); + + writer.Finalize (); + + _file = p; +} + +bool +SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions options, NoteHandler note) const +{ + /* XXX */ + return false; +} + diff --git a/src/smpte_subtitle_asset.h b/src/smpte_subtitle_asset.h index 639c8eb7..5f4d8833 100644 --- a/src/smpte_subtitle_asset.h +++ b/src/smpte_subtitle_asset.h @@ -18,13 +18,15 @@ */ #include "subtitle_asset.h" +#include "local_time.h" +#include "mxf.h" #include namespace dcp { class SMPTELoadFontNode; -class SMPTESubtitleAsset : public SubtitleAsset +class SMPTESubtitleAsset : public SubtitleAsset, public MXF { public: /** @param file File name @@ -32,11 +34,40 @@ public: */ SMPTESubtitleAsset (boost::filesystem::path file, bool mxf = true); + bool equals ( + boost::shared_ptr, + EqualityOptions, + NoteHandler note + ) const; + std::list > load_font_nodes () const; + Glib::ustring xml_as_string () const; + void write (boost::filesystem::path path) const; + + /** @return language, if one was specified */ + boost::optional language () const { + return _language; + } + static bool valid_mxf (boost::filesystem::path); + +protected: + + std::string pkl_type (Standard) const { + return "application/mxf"; + } private: + std::string _content_title_text; + boost::optional _annotation_text; + LocalTime _issue_date; + boost::optional _reel_number; + boost::optional _language; + Fraction _edit_rate; + int _time_code_rate; + boost::optional