Initial work on SMPTE subtitles.
authorCarl Hetherington <cth@carlh.net>
Thu, 4 Jun 2015 11:25:48 +0000 (12:25 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 5 Jun 2015 14:53:48 +0000 (15:53 +0100)
15 files changed:
run/tests
src/dcp_time.cc
src/interop_subtitle_asset.cc
src/interop_subtitle_asset.h
src/mxf.cc
src/mxf.h
src/smpte_subtitle_asset.cc
src/smpte_subtitle_asset.h
src/subtitle_asset.cc
src/subtitle_asset.h
src/types.cc
src/types.h
test/rewrite_subs.cc
test/write_subtitle_test.cc
tools/dcpinfo.cc

index c4065dff3f60756e43350a3ab03f220959260940..8f12e53618b48a8fe0afc8a9f49feab8cc5e5007 100755 (executable)
--- a/run/tests
+++ b/run/tests
@@ -1,17 +1,17 @@
 #!/bin/bash -e
 #
-# Run our test suite, which (amongst other things)
-# builds a couple of DCPs.
-# The outputs are compared against the ones
-# in test/ref/DCP, and an error is given
-# if anything is different.
+# Run our test suite.
 
+# Private test data; this is stuff that is non-distributable
 private=../libdcp1-test-private
+# Work directory
 work=build/test
+# Path to dcpinfo tool
 dcpinfo=build/tools/dcpinfo
 
 export LD_LIBRARY_PATH=build/src:build/asdcplib/src:$LD_LIBRARY_PATH
 
+# Make sure we have the required tools
 for c in xmlsec1 xmldiff; do
   hash $c 2>/dev/null || { echo >&2 "$c required but not found; aborting"; exit 1; }
 done
@@ -63,6 +63,8 @@ if [ ! -e "$private/info.log" ]; then
 fi
 
 # Run dcpinfo on all the DCPs in private/metadata, writing $work/info.log
+# This writes details of the CPLs and all subtitle details, so it checks
+# if the code is reading subtitle files correctly.
 rm -f $work/info.log
 for d in `find $private/metadata -mindepth 1 -maxdepth 1 -type d | sort -f -d`; do
     if [ `basename $d` != ".git" ]; then
@@ -81,8 +83,9 @@ if [ "$?" != "0" ]; then
     exit 1
 fi
 
-# Copy test/private into build/ then re-write the subtitles of every DCP using 
+# Copy $private into build/ then re-write the subtitles of every DCP using 
 # $work/rewrite_subs.  This tests round-trip of subtitle reading/writing.
+# Note that all the subs in $private/metadata are Interop.
 rm -f $work/info2.log
 rm -rf $work/private
 mkdir $work/private
@@ -104,8 +107,12 @@ if [ "$?" != "0" ]; then
     exit 1
 fi
 
-# Dump the subs of JourneyToJah... (which has MXF-wrapped subtitles)
+# Dump the subs of JourneyToJah... (which has MXF-wrapped SMPTE subtitles)
 # and check that they are right
 $dcpinfo -s $private/data/JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV >> $work/jah.log
 
+# Rewrite the subs of JourneyToJah... 
+#cp -r $private/data/JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV $work/
+#$work/rewrite_subs $WORK/Jou
+
 echo "PASS"
index d34804e981ba3f4cd19e2cd7960a04041694b7fd..5d9bed81b4deacb80669d5ee34c1b8b1fa547c7f 100644 (file)
@@ -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_)
 {
index a6bdfcafab41bef83d56a7603097d1d496f65229..528b7d90376ca3a74ee9d71e72ad5bf291399869 100644 (file)
 #include "xml.h"
 #include "raw_convert.h"
 #include "font_node.h"
+#include "util.h"
 #include <libxml++/libxml++.h>
 #include <boost/foreach.hpp>
 #include <cmath>
+#include <cstdio>
 
 using std::list;
 using std::string;
@@ -40,7 +42,8 @@ InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file)
        shared_ptr<cxml::Document> 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<dcp::InteropLoadFontNode> (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<SubtitleString> sorted = _subtitles;
-       sorted.sort (SubtitleSorter ());
-
-       /* XXX: script, underlined, weight not supported */
-
-       optional<string> 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<SubtitleString>::iterator i = sorted.begin(); i != sorted.end(); ++i) {
-
-               /* We will start a new <Font>...</Font> whenever some font property changes.
-                  I suppose we should really make an optimal hierarchy of <Font> 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<string> (size));
-                       if (fabs (aspect_adjust - 1.0) > ASPECT_ADJUST_EPSILON) {
-                               font_element->set_attribute ("AspectAdjust", raw_convert<string> (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<string> (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<string> (i->fade_up_time().as_editable_units(250)));
-                       subtitle_element->set_attribute ("FadeDownTime", raw_convert<string> (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<string> (i->h_position() * 100, 6));
-               }
-               text->set_attribute ("VAlign", valign_to_string (i->v_align()));                
-               text->set_attribute ("VPosition", raw_convert<string> (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;
+}
index 586bb67c541211eeffc0b7d5ca4b6465d3bd4ca9..2a1fae7f7300a66feb30ab1a81a0e93d5f51b1d7 100644 (file)
@@ -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<boost::shared_ptr<InteropLoadFontNode> > _load_font_nodes;
 };
index 80a351e60d19b72360fb8b40a3dec10d45b82908..0cde395c1ca92752ff0505a85b0ec0e5ed5f72b4 100644 (file)
@@ -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;
index a9f1bfa3b7fe4f94f058d2a4e5243d87d9b4e462..60c635be2c0c7a874431769b2d675eb168521a83 100644 (file)
--- 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 */
index e5a083b187a93209ae50537ad9e497f7d6b1cdf5..40afc76db9c83daf7f100e2c4bbf14c1b13819ed 100644 (file)
 #include "xml.h"
 #include "AS_DCP.h"
 #include "KM_util.h"
+#include "raw_convert.h"
+#include <libxml++/libxml++.h>
 #include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
 
 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<dcp::SMPTELoadFontNode> (xml, "LoadFont");
 
-       int tcr = xml->number_child<int> ("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<int> ("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<string> er_parts;
+       split (er_parts, er, is_any_of (" "));
+       if (er_parts.size() == 1) {
+               _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
+       } else if (er_parts.size() == 2) {
+               _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
+       } else {
+               throw XMLError ("malformed EditRate " + er);
+       }
+
+       _time_code_rate = xml->number_child<int> ("TimeCodeRate");
+       if (xml->optional_string_child ("StartTime")) {
+               _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
+       }
 
        shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
 
        list<cxml::NodePtr> f = subtitle_list->node_children ("Font");
        list<shared_ptr<dcp::FontNode> > font_nodes;
        BOOST_FOREACH (cxml::NodePtr& i, f) {
-               font_nodes.push_back (shared_ptr<FontNode> (new FontNode (i, tcr)));
+               font_nodes.push_back (shared_ptr<FontNode> (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<string> (_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<string> (_time_code_rate));
+       if (_start_time) {
+               root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string ());
+       }
+
+       BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> 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<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
+{
+       /* XXX */
+       return false;
+}
+
index 639c8eb721e3bad29b406a06e938d65eafe714fe..5f4d8833137251d2fc1ae299f4580772ac14821d 100644 (file)
 */
 
 #include "subtitle_asset.h"
+#include "local_time.h"
+#include "mxf.h"
 #include <boost/filesystem.hpp>
 
 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<const Asset>,
+               EqualityOptions,
+               NoteHandler note
+               ) const;
+       
        std::list<boost::shared_ptr<LoadFontNode> > 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<std::string> 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<std::string> _annotation_text;
+       LocalTime _issue_date;
+       boost::optional<int> _reel_number;
+       boost::optional<std::string> _language;
+       Fraction _edit_rate;
+       int _time_code_rate;
+       boost::optional<Time> _start_time;
+       
        std::list<boost::shared_ptr<SMPTELoadFontNode> > _load_font_nodes;
 };
 
index f1ecc9392c45778a62036c30f13a24e0bc8c1149..1c341de49d309369d0ac89a85e6d2c27dfcccd12 100644 (file)
@@ -43,14 +43,12 @@ using boost::dynamic_pointer_cast;
 using namespace dcp;
 
 SubtitleAsset::SubtitleAsset ()
-       : _reel_number ("1")
 {
 
 }
 
 SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
        : Asset (file)
-       , _reel_number ("1")
 {
 
 }
@@ -58,13 +56,7 @@ SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
 void
 SubtitleAsset::parse_common (shared_ptr<cxml::Document> xml, list<shared_ptr<dcp::FontNode> > font_nodes)
 {
-       _reel_number = xml->string_child ("ReelNumber");
-       _language = xml->string_child ("Language");
-
-       /* Now make Subtitle objects to represent the raw XML nodes
-          in a sane way.
-       */
-
+       /* Make Subtitle objects to represent the raw XML nodes in a sane way */
        ParseState parse_state;
        examine_font_nodes (xml, font_nodes, parse_state);
 }
@@ -169,21 +161,6 @@ SubtitleAsset::add (SubtitleString s)
        _subtitles.push_back (s);
 }
 
-void
-SubtitleAsset::write_xml (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;
-}
-
 Time
 SubtitleAsset::latest_subtitle_out () const
 {
@@ -209,16 +186,6 @@ SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions opti
                return false;
        }
 
-       if (_reel_number != other->_reel_number) {
-               note (DCP_ERROR, "subtitle reel numbers differ");
-               return false;
-       }
-
-       if (_language != other->_language) {
-               note (DCP_ERROR, "subtitle languages differ");
-               return false;
-       }
-
        if (_subtitles != other->_subtitles) {
                note (DCP_ERROR, "subtitles differ");
                return false;
@@ -226,3 +193,115 @@ SubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions opti
 
        return true;
 }
+
+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();
+       }
+};
+
+void
+SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, string xmlns) const
+{
+       list<SubtitleString> sorted = _subtitles;
+       sorted.sort (SubtitleSorter ());
+
+       /* XXX: script, underlined, weight not supported */
+
+       optional<string> 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<SubtitleString>::iterator i = sorted.begin(); i != sorted.end(); ++i) {
+
+               /* We will start a new <Font>...</Font> whenever some font property changes.
+                  I suppose we should really make an optimal hierarchy of <Font> 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", xmlns);
+                       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<string> (size));
+                       if (fabs (aspect_adjust - 1.0) > ASPECT_ADJUST_EPSILON) {
+                               font_element->set_attribute ("AspectAdjust", raw_convert<string> (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", xmlns);
+                       subtitle_element->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
+                       subtitle_element->set_attribute ("TimeIn", i->in().rebase(time_code_rate).as_string());
+                       subtitle_element->set_attribute ("TimeOut", i->out().rebase(time_code_rate).as_string());
+                       subtitle_element->set_attribute ("FadeUpTime", raw_convert<string> (i->fade_up_time().as_editable_units(time_code_rate)));
+                       subtitle_element->set_attribute ("FadeDownTime", raw_convert<string> (i->fade_down_time().as_editable_units(time_code_rate)));
+
+                       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", xmlns);
+               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<string> (i->h_position() * 100, 6));
+               }
+               text->set_attribute ("VAlign", valign_to_string (i->v_align()));                
+               text->set_attribute ("VPosition", raw_convert<string> (i->v_position() * 100, 6));
+               text->add_child_text (i->text());
+       }
+}
+
+       
index deb72ece8059a738ff121240981b047401c05bbe..c8100d58b1503939aa841a3ab4313dfbf06536e3 100644 (file)
 #include "subtitle_string.h"
 #include <libcxml/cxml.h>
 
+namespace xmlpp {
+       class Element;
+}
+
 namespace dcp
 {
 
@@ -49,10 +53,6 @@ public:
                NoteHandler note
                ) const;
 
-       std::string language () const {
-               return _language;
-       }
-
        std::list<SubtitleString> subtitles_during (Time from, Time to) const;
        std::list<SubtitleString> const & subtitles () const {
                return _subtitles;
@@ -60,11 +60,8 @@ public:
 
        void add (SubtitleString);
 
-       void write_xml (boost::filesystem::path) const;
-       virtual Glib::ustring xml_as_string () const {
-               /* XXX: this should be pure virtual when SMPTE writing is implemented */
-               return "";
-       }
+       virtual void write (boost::filesystem::path) const = 0;
+       virtual Glib::ustring xml_as_string () const = 0;
 
        Time latest_subtitle_out () const;
 
