Merge master.
authorCarl Hetherington <cth@carlh.net>
Wed, 14 May 2014 08:33:23 +0000 (09:33 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 14 May 2014 08:33:23 +0000 (09:33 +0100)
12 files changed:
1  2 
src/asset.cc
src/cpl.cc
src/dcp.cc
src/dcp_time.cc
src/font.cc
src/mxf.cc
src/raw_convert.h
src/reel_asset.cc
src/subtitle.cc
src/subtitle_content.cc
src/types.cc
src/wscript

diff --cc src/asset.cc
index aaa79dc75660bd2877f52254a6c952e0bf386f7f,00ad67bc5a17dfc46e8a8bc495552eb78a40a4c7..6bf8fec5dcc1fc70dc65596d8df381e9b5d859b3
  */
  
  /** @file  src/asset.cc
 - *  @brief Parent class for assets of DCPs.
 + *  @brief Asset class.
   */
  
 -#include <iostream>
 -#include <boost/filesystem.hpp>
 -#include <boost/function.hpp>
 -#include <libxml++/nodes/element.h>
 -#include "AS_DCP.h"
 -#include "KM_util.h"
++#include "raw_convert.h"
  #include "asset.h"
  #include "util.h"
 -#include "metadata.h"
 +#include "exceptions.h"
  #include "compose.hpp"
 -#include "raw_convert.h"
 +#include <libxml++/libxml++.h>
- #include <boost/lexical_cast.hpp>
  
  using std::string;
- using boost::lexical_cast;
 -using boost::shared_ptr;
 -using namespace libdcp;
 -
 -Asset::Asset (boost::filesystem::path directory, boost::filesystem::path file_name)
 -      : _directory (directory)
 -      , _file_name (file_name)
 -      , _uuid (make_uuid ())
 -      , _edit_rate (0)
 -      , _entry_point (0)
 -      , _intrinsic_duration (0)
 -      , _duration (0)
 +using boost::function;
 +using boost::optional;
 +using namespace dcp;
 +
 +/** Create an Asset with a randomly-generated ID */
 +Asset::Asset ()
  {
 -      if (_file_name.empty ()) {
 -              _file_name = _uuid + ".xml";
 -      }
 +
 +}
 +
 +/** Create an Asset from a given file.  The ID will
 + *  be extracted from the file.
 + *  @param file File name.
 + */
 +Asset::Asset (boost::filesystem::path file)
 +      : _file (file)
 +{
 +
 +}
 +
 +/** Create an Asset with a specified ID.
 + *  @param id ID to use.
 + */
 +Asset::Asset (string id)
 +      : Object (id)
 +{
 +
  }
  
  void
 -Asset::write_to_pkl (xmlpp::Node* node, bool interop) const
 +Asset::write_to_pkl (xmlpp::Node* node, Standard standard) const
  {
 +      assert (!_file.empty ());
 +      
        xmlpp::Node* asset = node->add_child ("Asset");
 -      asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
 -      asset->add_child("AnnotationText")->add_child_text (_file_name.string ());
 -      asset->add_child("Hash")->add_child_text (digest ());
 -      asset->add_child("Size")->add_child_text (raw_convert<string> (boost::filesystem::file_size(path())));
 -      if (interop) {
 -              asset->add_child("Type")->add_child_text (String::compose ("application/x-smpte-mxf;asdcpKind=%1", asdcp_kind ()));
 -      } else {
 -              asset->add_child("Type")->add_child_text ("application/mxf");
 -      }
 +      asset->add_child("Id")->add_child_text ("urn:uuid:" + _id);
 +      asset->add_child("AnnotationText")->add_child_text (_id);
 +      asset->add_child("Hash")->add_child_text (hash ());
-       asset->add_child("Size")->add_child_text (lexical_cast<string> (boost::filesystem::file_size (_file)));
++      asset->add_child("Size")->add_child_text (raw_convert<string> (boost::filesystem::file_size (_file)));
 +      asset->add_child("Type")->add_child_text (pkl_type (standard));
  }
  
  void
 -Asset::write_to_assetmap (xmlpp::Node* node) const
 +Asset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const
  {
 +      assert (!_file.empty ());
 +
        xmlpp::Node* asset = node->add_child ("Asset");
 -      asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
 +      asset->add_child("Id")->add_child_text ("urn:uuid:" + _id);
        xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
        xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
 -      chunk->add_child("Path")->add_child_text (_file_name.string ());
 +      optional<boost::filesystem::path> path = relative_to_root (root, _file);
 +      if (!path) {
 +              throw MiscError (String::compose ("Asset %1 is not within the directory %2", _file, root));
 +      }
 +      chunk->add_child("Path")->add_child_text (path.get().string ());
        chunk->add_child("VolumeIndex")->add_child_text ("1");
        chunk->add_child("Offset")->add_child_text ("0");
-       chunk->add_child("Length")->add_child_text (lexical_cast<string> (boost::filesystem::file_size (_file)));
 -      chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size(path())));
 -}
 -
 -boost::filesystem::path
 -Asset::path () const
 -{
 -      boost::filesystem::path p;
 -      p /= _directory;
 -      p /= _file_name;
 -      return p;
++      chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (_file)));
  }
  
  string
diff --cc src/cpl.cc
index 612ff4796a340cbf79779b367f8d3803aeb66f4a,5079b4f632b3e95b4484d19564853ff299b43d4d..fcfa9efac37e5006b799ca6672d672d228df2197
  
  */
  
 -#include <libxml/parser.h>
++#include "raw_convert.h"
  #include "cpl.h"
 -#include "parse/cpl.h"
  #include "util.h"
 -#include "mono_picture_asset.h"
 -#include "stereo_picture_asset.h"
 -#include "sound_asset.h"
 -#include "subtitle_asset.h"
 -#include "parse/asset_map.h"
 +#include "mono_picture_mxf.h"
 +#include "stereo_picture_mxf.h"
 +#include "sound_mxf.h"
 +#include "subtitle_content.h"
  #include "reel.h"
  #include "metadata.h"
  #include "signer.h"