@@ -73,18 +70,14 @@ public:
 protected:
        void parse_common (boost::shared_ptr<cxml::Document> xml, std::list<boost::shared_ptr<FontNode> > font_nodes);
        
-       std::string pkl_type (Standard) const {
-               return "text/xml";
-       }
+       virtual std::string pkl_type (Standard) const = 0;
 
        std::string asdcp_kind () const {
                return "Subtitle";
        }
-       
-       /* strangely, this is sometimes a string */
-       std::string _reel_number;
-       std::string _language;
 
+       void subtitles_as_xml (xmlpp::Element* root, int time_code_rate, std::string xmlns) const;
+       
        std::list<SubtitleString> _subtitles;
 
 private:
index e86e999c6a9006cdaf8627a30aa9faea3887f44a..376667019ab150f0d821bf202e37d5ef6dd06adc 100644 (file)
@@ -20,6 +20,7 @@
 #include "raw_convert.h"
 #include "types.h"
 #include "exceptions.h"
+#include "compose.hpp"
 #include <boost/algorithm/string.hpp>
 #include <vector>
 #include <cstdio>
@@ -43,6 +44,12 @@ Fraction::Fraction (string s)
        denominator = raw_convert<int> (b[1]);
 }
 
+string
+Fraction::as_string () const
+{
+       return String::compose ("%1 %2", numerator, denominator);
+}
+
 bool
 dcp::operator== (Fraction const & a, Fraction const & b)
 {
index 626666b4263f6e22217c45c263cf42f040b60a35..ca7603abdbad9d5df1480d49edcf7dd9bd9b421e 100644 (file)
@@ -139,6 +139,8 @@ public:
                return float (numerator) / denominator;
        }
 