@@@ -42,63 -40,153 +43,62 @@@ using std::list
  using std::pair;
  using std::make_pair;
  using boost::shared_ptr;
--using boost::lexical_cast;
  using boost::optional;
 -using namespace libdcp;
 +using namespace dcp;
  
 -CPL::CPL (boost::filesystem::path directory, string name, ContentKind content_kind, int length, int frames_per_second)
 -      : _directory (directory)
 -      , _name (name)
 +CPL::CPL (string annotation_text, ContentKind content_kind)
 +      : _annotation_text (annotation_text)
 +      /* default _content_title_text to _annotation_text */
 +      , _content_title_text (annotation_text)
        , _content_kind (content_kind)
 -      , _length (length)
 -      , _fps (frames_per_second)
 +      , _content_version_id ("urn:uuid:" + make_uuid ())
  {
 -      _id = make_uuid ();
 +      /* default _content_version_id to and _content_version_label to
 +         a random ID and the current time.
 +      */
 +      _content_version_id = "urn:uuid:" + make_uuid() + LocalTime().as_string ();
 +      _content_version_label_text = _content_version_id;
  }
  
 -/** Construct a CPL object from a XML file.
 - *  @param directory The directory containing this CPL's DCP.
 - *  @param file The CPL XML filename.
 - *  @param asset_maps AssetMaps to look for assets in.
 - *  @param require_mxfs true to throw an exception if a required MXF file does not exist.
 - */
 -CPL::CPL (boost::filesystem::path directory, string file, list<PathAssetMap> asset_maps, bool require_mxfs)
 -      : _directory (directory)
 +/** Construct a CPL object from a XML file */
 +CPL::CPL (boost::filesystem::path file)
 +      : Asset (file)
        , _content_kind (FEATURE)
 -      , _length (0)
 -      , _fps (0)
  {
 -      /* Read the XML */
 -      shared_ptr<parse::CPL> cpl;
 -      try {
 -              cpl.reset (new parse::CPL (file));
 -      } catch (FileError& e) {
 -              boost::throw_exception (FileError ("could not load CPL file", file, e.number ()));
 -      }
 -      
 -      /* Now cherry-pick the required bits into our own data structure */
 -      
 -      _name = cpl->annotation_text;
 -      _content_kind = cpl->content_kind;
 -
 -      /* Trim urn:uuid: off the front */
 -      _id = cpl->id.substr (9);
 -
 -      for (list<shared_ptr<parse::Reel> >::iterator i = cpl->reels.begin(); i != cpl->reels.end(); ++i) {
 -
 -              shared_ptr<parse::Picture> p;
 -
 -              if ((*i)->asset_list->main_picture) {
 -                      p = (*i)->asset_list->main_picture;
 -              } else {
 -                      p = (*i)->asset_list->main_stereoscopic_picture;
 -              }
 -              
 -              _fps = p->edit_rate.numerator;
 -              _length += p->duration;
 -
 -              shared_ptr<PictureAsset> picture;
 -              shared_ptr<SoundAsset> sound;
 -              shared_ptr<SubtitleAsset> subtitle;
 -
 -              /* Some rather twisted logic to decide if we are 3D or not;
 -                 some DCPs give a MainStereoscopicPicture to indicate 3D, others
 -                 just have a FrameRate twice the EditRate and apparently
 -                 expect you to divine the fact that they are hence 3D.
 -              */
 -
 -              if (!(*i)->asset_list->main_stereoscopic_picture && p->edit_rate == p->frame_rate) {
 +      cxml::Document f ("CompositionPlaylist");
 +      f.read_file (file);
  
 -                      try {
 -                              pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, p->id);
 -
 -                              picture.reset (new MonoPictureAsset (asset.first, asset.second->chunks.front()->path));
 -
 -                              picture->read ();
 -                              picture->set_edit_rate (_fps);
 -                              picture->set_entry_point (p->entry_point);
 -                              picture->set_duration (p->duration);
 -                              if (p->key_id.length() > 9) {
 -                                      /* Trim urn:uuid: */
 -                                      picture->set_key_id (p->key_id.substr (9));
 -                              }
 -                      } catch (MXFFileError) {
 -                              if (require_mxfs) {
 -                                      throw;
 -                              }
 -                      }
 -                      
 -              } else {
 -                      try {
 -                              pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, p->id);
 -
 -                              picture.reset (new StereoPictureAsset (asset.first, asset.second->chunks.front()->path));
 -
 -                              picture->read ();
 -                              picture->set_edit_rate (_fps);
 -                              picture->set_entry_point (p->entry_point);
 -                              picture->set_duration (p->duration);
 -                              if (p->key_id.length() > 9) {
 -                                      /* Trim urn:uuid: */
 -                                      picture->set_key_id (p->key_id.substr (9));
 -                              }
 -                              
 -                      } catch (MXFFileError) {
 -                              if (require_mxfs) {
 -                                      throw;
 -                              }
 -                      }
 -                      
 -              }
 -              
 -              if ((*i)->asset_list->main_sound) {
 -                      
 -                      try {
 -                              pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, (*i)->asset_list->main_sound->id);
 -                      
 -                              sound.reset (new SoundAsset (asset.first, asset.second->chunks.front()->path));
 -                              shared_ptr<parse::MainSound> s = (*i)->asset_list->main_sound;
 -
 -                              sound->read ();
 -                              sound->set_entry_point (s->entry_point);
 -                              sound->set_duration (s->duration);
 -                              if (s->key_id.length() > 9) {
 -                                      /* Trim urn:uuid: */
 -                                      sound->set_key_id (s->key_id.substr (9));
 -                              }
 -                      } catch (MXFFileError) {
 -                              if (require_mxfs) {
 -                                      throw;
 -                              }
 -                      }
 -              }
 -
 -              if ((*i)->asset_list->main_subtitle) {
 -                      
 -                      pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, (*i)->asset_list->main_subtitle->id);
 +      _id = f.string_child ("Id");
 +      if (_id.length() > 9) {
 +              _id = _id.substr (9);
 +      }
 +      _annotation_text = f.optional_string_child ("AnnotationText").get_value_or ("");
 +      _metadata.issuer = f.optional_string_child ("Issuer").get_value_or ("");
 +      _metadata.creator = f.optional_string_child ("Creator").get_value_or ("");
 +      _metadata.issue_date = f.string_child ("IssueDate");
 +      _content_title_text = f.string_child ("ContentTitleText");
 +      _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
 +      shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
 +      if (content_version) {
 +              _content_version_id = content_version->optional_string_child ("Id").get_value_or ("");
 +              _content_version_label_text = content_version->string_child ("LabelText");
 +              content_version->done ();
 +      }
 +      f.ignore_child ("RatingList");
 +      _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
  
 -                      subtitle.reset (new SubtitleAsset (asset.first, asset.second->chunks.front()->path));
 +      f.ignore_child ("Issuer");
 +      f.ignore_child ("Signer");
 +      f.ignore_child ("Signature");
  
 -                      subtitle->set_entry_point ((*i)->asset_list->main_subtitle->entry_point);
 -                      subtitle->set_duration ((*i)->asset_list->main_subtitle->duration);
 -              }
 -                      
 -              _reels.push_back (shared_ptr<Reel> (new Reel (picture, sound, subtitle)));
 -      }
 +      f.done ();
  }
  
 +/** Add a reel to this CPL.
 + *  @param reel Reel to add.
 + */
  void
 -CPL::add_reel (shared_ptr<Reel> reel)
 +CPL::add (boost::shared_ptr<Reel> reel)
  {
        _reels.push_back (reel);
  }
diff --cc src/dcp.cc
index 9374bffdda62647ab81adcdf03a72935e7f0bf53,7cce30feb31c77ab44558690310a5965fc434f27..9d3cd72c33e0635ca7c483c9a7e5c94c64196824
  */
  
  /** @file  src/dcp.cc
 - *  @brief A class to create a DCP.
 + *  @brief DCP class.
   */
  
 -#include <sstream>
 -#include <iomanip>
 -#include <cassert>
 -#include <iostream>
 -#include <boost/filesystem.hpp>
 -#include <boost/algorithm/string.hpp>
 -#include <libxml++/libxml++.h>
 -#include <xmlsec/xmldsig.h>
 -#include <xmlsec/app.h>
++#include "raw_convert.h"
  #include "dcp.h"
 -#include "asset.h"
 -#include "sound_asset.h"
 -#include "picture_asset.h"
 -#include "subtitle_asset.h"
 +#include "sound_mxf.h"
 +#include "picture_mxf.h"
 +#include "subtitle_content.h"
 +#include "mono_picture_mxf.h"
 +#include "stereo_picture_mxf.h"
  #include "util.h"
  #include "metadata.h"
  #include "exceptions.h"
  #include "reel.h"
  #include "cpl.h"
  #include "signer.h"
 -#include "kdm.h"
 -#include "raw_convert.h"
 +#include "compose.hpp"
 +#include "AS_DCP.h"
 +#include "decrypted_kdm.h"
 +#include "decrypted_kdm_key.h"
 +#include <xmlsec/xmldsig.h>
 +#include <xmlsec/app.h>
 +#include <libxml++/libxml++.h>
 +#include <boost/filesystem.hpp>
 +#include <boost/algorithm/string.hpp>
- #include <boost/lexical_cast.hpp>
 +#include <sstream>
 +#include <iomanip>
 +#include <cassert>
 +#include <iostream>
  
  using std::string;
  using std::list;
  using std::stringstream;
  using std::ostream;
 -using std::copy;
 -using std::back_inserter;
  using std::make_pair;
 +using std::map;
 +using std::cout;
 +using std::exception;
  using boost::shared_ptr;
- using boost::lexical_cast;
 -using namespace libdcp;
 +using boost::algorithm::starts_with;
 +using namespace dcp;
  
  DCP::DCP (boost::filesystem::path directory)
        : _directory (directory)