+       std::string as_string () const;
+
        int numerator;
        int denominator;
 };
index e62b9169f9baab3977f6a22b7952e31b16e68779..5bfe126f862a11d131bad2beb2feaab1de1b65e9 100644 (file)
@@ -51,7 +51,7 @@ main (int argc, char* argv[])
                        for (list<shared_ptr<Reel> >::iterator j = reels.begin(); j != reels.end(); ++j) {
                                
                                if ((*j)->main_subtitle()) {
-                                       (*j)->main_subtitle()->subtitle_asset()->write_xml ((*j)->main_subtitle()->subtitle_asset()->file ());
+                                       (*j)->main_subtitle()->subtitle_asset()->write ((*j)->main_subtitle()->subtitle_asset()->file ());
                                }
                        }
                }
index b02a6e59648e61d9bfb41cab05edb6adbe4ebbbc..958fc981653b1cd613f9a459a2e429383e21683c 100644 (file)
@@ -30,6 +30,7 @@ using boost::shared_ptr;
 BOOST_AUTO_TEST_CASE (write_subtitle_test)
 {
        dcp::InteropSubtitleAsset c ("Test", "EN");
+       c.set_reel_number ("1");
 
        c.add (
                dcp::SubtitleString (
@@ -83,12 +84,12 @@ BOOST_AUTO_TEST_CASE (write_subtitle_test)
                "  <ReelNumber>1</ReelNumber>\n"
                "  <Language>EN</Language>\n"
                "  <Font Id=\"Frutiger\" Italic=\"no\" Color=\"FFFFFFFF\" Size=\"48\" Effect=\"none\" EffectColor=\"FF000000\" Script=\"normal\" Underlined=\"no\" Weight=\"normal\">\n"
-               "    <Subtitle SpotNumber=\"1\" TimeIn=\"00:04:09:022\" TimeOut=\"00:04:11:022\" FadeUpTime=\"0\" FadeDownTime=\"0\">\n"
+               "    <Subtitle SpotNumber=\"1\" TimeIn=\"00:04:09:229\" TimeOut=\"00:04:11:229\" FadeUpTime=\"0\" FadeDownTime=\"0\">\n"
                "      <Text VAlign=\"top\" VPosition=\"80\">Hello world</Text>\n"
                "    </Subtitle>\n"
                "  </Font>\n"
                "  <Font Italic=\"yes\" Color=\"FF800040\" Size=\"91\" Effect=\"border\" EffectColor=\"FF010203\" Script=\"normal\" Underlined=\"no\" Weight=\"normal\">\n"
-               "    <Subtitle SpotNumber=\"2\" TimeIn=\"05:41:00:021\" TimeOut=\"06:12:15:021\" FadeUpTime=\"930790\" FadeDownTime=\"4591830\">\n"
+               "    <Subtitle SpotNumber=\"2\" TimeIn=\"05:41:00:219\" TimeOut=\"06:12:15:219\" FadeUpTime=\"930790\" FadeDownTime=\"4591830\">\n"
                "      <Text VAlign=\"bottom\" VPosition=\"40\">What's going on</Text>\n"
                "    </Subtitle>\n"
                "  </Font>\n"
index 965088c20d4b89e29e8dcb50ee3818dcbd836b0f..32920be24f566bfaf98c264fc7d4b5a794e6c05d 100644 (file)
@@ -27,6 +27,8 @@
 #include "reel_sound_asset.h"
 #include "reel_subtitle_asset.h"
 #include "subtitle_string.h"
+#include "interop_subtitle_asset.h"
+#include "smpte_subtitle_asset.h"
 #include "cpl.h"
 #include "common.h"
 #include <getopt.h>
@@ -40,6 +42,7 @@ using std::cerr;
 using std::cout;
 using std::list;
 using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
 using namespace dcp;
 
 static void
@@ -81,7 +84,15 @@ main_subtitle (shared_ptr<Reel> reel, bool list_subtitles)
        }
        
        list<SubtitleString> subs = reel->main_subtitle()->subtitle_asset()->subtitles ();
-       cout << "      Subtitle: " << subs.size() << " subtitles in " << reel->main_subtitle()->subtitle_asset()->language() << "\n";
+       cout << "      Subtitle: " << subs.size() << " subtitles";
+       shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (reel->main_subtitle()->subtitle_asset());
+       if (iop) {
+               cout << " in " << iop->language() << "\n";
+       }
+       shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (reel->main_subtitle()->subtitle_asset());
+       if (smpte && smpte->language ()) {
+               cout << " in " << smpte->language().get() << "\n";
+       }
        if (list_subtitles) {
                BOOST_FOREACH (SubtitleString const& k, subs) {
                        cout << k << "\n";