@@@ -372,10 -189,15 +371,10 @@@ DCP::write_assetmap (Standard standard
        chunk->add_child("Path")->add_child_text (pkl_uuid + "_pkl.xml");
        chunk->add_child("VolumeIndex")->add_child_text ("1");
        chunk->add_child("Offset")->add_child_text ("0");
-       chunk->add_child("Length")->add_child_text (lexical_cast<string> (pkl_length));
+       chunk->add_child("Length")->add_child_text (raw_convert<string> (pkl_length));
        
 -      for (list<shared_ptr<CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
 -              (*i)->write_to_assetmap (asset_list);
 -      }
 -
 -      list<shared_ptr<const Asset> > a = assets ();
 -      for (list<shared_ptr<const Asset> >::const_iterator i = a.begin(); i != a.end(); ++i) {
 -              (*i)->write_to_assetmap (asset_list);
 +      for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
 +              (*i)->write_to_assetmap (asset_list, _directory);
        }
  
        /* This must not be the _formatted version otherwise signature digests will be wrong */
diff --cc src/dcp_time.cc
index 5376b9724e08a9f7ddef6907ec7fd05cd4edf0c4,d597e3dc203e765e10e2d20ec9331a7e0de637f1..a3822ed08ea578edf9c704613ccb046d8102829d
  */
  
  /** @file  src/dcp_time.cc
 - *  @brief A representation of time within a DCP.
 + *  @brief Time class.
   */
  
- #include <boost/lexical_cast.hpp>
++#include "raw_convert.h"
 +#include "dcp_time.h"
 +#include "exceptions.h"
 +#include <boost/algorithm/string.hpp>
  #include <iostream>
  #include <vector>
 -#include <boost/algorithm/string.hpp>
  #include <cmath>
 -#include "dcp_time.h"
 -#include "exceptions.h"
 -#include "raw_convert.h"
  
  using namespace std;
  using namespace boost;
diff --cc src/font.cc
index 52996a73ec470ce776315fa81f832d1aa3916882,0000000000000000000000000000000000000000..51bd866e435cbc73dc27fe8b4637a27564bf2a15
mode 100644,000000..100644
--- /dev/null
@@@ -1,81 -1,0 +1,83 @@@
 +/*
 +    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
++#include "types.h"
++#include "raw_convert.h"
 +#include "font.h"
 +#include "xml.h"
 +#include "text.h"
 +#include <libcxml/cxml.h>
 +
 +using std::string;
 +using std::list;
 +using boost::shared_ptr;
 +using boost::optional;
 +using namespace dcp;
 +
 +Font::Font (boost::shared_ptr<const cxml::Node> node)
 +{
 +      text = node->content ();
 +      
 +      id = node->optional_string_attribute ("Id").get_value_or ("");
 +      size = node->optional_number_attribute<int64_t> ("Size").get_value_or (0);
 +      italic = node->optional_bool_attribute ("Italic");
 +      optional<string> c = node->optional_string_attribute ("Color");
 +      if (c) {
 +              color = Color (c.get ());
 +      }
 +      optional<string> const e = node->optional_string_attribute ("Effect");
 +      if (e) {
 +              effect = string_to_effect (e.get ());
 +      }
 +      c = node->optional_string_attribute ( "EffectColor");
 +      if (c) {
 +              effect_color = Color (c.get ());
 +      }
 +      subtitle_nodes = type_children<Subtitle> (node, "Subtitle");
 +      font_nodes = type_children<Font> (node, "Font");
 +      text_nodes = type_children<Text> (node, "Text");
 +}
 +
 +Font::Font (std::list<boost::shared_ptr<Font> > const & font_nodes)
 +      : size (0)
 +      , italic (false)
 +      , color ("FFFFFFFF")
 +      , effect_color ("FFFFFFFF")
 +{
 +      for (list<shared_ptr<Font> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
 +              if (!(*i)->id.empty ()) {
 +                      id = (*i)->id;
 +              }
 +              if ((*i)->size != 0) {
 +                      size = (*i)->size;
 +              }
 +              if ((*i)->italic) {
 +                      italic = (*i)->italic.get ();
 +              }
 +              if ((*i)->color) {
 +                      color = (*i)->color.get ();
 +              }
 +              if ((*i)->effect) {
 +                      effect = (*i)->effect.get ();
 +              }
 +              if ((*i)->effect_color) {
 +                      effect_color = (*i)->effect_color.get ();
 +              }
 +      }
 +}
diff --cc src/mxf.cc
index d73c590bd556ad1abd4a98389650d1a0e1e2ad07,0000000000000000000000000000000000000000..dd4d2efcbb1b87c57636674c186650a82bd38f12
mode 100644,000000..100644
--- /dev/null
@@@ -1,166 -1,0 +1,167 @@@
 +/*
 +    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +/** @file  src/asset.cc
 + *  @brief Parent class for assets of DCPs made up of MXF files.
 + */
 +
++#include "raw_convert.h"
 +#include "AS_DCP.h"
 +#include "KM_prng.h"
 +#include "KM_util.h"
 +#include "mxf.h"
 +#include "util.h"
 +#include "metadata.h"
 +#include "exceptions.h"
 +#include "compose.hpp"
 +#include <libxml++/nodes/element.h>
 +#include <boost/filesystem.hpp>
 +#include <iostream>
 +
 +using std::string;
 +using std::list;
 +using std::pair;
 +using boost::shared_ptr;
 +using boost::dynamic_pointer_cast;
 +using namespace dcp;
 +
 +MXF::MXF (Fraction edit_rate)
 +      : Content (edit_rate)
 +      , _encryption_context (0)
 +      , _decryption_context (0)
 +{
 +
 +}
 +
 +MXF::MXF (boost::filesystem::path file)
 +      : Content (file)
 +      , _encryption_context (0)
 +      , _decryption_context (0)
 +{
 +
 +}
 +
 +MXF::~MXF ()
 +{
 +      delete _encryption_context;
 +      delete _decryption_context;
 +}
 +
 +void
 +MXF::fill_writer_info (ASDCP::WriterInfo* writer_info, Standard standard)
 +{
 +      writer_info->ProductVersion = _metadata.product_version;
 +      writer_info->CompanyName = _metadata.company_name;
 +      writer_info->ProductName = _metadata.product_name.c_str();
 +
 +      if (standard == INTEROP) {
 +              writer_info->LabelSetType = ASDCP::LS_MXF_INTEROP;
 +      } else {
 +              writer_info->LabelSetType = ASDCP::LS_MXF_SMPTE;
 +      }
 +      unsigned int c;
 +      Kumu::hex2bin (_id.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c);
 +      assert (c == Kumu::UUID_Length);
 +
 +      if (_key) {
 +              Kumu::GenRandomUUID (writer_info->ContextID);
 +              writer_info->EncryptedEssence = true;
 +
 +              unsigned int c;
 +              Kumu::hex2bin (_key_id.c_str(), writer_info->CryptographicKeyID, Kumu::UUID_Length, &c);
 +              assert (c == Kumu::UUID_Length);
 +      }
 +}
 +
 +bool
 +MXF::equals (shared_ptr<const Content> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const
 +{
 +      if (!Content::equals (other, opt, note)) {
 +              return false;
 +      }
 +      
 +      shared_ptr<const MXF> other_mxf = dynamic_pointer_cast<const MXF> (other);
 +      if (!other_mxf) {
 +              note (ERROR, "comparing an MXF asset with a non-MXF asset");
 +              return false;
 +      }
 +      
 +      if (_file != other_mxf->file ()) {
 +              note (ERROR, "MXF names differ");
 +              if (!opt.mxf_names_can_differ) {
 +                      return false;
 +              }
 +      }
 +      
 +      return true;
 +}
 +
 +/** Set the (private) key that will be used to encrypt or decrypt this MXF's content.
 + *  This is the top-secret key that is distributed (itself encrypted) to cinemas
 + *  via Key Delivery Messages (KDMs).
 + *  @param key Key to use.
 + */
 +void
 +MXF::set_key (Key key)
 +{
 +      _key = key;
 +
 +      if (_key_id.empty ()) {
 +              /* No key ID so far; we now need one */
 +              _key_id = make_uuid ();
 +      }
 +      
 +      _decryption_context = new ASDCP::AESDecContext;
 +      if (ASDCP_FAILURE (_decryption_context->InitKey (_key->value ()))) {
 +              throw MiscError ("could not set up decryption context");
 +      }
 +
 +      _encryption_context = new ASDCP::AESEncContext;
 +      if (ASDCP_FAILURE (_encryption_context->InitKey (_key->value ()))) {
 +              throw MiscError ("could not set up encryption context");
 +      }
 +      
 +      uint8_t cbc_buffer[ASDCP::CBC_BLOCK_SIZE];
 +      
 +      Kumu::FortunaRNG rng;
 +      if (ASDCP_FAILURE (_encryption_context->SetIVec (rng.FillRandom (cbc_buffer, ASDCP::CBC_BLOCK_SIZE)))) {
 +              throw MiscError ("could not set up CBC initialization vector");
 +      }
 +}
 +
 +void
 +MXF::read_writer_info (ASDCP::WriterInfo const & info)
 +{
 +      char buffer[64];
 +      Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
 +      _id = buffer;
 +}
 +
 +string
 +MXF::pkl_type (Standard standard) const
 +{
 +      switch (standard) {
 +      case INTEROP:
 +              return String::compose ("application/x-smpte-mxf;asdcpKind=%1", asdcp_kind ());
 +      case SMPTE:
 +              return "application/mxf";
 +      default:
 +              assert (false);
 +      }
 +}
index 0000000000000000000000000000000000000000,507af15223c49e5887b10b2bfa6b7c0bdfd66c1f..68bbaf7a13d61a2f2a16e2ae3ab65df9148a1cd2
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,39 +1,39 @@@
 -namespace libdcp {
+ /*
+     Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+     This program is free software; you can redistribute it and/or modify
+     it under the terms of the GNU General Public License as published by
+     the Free Software Foundation; either version 2 of the License, or
+     (at your option) any later version.
+     This program is distributed in the hope that it will be useful,
+     but WITHOUT ANY WARRANTY; without even the implied warranty of
+     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     GNU General Public License for more details.
+     You should have received a copy of the GNU General Public License
+     along with this program; if not, write to the Free Software
+     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ #include <sstream>
++namespace dcp {
+ /** A sort-of version of boost::lexical_cast that does uses the "C"
+  *  locale (i.e. no thousands separators and a . for the decimal separator).
+  */
+ template <typename P, typename Q>
+ P
+ raw_convert (Q v)
+ {
+       std::stringstream s;
+       s.imbue (std::locale::classic ());
+       s << v;
+       P r;
+       s >> r;
+       return r;
+ }
+ };
index fa0068c50e5941645ef11da909ba6082ad7edd67,0000000000000000000000000000000000000000..7987a1554f77c374c993bfcb0bb843a27bb25470
mode 100644,000000..100644
--- /dev/null
@@@ -1,104 -1,0 +1,104 @@@
- using boost::lexical_cast;
 +/*
 +    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
++#include "raw_convert.h"
 +#include "reel_asset.h"
 +#include "content.h"
 +#include "compose.hpp"
 +#include <libcxml/cxml.h>
 +
 +using std::pair;
 +using std::string;
 +using std::make_pair;
 +using boost::shared_ptr;
-         a->add_child("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration));
-         a->add_child("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point));
-         a->add_child("Duration")->add_child_text (lexical_cast<string> (_duration));
 +using namespace dcp;
 +
 +ReelAsset::ReelAsset ()
 +      : Object (make_uuid ())
 +      , _content (_id)
 +      , _edit_rate (Fraction (24, 1))
 +      , _intrinsic_duration (0)
 +      , _entry_point (0)
 +      , _duration (0)
 +{
 +
 +}
 +
 +/** Construct a ReelAsset.
 + *  @param content Content that this asset refers to.
 + *  @param entry_point Entry point to use in that content.
 + */
 +ReelAsset::ReelAsset (boost::shared_ptr<Content> content, int64_t entry_point)
 +      : Object (content->id ())
 +      , _content (content)
 +      , _edit_rate (content->edit_rate ())
 +      , _intrinsic_duration (content->intrinsic_duration ())
 +      , _entry_point (entry_point)
 +      , _duration (_intrinsic_duration - _entry_point)
 +      , _hash (make_digest (content->file (), 0))
 +{
 +      /* default _annotation_text to the leaf name of our file */
 +        _annotation_text = content->file().leaf().string ();
 +}
 +
 +ReelAsset::ReelAsset (boost::shared_ptr<const cxml::Node> node)
 +      : Object (node->string_child ("Id"))
 +      , _content (_id)
 +      , _annotation_text (node->optional_string_child ("AnnotationText").get_value_or (""))
 +      , _edit_rate (Fraction (node->string_child ("EditRate")))
 +      , _intrinsic_duration (node->number_child<int64_t> ("IntrinsicDuration"))
 +      , _entry_point (node->number_child<int64_t> ("EntryPoint"))
 +      , _duration (node->number_child<int64_t> ("Duration"))
 +      , _hash (node->optional_string_child ("Hash").get_value_or (""))
 +      , _key_id (node->optional_string_child ("KeyId").get_value_or (""))
 +{
 +      if (_id.length() > 9) {
 +              _id = _id.substr (9);
 +              _content.set_id (_id);
 +      }
 +      
 +      if (_key_id.length() > 9) {
 +              _key_id = _key_id.substr (9);
 +      }
 +}
 +
 +void
 +ReelAsset::write_to_cpl (xmlpp::Node* node, Standard) const
 +{
 +        pair<string, string> const attr = cpl_node_attribute ();
 +        xmlpp::Element* a = node->add_child (cpl_node_name ());
 +        if (!attr.first.empty ()) {
 +                a->set_attribute (attr.first, attr.second);
 +        }
 +        a->add_child("Id")->add_child_text ("urn:uuid:" + _id);
 +        a->add_child("AnnotationText")->add_child_text (_annotation_text);
 +        a->add_child("EditRate")->add_child_text (String::compose ("%1 %2", _edit_rate.numerator, _edit_rate.denominator));
++        a->add_child("IntrinsicDuration")->add_child_text (raw_convert<string> (_intrinsic_duration));
++        a->add_child("EntryPoint")->add_child_text (raw_convert<string> (_entry_point));
++        a->add_child("Duration")->add_child_text (raw_convert<string> (_duration));
 +        if (!_key_id.empty ()) {
 +                a->add_child("KeyId")->add_child_text ("urn:uuid:" + _key_id);
 +        }
 +}
 +
 +pair<string, string>
 +ReelAsset::cpl_node_attribute () const
 +{
 +      return make_pair ("", "");
 +}
diff --cc src/subtitle.cc
index 8c40254acce530fd80f95d745cc827d7b32266d5,0000000000000000000000000000000000000000..12714961445eb8ac9077bb379ba9b991b9d40262
mode 100644,000000..100644
--- /dev/null
@@@ -1,60 -1,0 +1,61 @@@
 +/*
 +    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
 +#include "subtitle.h"
 +#include "xml.h"
 +#include "font.h"
 +#include "text.h"
 +#include <libcxml/cxml.h>
++#include <boost/lexical_cast.hpp>
 +
 +using std::string;
 +using boost::shared_ptr;
 +using boost::lexical_cast;
 +using namespace dcp;
 +
 +Subtitle::Subtitle (boost::shared_ptr<const cxml::Node> node)
 +{
 +      in = Time (node->string_attribute ("TimeIn"));
 +      out = Time (node->string_attribute ("TimeOut"));
 +      font_nodes = type_children<Font> (node, "Font");
 +      text_nodes = type_children<Text> (node, "Text");
 +      fade_up_time = fade_time (node, "FadeUpTime");
 +      fade_down_time = fade_time (node, "FadeDownTime");
 +}
 +
 +Time
 +Subtitle::fade_time (shared_ptr<const cxml::Node> node, string name)
 +{
 +      string const u = node->optional_string_attribute (name).get_value_or ("");
 +      Time t;
 +      
 +      if (u.empty ()) {
 +              t = Time (0, 0, 0, 20);
 +      } else if (u.find (":") != string::npos) {
 +              t = Time (u);
 +      } else {
 +              t = Time (0, 0, 0, lexical_cast<int> (u));
 +      }
 +
 +      if (t > Time (0, 0, 8, 0)) {
 +              t = Time (0, 0, 8, 0);
 +      }
 +
 +      return t;
 +}
index 0aaab9a2163ae565777d7fa42ff2d5e1d453bcfb,0000000000000000000000000000000000000000..a622e7b09a2cce1b144c0e03763b0e5d7540fd6a
mode 100644,000000..100644
--- /dev/null
@@@ -1,365 -1,0 +1,364 @@@
- #include <boost/lexical_cast.hpp>
 +/*
 +    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License
 +    along with this program; if not, write to the Free Software
 +    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 +
 +*/
 +
++#include "raw_convert.h"
 +#include "subtitle_content.h"
 +#include "util.h"
 +#include "xml.h"
 +#include "font.h"
 +#include "text.h"
 +#include "load_font.h"
 +#include "subtitle_string.h"
 +#include "AS_DCP.h"
 +#include "KM_util.h"
 +#include <libxml++/nodes/element.h>
- using boost::lexical_cast;
 +#include <boost/algorithm/string.hpp>
 +#include <fstream>
 +
 +using std::string;
 +using std::list;
 +using std::ostream;
 +using std::ofstream;
 +using std::stringstream;
 +using std::cout;
 +using boost::shared_ptr;
-       root->add_child("ReelNumber")->add_child_text (lexical_cast<string> (_reel_number));
 +using boost::optional;
 +using namespace dcp;
 +
 +SubtitleContent::SubtitleContent (boost::filesystem::path file, bool mxf)
 +      : Content (file)
 +      , _need_sort (false)
 +{
 +      shared_ptr<cxml::Document> xml;
 +      
 +      if (mxf) {
 +              ASDCP::TimedText::MXFReader reader;
 +              Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
 +              if (ASDCP_FAILURE (r)) {
 +                      boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r));
 +              }
 +              
 +              string s;
 +              reader.ReadTimedTextResource (s, 0, 0);
 +              xml.reset (new cxml::Document ("SubtitleReel"));
 +              stringstream t;
 +              t << s;
 +              xml->read_stream (t);
 +
 +              ASDCP::WriterInfo info;
 +              reader.FillWriterInfo (info);
 +              
 +              char buffer[64];
 +              Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
 +              _id = buffer;
 +
 +      } else {
 +              xml.reset (new cxml::Document ("DCSubtitle"));
 +              xml->read_file (file);
 +              _id = xml->string_child ("SubtitleID");
 +      }
 +
 +      /* XXX: hacks aplenty in here; probably need separate parsers for DCSubtitle and SubtitleReel */
 +
 +      _movie_title = xml->optional_string_child ("MovieTitle");
 +      _reel_number = xml->string_child ("ReelNumber");
 +      _language = xml->string_child ("Language");
 +
 +      xml->ignore_child ("LoadFont");
 +
 +      list<shared_ptr<dcp::Font> > font_nodes = type_children<dcp::Font> (xml, "Font");
 +      _load_font_nodes = type_children<dcp::LoadFont> (xml, "LoadFont");
 +
 +      /* Now make Subtitle objects to represent the raw XML nodes
 +         in a sane way.
 +      */
 +
 +      shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
 +      if (subtitle_list) {
 +              list<shared_ptr<dcp::Font> > font = type_children<dcp::Font> (subtitle_list, "Font");
 +              copy (font.begin(), font.end(), back_inserter (font_nodes));
 +      }
 +      
 +      ParseState parse_state;
 +      examine_font_nodes (xml, font_nodes, parse_state);
 +}
 +
 +SubtitleContent::SubtitleContent (Fraction edit_rate, string movie_title, string language)
 +      : Content (edit_rate)
 +      , _movie_title (movie_title)
 +      , _reel_number ("1")
 +      , _language (language)
 +      , _need_sort (false)
 +{
 +
 +}
 +
 +void
 +SubtitleContent::examine_font_nodes (
 +      shared_ptr<const cxml::Node> xml,
 +      list<shared_ptr<dcp::Font> > const & font_nodes,
 +      ParseState& parse_state
 +      )
 +{
 +      for (list<shared_ptr<dcp::Font> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
 +
 +              parse_state.font_nodes.push_back (*i);
 +              maybe_add_subtitle ((*i)->text, parse_state);
 +
 +              for (list<shared_ptr<dcp::Subtitle> >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) {
 +                      parse_state.subtitle_nodes.push_back (*j);
 +                      examine_text_nodes (xml, (*j)->text_nodes, parse_state);
 +                      examine_font_nodes (xml, (*j)->font_nodes, parse_state);
 +                      parse_state.subtitle_nodes.pop_back ();
 +              }
 +      
 +              examine_font_nodes (xml, (*i)->font_nodes, parse_state);
 +              examine_text_nodes (xml, (*i)->text_nodes, parse_state);
 +              
 +              parse_state.font_nodes.pop_back ();
 +      }
 +}
 +
 +void
 +SubtitleContent::examine_text_nodes (
 +      shared_ptr<const cxml::Node> xml,
 +      list<shared_ptr<dcp::Text> > const & text_nodes,
 +      ParseState& parse_state
 +      )
 +{
 +      for (list<shared_ptr<dcp::Text> >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) {
 +              parse_state.text_nodes.push_back (*i);
 +              maybe_add_subtitle ((*i)->text, parse_state);
 +              examine_font_nodes (xml, (*i)->font_nodes, parse_state);
 +              parse_state.text_nodes.pop_back ();
 +      }
 +}
 +
 +void
 +SubtitleContent::maybe_add_subtitle (string text, ParseState const & parse_state)
 +{
 +      if (empty_or_white_space (text)) {
 +              return;
 +      }
 +      
 +      if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
 +              return;
 +      }
 +
 +      assert (!parse_state.text_nodes.empty ());
 +      assert (!parse_state.subtitle_nodes.empty ());
 +      
 +      dcp::Font effective_font (parse_state.font_nodes);
 +      dcp::Text effective_text (*parse_state.text_nodes.back ());
 +      dcp::Subtitle effective_subtitle (*parse_state.subtitle_nodes.back ());
 +
 +      _subtitles.push_back (
 +              shared_ptr<SubtitleString> (
 +                      new SubtitleString (
 +                              font_id_to_name (effective_font.id),
 +                              effective_font.italic.get(),
 +                              effective_font.color.get(),
 +                              effective_font.size,
 +                              effective_subtitle.in,
 +                              effective_subtitle.out,
 +                              effective_text.v_position,
 +                              effective_text.v_align,
 +                              text,
 +                              effective_font.effect ? effective_font.effect.get() : NONE,
 +                              effective_font.effect_color.get(),
 +                              effective_subtitle.fade_up_time,
 +                              effective_subtitle.fade_down_time
 +                              )
 +                      )
 +              );
 +}
 +
 +list<shared_ptr<SubtitleString> >
 +SubtitleContent::subtitles_at (Time t) const
 +{
 +      list<shared_ptr<SubtitleString> > s;
 +      for (list<shared_ptr<SubtitleString> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
 +              if ((*i)->in() <= t && t <= (*i)->out ()) {
 +                      s.push_back (*i);
 +              }
 +      }
 +
 +      return s;
 +}
 +
 +std::string
 +SubtitleContent::font_id_to_name (string id) const
 +{
 +      list<shared_ptr<dcp::LoadFont> >::const_iterator i = _load_font_nodes.begin();
 +      while (i != _load_font_nodes.end() && (*i)->id != id) {
 +              ++i;
 +      }
 +
 +      if (i == _load_font_nodes.end ()) {
 +              return "";
 +      }
 +
 +      if ((*i)->uri && (*i)->uri.get() == "arial.ttf") {
 +              return "Arial";
 +      }
 +
 +      return "";
 +}
 +
 +void
 +SubtitleContent::add (shared_ptr<SubtitleString> s)
 +{
 +      _subtitles.push_back (s);
 +      _need_sort = true;
 +}
 +
 +struct SubtitleSorter {
 +      bool operator() (shared_ptr<SubtitleString> a, shared_ptr<SubtitleString> b) {
 +              if (a->in() != b->in()) {
 +                      return a->in() < b->in();
 +              }
 +              return a->v_position() < b->v_position();
 +      }
 +};
 +
 +void
 +SubtitleContent::write_xml () const
 +{
 +      FILE* f = fopen_boost (file (), "r");
 +      Glib::ustring const s = xml_as_string ();
 +      fwrite (s.c_str(), 1, s.length(), f);
 +      fclose (f);
 +}
 +
 +Glib::ustring
 +SubtitleContent::xml_as_string () const
 +{
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("DCSubtitle");
 +      root->set_attribute ("Version", "1.0");
 +
 +      root->add_child("SubtitleID")->add_child_text (_id);
 +      if (_movie_title) {
 +              root->add_child("MovieTitle")->add_child_text (_movie_title.get ());
 +      }
-                       font->set_attribute ("Size", lexical_cast<string> (size));
++      root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
 +      root->add_child("Language")->add_child_text (_language);
 +
 +      if (_load_font_nodes.size() > 1) {
 +              boost::throw_exception (MiscError ("multiple LoadFont nodes not supported"));
 +      }
 +
 +      if (!_load_font_nodes.empty ()) {
 +              xmlpp::Element* load_font = root->add_child("LoadFont");
 +              load_font->set_attribute ("Id", _load_font_nodes.front()->id);
 +              if (_load_font_nodes.front()->uri) {
 +                      load_font->set_attribute ("URI", _load_font_nodes.front()->uri.get ());
 +              }
 +      }
 +
 +      list<shared_ptr<SubtitleString> > sorted = _subtitles;
 +      if (_need_sort) {
 +              sorted.sort (SubtitleSorter ());
 +      }
 +
 +      /* XXX: multiple fonts not supported */
 +      /* XXX: script, underlined, weight not supported */
 +
 +      bool italic = false;
 +      Color color;
 +      int size = 0;
 +      Effect effect = NONE;
 +      Color effect_color;
 +      int spot_number = 1;
 +      Time last_in;
 +      Time last_out;
 +      Time last_fade_up_time;
 +      Time last_fade_down_time;
 +
 +      xmlpp::Element* font = 0;
 +      xmlpp::Element* subtitle = 0;
 +
 +      for (list<shared_ptr<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 =
 +                      italic       != (*i)->italic()       ||
 +                      color        != (*i)->color()        ||
 +                      size         != (*i)->size()         ||
 +                      effect       != (*i)->effect()       ||
 +                      effect_color != (*i)->effect_color();
 +
 +              if (font_changed) {
 +                      italic = (*i)->italic ();
 +                      color = (*i)->color ();
 +                      size = (*i)->size ();
 +                      effect = (*i)->effect ();
 +                      effect_color = (*i)->effect_color ();
 +              }
 +
 +              if (!font || font_changed) {
 +                      font = root->add_child ("Font");
 +                      string id = "theFontId";
 +                      if (!_load_font_nodes.empty()) {
 +                              id = _load_font_nodes.front()->id;
 +                      }
 +                      font->set_attribute ("Id", id);
 +                      font->set_attribute ("Italic", italic ? "yes" : "no");
 +                      font->set_attribute ("Color", color.to_argb_string());
-                       subtitle->set_attribute ("SpotNumber", lexical_cast<string> (spot_number++));
++                      font->set_attribute ("Size", raw_convert<string> (size));
 +                      font->set_attribute ("Effect", effect_to_string (effect));
 +                      font->set_attribute ("EffectColor", effect_color.to_argb_string());
 +                      font->set_attribute ("Script", "normal");
 +                      font->set_attribute ("Underlined", "no");
 +                      font->set_attribute ("Weight", "normal");
 +              }
 +
 +              if (!subtitle || 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 = font->add_child ("Subtitle");
-                       subtitle->set_attribute ("FadeUpTime", lexical_cast<string> ((*i)->fade_up_time().to_ticks()));
-                       subtitle->set_attribute ("FadeDownTime", lexical_cast<string> ((*i)->fade_down_time().to_ticks()));
++                      subtitle->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
 +                      subtitle->set_attribute ("TimeIn", (*i)->in().to_string());
 +                      subtitle->set_attribute ("TimeOut", (*i)->out().to_string());
-               text->set_attribute ("VPosition", lexical_cast<string> ((*i)->v_position()));
++                      subtitle->set_attribute ("FadeUpTime", raw_convert<string> ((*i)->fade_up_time().to_ticks()));
++                      subtitle->set_attribute ("FadeDownTime", raw_convert<string> ((*i)->fade_down_time().to_ticks()));
 +
 +                      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->add_child ("Text");
 +              text->set_attribute ("VAlign", valign_to_string ((*i)->v_align()));             
++              text->set_attribute ("VPosition", raw_convert<string> ((*i)->v_position()));
 +              text->add_child_text ((*i)->text());
 +      }
 +
 +      return doc.write_to_string_formatted ("UTF-8");
 +}
 +
diff --cc src/types.cc
index 0ecf7a16439c0b1bed10ee6f7d8f4e42c2bee202,f45e3345b8b5894c62a14dbf5ed44de98333b7aa..fc6d50f3e0dec5c56953061ed6cd6e12b07b3f97
  
  */
  
- #include <boost/lexical_cast.hpp>
++#include "raw_convert.h"
 +#include "types.h"
 +#include "exceptions.h"
 +#include <boost/algorithm/string.hpp>
  #include <vector>
  #include <cstdio>
  #include <iomanip>
diff --cc src/wscript
index fbbd9f2b7782c93ddba219fa763e9f793690de8d,8923455f1dffa044539ecd68a138d8b404cc803e..e5f0c2a51aa8a0460d2fd90bd03c761637e935b8
@@@ -80,25 -63,20 +80,26 @@@ def build(bld)
                exceptions.h
                gamma_lut.h
                image.h
 -              kdm.h
                key.h
 -              lut.h
 +              local_time.h
                lut_cache.h
                metadata.h
 -              mono_picture_asset.h
 +              mono_picture_mxf.h
                mono_picture_frame.h
 -              mxf_asset.h
 -              picture_asset.h
 -              picture_asset_writer.h
 +              mxf.h
 +              mxf_writer.h
 +              object.h
 +              picture_mxf.h
 +              picture_mxf_writer.h
+               raw_convert.h
                rgb_xyz.h
 -              rec709_linearised_gamma_lut.h
                reel.h
 +              reel_asset.h
 +              reel_mono_picture_asset.h
 +              reel_picture_asset.h
 +              reel_sound_asset.h
 +              reel_stereo_picture_asset.h
 +              ref.h
                argb_frame.h
                signer.h
                signer_chain.h