Merge master; MXF subtitle stuff not included.
authorCarl Hetherington <cth@carlh.net>
Wed, 26 Feb 2014 18:41:41 +0000 (18:41 +0000)
committerCarl Hetherington <cth@carlh.net>
Wed, 26 Feb 2014 18:41:41 +0000 (18:41 +0000)
20 files changed:
1  2 
src/asset.cc
src/asset.h
src/content.h
src/cpl.cc
src/cpl.h
src/dcp.cc
src/mxf.cc
src/mxf.h
src/picture_mxf.h
src/signer_chain.cc
src/sound_mxf.h
src/subtitle_content.cc
src/subtitle_content.h
test/dcp_test.cc
test/decryption_test.cc
test/encryption_test.cc
test/rewrite_subs.cc
test/test.cc
test/test.h
wscript

diff --cc src/asset.cc
index f8de10b6529d2e3125433628374cc467bb7c081f,d51b78fd50650a53d7432825cbfd6c357e4495b9..aaa79dc75660bd2877f52254a6c952e0bf386f7f
@@@ -60,16 -52,18 +60,16 @@@ Asset::Asset (string id
  }
  
  void
- Asset::write_to_pkl (xmlpp::Node* node) const
 -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 (lexical_cast<string> (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("Type")->add_child_text (pkl_type ());
++      asset->add_child("Type")->add_child_text (pkl_type (standard));
  }
  
  void
diff --cc src/asset.h
index 57143310a1b95b8cd229c473e2d822259c060296,773e3d48ae2fb9c2b060519b2620693db36107e9..bad982f1878b2584741ef0955a65253c022ff19f
  #ifndef LIBDCP_ASSET_H
  #define LIBDCP_ASSET_H
  
 -#include <string>
 -#include <list>
 +#include "object.h"
 +#include "types.h"
  #include <boost/filesystem.hpp>
  #include <boost/function.hpp>
 -#include <libxml++/libxml++.h>
 -#include "types.h"
 -
 -namespace ASDCP {
 -      class WriterInfo;
 -}
 +#include <boost/bind.hpp>
  
  namespace xmlpp {
 -      class Element;
 +      class Node;
  }
  
 -namespace libdcp
 -{
 +namespace dcp {
  
 -/** @brief Parent class for assets of DCPs
 +/** @class Asset
 + *  @brief Parent class for DCP assets, i.e. picture/sound/subtitles and CPLs.
   *
 - *  These are collections of pictures or sound.
 + *  Note that this class is not used for ReelAssets; those are just for the metadata
 + *  that gets put into &lt;Reel&gt;s.
   */
 -class Asset
 +class Asset : public Object
  {
  public:
 -      /** Construct an Asset.
 -       *  @param directory Directory where our XML or MXF file is.
 -       *  @param file_name Name of our file within directory, or empty to make one up based on UUID.
 +      Asset ();
 +      Asset (boost::filesystem::path file);
 +      Asset (std::string id);
 +
 +      virtual bool equals (
 +              boost::shared_ptr<const Asset> other,
 +              EqualityOptions opt,
 +              boost::function<void (NoteType, std::string)> note
 +              ) const;
 +
 +      /** Write details of the asset to a ASSETMAP.
 +       *  @param node Parent node.
         */
 -      Asset (boost::filesystem::path directory, boost::filesystem::path file_name = "");
 -
 -      virtual ~Asset() {}
 -
 -      /** Write details of the asset to a CPL AssetList node.
 -       *  @param p Parent element.
 -       */
 -      virtual void write_to_cpl (xmlpp::Element* p) const = 0;
 +      void write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const;
  
        /** Write details of the asset to a PKL AssetList node.
 -       *  @param p Parent node.
 -       */
 -      void write_to_pkl (xmlpp::Node *, bool interop) const;
 -
 -      /** Write details of the asset to a ASSETMAP stream.
 -       *  @param s Stream.
 +       *  @param node Parent node.
++       *  @param standard Standard to use.
         */
-       void write_to_pkl (xmlpp::Node* node) const;
 -      void write_to_assetmap (xmlpp::Node *) const;
 -
 -      /** Compute the digest for this asset.  Calling this is optional: if
 -       *  it is not called, the digest will be computed when required.  However,
 -       *  calling this method allows the caller to see the progress of the
 -       *  computation, which can be long for large assets.
 -       *  @param Called with progress between 0 and 1.
 -       */
 -      void compute_digest (boost::function<void (float)> progress);
 -
 -      std::string uuid () const {
 -              return _uuid;
 -      }
++      void write_to_pkl (xmlpp::Node* node, Standard standard) const;
  
 -      boost::filesystem::path path () const;
 -
 -      void set_directory (boost::filesystem::path d) {
 -              _directory = d;
 -      }
 -
 -      void set_file_name (boost::filesystem::path f) {
 -              _file_name = f;
 -      }
 -
 -      int entry_point () const {
 -              return _entry_point;
 +      boost::filesystem::path file () const {
 +              return _file;
        }
  
 -      int duration () const {
 -              return _duration;
 -      }
 -      
 -      int intrinsic_duration () const {
 -              return _intrinsic_duration;
 -      }
 -      
 -      int edit_rate () const {
 -              return _edit_rate;
 -      }
 -
 -      void set_entry_point (int e) {
 -              _entry_point = e;
 -      }
 -      
 -      void set_duration (int d) {
 -              _duration = d;
 -      }
 -
 -      void set_intrinsic_duration (int d) {
 -              _intrinsic_duration = d;
 -      }
 -
 -      void set_edit_rate (int r) {
 -              _edit_rate = r;
 -      }
 +      void set_file (boost::filesystem::path file) const;
  
 -      virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)>) const;
 +      /** @return the hash of this asset's file */
 +      std::string hash (boost::function<void (float)> progress = 0) const;
  
  protected:
-       virtual std::string pkl_type () const = 0;
++      virtual std::string pkl_type (Standard standard) const = 0;
  
 -      /** @return Interop PKL asdcpKind for the &lt;Type&gt; tag e.g. Picture, Sound etc. */
 -      virtual std::string asdcp_kind () const = 0;
 -      
 -      std::string digest () const;
 -
 -      /** Directory that our MXF or XML file is in */
 -      boost::filesystem::path _directory;
 -      /** Name of our MXF or XML file */
 -      boost::filesystem::path _file_name;
 -      /** Our UUID */
 -      std::string _uuid;
 -      /** The edit rate; this is normally equal to the number of video frames per second */
 -      int _edit_rate;
 -      /** Start point to present in frames */
 -      int _entry_point;
 -      /** Total length in frames */
 -      int _intrinsic_duration;
 -      /** Length to present in frames */
 -      int _duration;
 -
 -private:      
 -      /** Digest of our MXF or XML file */
 -      mutable std::string _digest;
 +      /** The disk file that represents this asset, if one exists */
 +      mutable boost::filesystem::path _file;
 +      /** Hash of _file, or empty if the hash has not yet been computed */
 +      mutable std::string _hash;
  };
  
  }
diff --cc src/content.h
index e7445d473024548c92d0a6709d00207bab45889f,0000000000000000000000000000000000000000..2059c8f54a9380339b675aaf16b00ee51472f8e7
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.
 +
 +*/
 +
 +/** @file  src/content.h
 + *  @brief Content class.
 + */
 +
 +#ifndef LIBDCP_CONTENT_H
 +#define LIBDCP_CONTENT_H
 +
 +#include "types.h"
 +#include "asset.h"
 +#include <libxml++/libxml++.h>
 +#include <boost/filesystem.hpp>
 +#include <boost/function.hpp>
 +#include <string>
 +#include <list>
 +
 +namespace ASDCP {
 +      class WriterInfo;
 +}
 +
 +namespace xmlpp {
 +      class Element;
 +}
 +
 +namespace dcp
 +{
 +
 +/** @class Content
 + *  @brief An asset that represents a piece of content, i.e. picture, sound or subtitle.
 + *
 + *  Such a piece of content will be contained in a file (either MXF or XML) within a DCP.
 + */
 +class Content : public Asset
 +{
 +public:
 +      Content (boost::filesystem::path file);
 +      Content (Fraction edit_rate);
 +      virtual ~Content () {}
 +
 +      bool equals (
 +              boost::shared_ptr<const Content> other,
 +              EqualityOptions opt,
 +              boost::function<void (NoteType, std::string)>
 +              ) const;
 +
 +      Fraction edit_rate () const {
 +              return _edit_rate;
 +      }
 +
 +      int64_t intrinsic_duration () const {
 +              return _intrinsic_duration;
 +      }
 +
 +protected:
 +      friend class MXFWriter;
++
++      virtual std::string asdcp_kind () const = 0;
 +      
 +      Fraction _edit_rate;
 +      int64_t _intrinsic_duration;
 +};
 +
 +}
 +
 +#endif
diff --cc src/cpl.cc
index cd255309dcb65fbdab8b3bd6d49d9dd2f518be97,e333df0db717adfb3f1d09353e6f801a5703ed21..9eae09ad51d456168af3d37f18f2a8f3b9b4a4ae
@@@ -149,24 -236,39 +149,25 @@@ CPL::write_xml (boost::filesystem::pat
        }
  
        /* This must not be the _formatted version otherwise signature digests will be wrong */
 -      doc.write_to_file (p.string (), "UTF-8");
 +      doc.write_to_file (file.string (), "UTF-8");
  
 -      _digest = make_digest (p.string (), 0);
 -      _length = boost::filesystem::file_size (p.string ());
 +      set_file (file);
  }
  
 -void
 -CPL::write_to_pkl (xmlpp::Node* node, bool interop) const
 +list<shared_ptr<const Content> >
 +CPL::content () const
  {
 -      xmlpp::Node* asset = node->add_child ("Asset");
 -      asset->add_child("Id")->add_child_text ("urn:uuid:" + _id);
 -      asset->add_child("Hash")->add_child_text (_digest);
 -      asset->add_child("Size")->add_child_text (lexical_cast<string> (_length));
 -      if (interop) {
 -              asset->add_child("Type")->add_child_text ("text/xml;asdcpKind=CPL");
 -      } else {
 -              asset->add_child("Type")->add_child_text ("text/xml");
 -      }
 -}
 +      list<shared_ptr<const Content> > c;
 -list<shared_ptr<const Asset> >
 -CPL::assets () const
 -{
 -      list<shared_ptr<const Asset> > a;
        for (list<shared_ptr<Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
                if ((*i)->main_picture ()) {
 -                      a.push_back ((*i)->main_picture ());
 +                      c.push_back ((*i)->main_picture()->mxf ());
                }
                if ((*i)->main_sound ()) {
 -                      a.push_back ((*i)->main_sound ());
 +                      c.push_back ((*i)->main_sound()->mxf ());
                }
                if ((*i)->main_subtitle ()) {
 -                      a.push_back ((*i)->main_subtitle ());
 +                      c.push_back ((*i)->main_subtitle()->subtitle_content ());
                }
        }
  
@@@ -245,9 -367,11 +246,23 @@@ CPL::set_mxf_keys (Key key
  }
  
  void
 -CPL::set_mxf_keys (Key key)
 +CPL::resolve_refs (list<shared_ptr<Object> > objects)
  {
        for (list<shared_ptr<Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
 -              (*i)->set_mxf_keys (key);
 +              (*i)->resolve_refs (objects);
        }
  }
++
++string
++CPL::pkl_type (Standard standard) const
++{
++      switch (standard) {
++      case INTEROP:
++              return "text/xml;asdcpKind=CPL";
++      case SMPTE:
++              return "text/xml";
++      default:
++              assert (false);
++      }
++}
++      
diff --cc src/cpl.h
index 6ef992fd6a15a70e4b02025baefa4a189571d4c3,c50d8f903a790a9619c23c99f693c3363b3b3610..8458a028c022be17ab2e663b922b1afcfee53b93
+++ b/src/cpl.h
@@@ -101,31 -88,37 +101,29 @@@ public
  
        void set_mxf_keys (Key);
  
 -      std::string id () const {
 -              return _id;
 -      }
 -      
 -      bool equals (CPL const & other, EqualityOptions options, boost::function<void (NoteType, std::string)> note) const;
 -      
 -      void write_xml (bool, XMLMetadata const &, boost::shared_ptr<const Signer>) const;
 -      void write_to_assetmap (xmlpp::Node *) const;
 -      void write_to_pkl (xmlpp::Node *, bool) const;
 +      void write_xml (
 +              boost::filesystem::path file,
 +              Standard standard,
 +              boost::shared_ptr<const Signer>
 +              ) const;
 +
 +      void resolve_refs (std::list<boost::shared_ptr<Object> >);
 +
 +protected:
 +      /** @return type string for PKLs for this asset */
-       std::string pkl_type () const {
-               return "text/xml";
-       }
++      std::string pkl_type (Standard standard) const;
  
 -      void add_kdm (KDM const &);
 -      
  private:
 -      std::pair<std::string, boost::shared_ptr<const parse::AssetMapAsset> > asset_from_id (std::list<PathAssetMap>, std::string id) const;
 -      
 -      boost::filesystem::path _directory;
 -      /** the name of the DCP */
 -      std::string _name;
 -      /** the content kind of the CPL */
 -      ContentKind _content_kind;
 -      /** length in frames */
 -      mutable int _length;
 -      /** frames per second */
 -      int _fps;
 -      /** reels */
 +      std::string _annotation_text;               ///< &lt;AnnotationText&gt;
 +      /** &lt;Issuer&gt;, &lt;Creator&gt; and &lt;IssueDate&gt;.  These are grouped
 +       *  because they occur together in a few places.
 +       */
 +      XMLMetadata _metadata;
 +      std::string _content_title_text;            ///< &lt;ContentTitleText&gt;
 +      ContentKind _content_kind;                  ///< &lt;ContentKind&gt;
 +      std::string _content_version_id;            ///< &lt;Id&gt; in &lt;ContentVersion&gt;
 +      std::string _content_version_label_text;    ///< &lt;LabelText&gt; in &lt;ContentVersion&gt;
        std::list<boost::shared_ptr<Reel> > _reels;
 -
 -      /** our UUID */
 -      std::string _id;
 -      /** a SHA1 digest of our XML */
 -      mutable std::string _digest;
  };
  
  }
diff --cc src/dcp.cc
index 36eedcf799d2814afa8c7b5f77b743f96fc1c566,ae69225616a07d4ed276859c14f9032e67f28031..88e365062f305d3d542eb1b9ffe3970f3c7f990d
@@@ -239,8 -109,13 +239,8 @@@ DCP::write_pkl (Standard standard, stri
        pkl->add_child("Creator")->add_child_text (metadata.creator);
  
        xmlpp::Element* asset_list = pkl->add_child("AssetList");
 -      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_pkl (asset_list, interop);
 -      }
 -      
 -      for (list<shared_ptr<CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
 -              (*i)->write_to_pkl (asset_list, interop);
 +      for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
-               (*i)->write_to_pkl (asset_list);
++              (*i)->write_to_pkl (asset_list, standard);
        }
  
        if (signer) {
        return p.string ();
  }
  
 +/** Write the VOLINDEX file.
 + *  @param standard DCP standard to use (INTEROP or SMPTE)
 + */
  void
 -DCP::write_volindex (bool interop) const
 +DCP::write_volindex (Standard standard) const
  {
 -      boost::filesystem::path p;
 -      p /= _directory;
 -      if (interop) {
 +      boost::filesystem::path p = _directory;
-       if (standard == INTEROP) {
++      switch (standard) {
++      case INTEROP:
                p /= "VOLINDEX";
--      } else {
++              break;
++      case SMPTE:
                p /= "VOLINDEX.xml";
++              break;
++      default:
++              assert (false);
        }
  
        xmlpp::Document doc;
-       xmlpp::Element* root = doc.create_root_node ("VolumeIndex", "http://www.smpte-ra.org/schemas/429-9/2007/AM");
+       xmlpp::Element* root;
 -      if (interop) {
++
++      switch (standard) {
++      case INTEROP:
+               root = doc.create_root_node ("VolumeIndex", "http://www.digicine.com/PROTO-ASDCP-AM-20040311#");
 -      } else {
++              break;
++      case SMPTE:
+               root = doc.create_root_node ("VolumeIndex", "http://www.smpte-ra.org/schemas/429-9/2007/AM");
++              break;
++      default:
++              assert (false);
+       }
++      
        root->add_child("Index")->add_child_text ("1");
        doc.write_to_file (p.string (), "UTF-8");
  }
  
  void
 -DCP::write_assetmap (string pkl_uuid, int pkl_length, bool interop, XMLMetadata const & metadata) const
 +DCP::write_assetmap (Standard standard, string pkl_uuid, int pkl_length, XMLMetadata metadata) const
  {
 -      boost::filesystem::path p;
 -      p /= _directory;
 -      if (interop) {
 +      boost::filesystem::path p = _directory;
-       if (standard == INTEROP) {
++      
++      switch (standard) {
++      case INTEROP:
                p /= "ASSETMAP";
--      } else {
++              break;
++      case SMPTE:
                p /= "ASSETMAP.xml";
++              break;
++      default:
++              assert (false);
        }
  
        xmlpp::Document doc;
        xmlpp::Element* root;
-       if (standard == INTEROP) {
 -      if (interop) {
++
++      switch (standard) {
++      case INTEROP:
                root = doc.create_root_node ("AssetMap", "http://www.digicine.com/PROTO-ASDCP-AM-20040311#");
--      } else {
++              break;
++      case SMPTE:
                root = doc.create_root_node ("AssetMap", "http://www.smpte-ra.org/schemas/429-9/2007/AM");
++              break;
++      default:
++              assert (false);
        }
  
        root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
        root->add_child("AnnotationText")->add_child_text ("Created by " + metadata.creator);
-       if (standard == INTEROP) {
 -      if (interop) {
++
++      switch (standard) {
++      case INTEROP:
                root->add_child("VolumeCount")->add_child_text ("1");
                root->add_child("IssueDate")->add_child_text (metadata.issue_date);
                root->add_child("Issuer")->add_child_text (metadata.issuer);
                root->add_child("Creator")->add_child_text (metadata.creator);
--      } else {
++              break;
++      case SMPTE:
                root->add_child("Creator")->add_child_text (metadata.creator);
                root->add_child("VolumeCount")->add_child_text ("1");
                root->add_child("IssueDate")->add_child_text (metadata.issue_date);
                root->add_child("Issuer")->add_child_text (metadata.issuer);
++              break;
++      default:
++              assert (false);
        }
                
        xmlpp::Node* asset_list = root->add_child ("AssetList");
diff --cc src/mxf.cc
index 3c75997b907bbeeb6cae6f19d52ac4364fe37ae2,0000000000000000000000000000000000000000..e1ed9650a77a75877d36f9a4d70b6440d7af88b9
mode 100644,000000..100644
--- /dev/null
@@@ -1,154 -1,0 +1,168 @@@
 +/*
 +    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 "AS_DCP.h"
 +#include "KM_prng.h"
 +#include "KM_util.h"
 +#include "mxf.h"
 +#include "util.h"
 +#include "metadata.h"
 +#include "exceptions.h"
 +#include "kdm.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::lexical_cast;
 +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, 16, buffer, 64);
 +      _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/x-smpte-mxf";
++      default:
++              assert (false);
++      }
++}
diff --cc src/mxf.h
index 525ac701c1b0542d13dfc57b63cc8da35a08f49b,0000000000000000000000000000000000000000..48a1f64ea6c553e9fbcc020a0d3e1f849a08fa49
mode 100644,000000..100644
--- /dev/null
+++ b/src/mxf.h
@@@ -1,122 -1,0 +1,119 @@@
-       std::string pkl_type () const {
-               return "application/x-smpte-mxf";
-       }
-       
 +/*
 +    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.
 +
 +*/
 +
 +#ifndef LIBDCP_MXF_H
 +#define LIBDCP_MXF_H
 +
 +#include "content.h"
 +#include "key.h"
 +#include "metadata.h"
 +#include <boost/signals2.hpp>
 +
 +namespace ASDCP {
 +      class AESEncContext;
 +      class AESDecContext;
 +}
 +
 +namespace dcp
 +{
 +
 +class MXFMetadata;    
 +
 +/** @class MXF
 + *  @brief Parent class for classes which represent MXF files.
 + */
 +class MXF : public Content
 +{
 +public:
 +      MXF (Fraction edit_rate);
 +      MXF (boost::filesystem::path file);
 +      ~MXF ();
 +
 +      /** @return the 4-character key type for this MXF (MDIK, MDAK, etc.) */
 +      virtual std::string key_type () const = 0;
 +      
 +      bool equals (
 +              boost::shared_ptr<const Content> other,
 +              EqualityOptions opt,
 +              boost::function<void (NoteType, std::string)> note
 +              ) const;
 +
 +      /** Fill in a ADSCP::WriteInfo struct.
 +       *  @param w struct to fill in.
 +       *  @param standard INTEROP or SMPTE.
 +       */
 +      void fill_writer_info (ASDCP::WriterInfo* w, Standard standard);
 +
 +      /** @return true if the data is encrypted */
 +      bool encrypted () const {
 +              return !_key_id.empty ();
 +      }
 +
 +      /** Set the ID of the key that is used for encryption/decryption.
 +       *  @param i key ID.
 +       */
 +      void set_key_id (std::string i) {
 +              _key_id = i;
 +      }
 +
 +      /** @return the ID of the key used for encryption/decryption, or an empty string */
 +      std::string key_id () const {
 +              return _key_id;
 +      }
 +
 +      void set_key (Key);
 +
 +      /** @return encryption/decryption key, if one has been set */
 +      boost::optional<Key> key () const {
 +              return _key;
 +      }
 +
 +      /** @return encryption context, set up with any key that has been passed to set_key() */
 +      ASDCP::AESEncContext* encryption_context () const {
 +              return _encryption_context;
 +      }
 +
 +      /** Set the metadata that is written to the MXF file.
 +       *  @param m Metadata.
 +       */
 +      void set_metadata (MXFMetadata m) {
 +              _metadata = m;
 +      }
 +
 +      /** @return metadata from the MXF file */
 +      MXFMetadata metadata () const {
 +              return _metadata;
 +      }
 +
 +protected:
++      std::string pkl_type (Standard standard) const;
 +      void read_writer_info (ASDCP::WriterInfo const &);
 +      
 +      ASDCP::AESEncContext* _encryption_context;
 +      ASDCP::AESDecContext* _decryption_context;
 +      /** ID of the key used for encryption/decryption, or an empty string */
 +      std::string _key_id;
 +      /** Key used for encryption/decryption, if there is one */
 +      boost::optional<Key> _key;
 +      MXFMetadata _metadata;
 +};
 +
 +}
 +
 +#endif
index 38a1819e6589f7c55208ce0f2e9c189106a1dbb1,0000000000000000000000000000000000000000..1ce2e7de08836d061a6f1af42bc70824be8f458d
mode 100644,000000..100644
--- /dev/null
@@@ -1,111 -1,0 +1,114 @@@
 +/*
 +    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.
 +
 +*/
 +
 +#ifndef LIBDCP_PICTURE_MXF_H
 +#define LIBDCP_PICTURE_MXF_H
 +
 +/** @file  src/picture_mxf.h
 + *  @brief PictureMXF class.
 + */
 +
 +#include "mxf.h"
 +#include "util.h"
 +#include "metadata.h"
 +#include <openjpeg.h>
 +
 +namespace ASDCP {
 +      namespace JP2K {
 +              class PictureDescriptor;
 +      }
 +}
 +
 +namespace dcp
 +{
 +
 +class MonoPictureFrame;       
 +class StereoPictureFrame;
 +class PictureMXFWriter;
 +
 +/** @class PictureMXF
 + *  @brief An asset made up of JPEG2000 data.
 + */
 +class PictureMXF : public MXF
 +{
 +public:
 +      PictureMXF (boost::filesystem::path file);
 +      PictureMXF (Fraction edit_rate);
 +
 +      virtual boost::shared_ptr<PictureMXFWriter> start_write (
 +              boost::filesystem::path file,
 +              Standard standard,
 +              bool overwrite
 +              ) = 0;
 +
 +      Size size () const {
 +              return _size;
 +      }
 +
 +      void set_size (Size s) {
 +              _size = s;
 +      }
 +
 +      Fraction frame_rate () const {
 +              return _frame_rate;
 +      }
 +
 +      void set_frame_rate (Fraction r) {
 +              _frame_rate = r;
 +      }
 +
 +      Fraction screen_aspect_ratio () const {
 +              return _screen_aspect_ratio;
 +      }
 +
 +      void set_screen_aspect_ratio (Fraction r) {
 +              _screen_aspect_ratio = r;
 +      }
 +
 +protected:
 +
 +      bool frame_buffer_equals (
 +              int frame, EqualityOptions opt, boost::function<void (NoteType, std::string)> note,
 +              uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B
 +              ) const;
 +
 +      bool descriptor_equals (
 +              ASDCP::JP2K::PictureDescriptor const & a,
 +              ASDCP::JP2K::PictureDescriptor const & b,
 +              boost::function<void (NoteType, std::string)>
 +              ) const;
 +
 +      void read_picture_descriptor (ASDCP::JP2K::PictureDescriptor const &);
 +
 +      /** picture size in pixels */
 +      Size _size;
 +      Fraction _frame_rate;
 +      Fraction _screen_aspect_ratio;
 +
 +private:
 +      std::string key_type () const;
++      std::string asdcp_kind () const {
++              return "Picture";
++      }
 +};
 +      
 +
 +}
 +
 +#endif
Simple merge
diff --cc src/sound_mxf.h
index 8d1f8db38ff7dbff6dca30c91ce15e25718024a7,0000000000000000000000000000000000000000..50d109570e0c02b5f1af4995f89b6d6a61c06d7e
mode 100644,000000..100644
--- /dev/null
@@@ -1,75 -1,0 +1,78 @@@
 +/*
 +    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/sound_mxf.h
 + *  @brief SoundMXF class
 + */
 +
 +#ifndef LIBDCP_SOUND_MXF_H
 +#define LIBDCP_SOUND_MXF_H
 +
 +#include "mxf.h"
 +#include "types.h"
 +#include "metadata.h"
 +
 +namespace dcp
 +{
 +
 +class SoundFrame;
 +class SoundMXFWriter;
 +
 +/** @class SoundMXF
 + *  @brief Representation of a MXF file containing sound
 + */
 +class SoundMXF : public MXF
 +{
 +public:
 +      SoundMXF (boost::filesystem::path file);
 +      SoundMXF (Fraction edit_rate, int sampling_rate, int channels);
 +
 +      boost::shared_ptr<SoundMXFWriter> start_write (boost::filesystem::path file, Standard standard);
 +      
 +      bool equals (
 +              boost::shared_ptr<const Content> other,
 +              EqualityOptions opt,
 +              boost::function<void (NoteType, std::string)> note
 +              ) const;
 +
 +      boost::shared_ptr<const SoundFrame> get_frame (int n) const;
 +
 +      /** @return number of channels */
 +      int channels () const {
 +              return _channels;
 +      }
 +
 +      /** @return sampling rate in Hz */
 +      int sampling_rate () const {
 +              return _sampling_rate;
 +      }
 +
 +private:
 +      std::string key_type () const;
++      std::string asdcp_kind () const {
++              return "Sound";
++      }
 +
 +      int _channels;      ///< number of channels
 +      int _sampling_rate; ///< sampling rate in Hz
 +};
 +
 +}
 +
 +#endif
index e32118c45d232acdc29e92566a8bd7622ec26d44,0000000000000000000000000000000000000000..f338517e407e35a4136eb1864d7db84b7f5bbd30
mode 100644,000000..100644
--- /dev/null
@@@ -1,325 -1,0 +1,326 @@@
 +/*
 +    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_content.h"
 +#include "util.h"
 +#include "xml.h"
 +#include "font.h"
 +#include "text.h"
 +#include "load_font.h"
 +#include "subtitle_string.h"
 +#include <libxml++/nodes/element.h>
 +#include <boost/lexical_cast.hpp>
 +#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;
 +using boost::lexical_cast;
 +using boost::optional;
 +using namespace dcp;
 +
 +SubtitleContent::SubtitleContent (boost::filesystem::path file)
 +      : Content (file)
 +      , _need_sort (false)
 +{
 +      shared_ptr<cxml::Document> xml (new cxml::Document ("DCSubtitle"));
 +      xml->read_file (file);
 +      
 +      _id = xml->string_child ("SubtitleID");
 +      _movie_title = xml->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.
 +      */
-               load_font->set_attribute("URI",  _load_font_nodes.front()->uri);
++      
 +      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 == "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);
 +      root->add_child("MovieTitle")->add_child_text (_movie_title);
 +      root->add_child("ReelNumber")->add_child_text (lexical_cast<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);
++              load_font->set_attribute("URI", _load_font_nodes.front()->uri);
 +      }
 +
 +      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());
 +                      font->set_attribute ("Size", lexical_cast<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 ("SpotNumber", lexical_cast<string> (spot_number++));
 +                      subtitle->set_attribute ("TimeIn", (*i)->in().to_string());
 +                      subtitle->set_attribute ("TimeOut", (*i)->out().to_string());
 +                      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()));
 +
 +                      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", lexical_cast<string> ((*i)->v_position()));
 +              text->add_child_text ((*i)->text());
 +      }
 +
 +      return doc.write_to_string_formatted ("UTF-8");
 +}
 +
index c4d0012bc9f7cbe76c08091bdb1c1f4ac7090c88,0000000000000000000000000000000000000000..410c5934a23cb1a4b96f07a2a95f2583a9d586bb
mode 100644,000000..100644
--- /dev/null
@@@ -1,109 -1,0 +1,113 @@@
-       std::string pkl_type () const {
 +/*
 +    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.
 +
 +*/
 +
 +#ifndef LIBDCP_SUBTITLE_CONTENT_H
 +#define LIBDCP_SUBTITLE_CONTENT_H
 +
 +#include "content.h"
 +#include "dcp_time.h"
 +#include <libcxml/cxml.h>
 +
 +namespace dcp
 +{
 +
 +class SubtitleString; 
 +class Font;
 +class Text;
 +class Subtitle;
 +class LoadFont;
 +
 +/** @class SubtitleContent
 + *  @brief A representation of an XML file containing subtitles.
 + */
 +class SubtitleContent : public Content
 +{
 +public:
 +      SubtitleContent (boost::filesystem::path file);
 +      SubtitleContent (Fraction edit_rate, std::string movie_title, std::string language);
 +
 +      bool equals (
 +              boost::shared_ptr<const Content>,
 +              EqualityOptions,
 +              boost::function<void (NoteType, std::string)> note
 +              ) const {
 +              /* XXX */
 +              note (ERROR, "subtitle content not compared yet");
 +              return true;
 +      }
 +
 +      std::string language () const {
 +              return _language;
 +      }
 +
 +      std::list<boost::shared_ptr<SubtitleString> > subtitles_at (Time t) const;
 +      std::list<boost::shared_ptr<SubtitleString> > const & subtitles () const {
 +              return _subtitles;
 +      }
 +
 +      void add (boost::shared_ptr<SubtitleString>);
 +
 +      void write_xml () const;
 +      Glib::ustring xml_as_string () const;
 +
 +protected:
++      std::string pkl_type (Standard) const {
 +              return "text/xml";
 +      }
++
++      std::string asdcp_kind () const {
++              return "Subtitle";
++      }
 +      
 +private:
 +      std::string font_id_to_name (std::string id) const;
 +
 +      struct ParseState {
 +              std::list<boost::shared_ptr<Font> > font_nodes;
 +              std::list<boost::shared_ptr<Text> > text_nodes;
 +              std::list<boost::shared_ptr<Subtitle> > subtitle_nodes;
 +      };
 +
 +      void maybe_add_subtitle (std::string text, ParseState const & parse_state);
 +      
 +      void examine_font_nodes (
 +              boost::shared_ptr<const cxml::Node> xml,
 +              std::list<boost::shared_ptr<Font> > const & font_nodes,
 +              ParseState& parse_state
 +              );
 +      
 +      void examine_text_nodes (
 +              boost::shared_ptr<const cxml::Node> xml,
 +              std::list<boost::shared_ptr<Text> > const & text_nodes,
 +              ParseState& parse_state
 +              );
 +
 +      std::string _movie_title;
 +      /* strangely, this is sometimes a string */
 +      std::string _reel_number;
 +      std::string _language;
 +      std::list<boost::shared_ptr<LoadFont> > _load_font_nodes;
 +
 +      std::list<boost::shared_ptr<SubtitleString> > _subtitles;
 +      bool _need_sort;
 +};
 +
 +}
 +
 +#endif
index 07defce0f38110fe0ccd8c5068f27b8a92cc1ed0,d0c8b973e91264d70c0db47e29f3565050d539a3..1736cb602a60b2d0ebfaadbc21e31de3a41b62f2
@@@ -51,57 -44,34 +51,57 @@@ BOOST_AUTO_TEST_CASE (dcp_test
        mxf_meta.product_name = "OpenDCP";
        mxf_meta.product_version = "0.0.25";
  
 -      /* We're making build/test/DCP/foo */
 +      /* We're making build/test/foo */
-       boost::filesystem::remove_all ("build/test/foo");
-       boost::filesystem::create_directories ("build/test/foo");
-       dcp::DCP d ("build/test/foo");
+       boost::filesystem::remove_all ("build/test/DCP/foo");
+       boost::filesystem::create_directories ("build/test/DCP/foo");
 -      libdcp::DCP d ("build/test/DCP/foo");
 -      shared_ptr<libdcp::CPL> cpl (new libdcp::CPL ("build/test/DCP/foo", "A Test DCP", libdcp::FEATURE, 24, 24));
++      dcp::DCP d ("build/test/DCP/foo");
 +      shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
 +      cpl->set_content_version_id ("urn:uri:81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00");
 +      cpl->set_content_version_label_text ("81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00");
  
 -      shared_ptr<libdcp::MonoPictureAsset> mp (new libdcp::MonoPictureAsset ("build/test/DCP/foo", "video.mxf"));
 -      mp->set_progress (&d.Progress);
 -      mp->set_edit_rate (24);
 -      mp->set_intrinsic_duration (24);
 -      mp->set_duration (24);
 -      mp->set_size (libdcp::Size (32, 32));
 +      shared_ptr<dcp::MonoPictureMXF> mp (new dcp::MonoPictureMXF (dcp::Fraction (24, 1)));
        mp->set_metadata (mxf_meta);
-       shared_ptr<dcp::PictureMXFWriter> picture_writer = mp->start_write ("build/test/foo/video.mxf", dcp::SMPTE, false);
 -      mp->create (j2c);
++      shared_ptr<dcp::PictureMXFWriter> picture_writer = mp->start_write ("build/test/DCP/foo/video.mxf", dcp::SMPTE, false);
 +      dcp::File j2c ("test/data/32x32_red_square.j2c");
 +      for (int i = 0; i < 24; ++i) {
 +              picture_writer->write (j2c.data (), j2c.size ());
 +      }
 +      picture_writer->finalize ();
  
 -      shared_ptr<libdcp::SoundAsset> ms (new libdcp::SoundAsset ("build/test/DCP/foo", "audio.mxf"));
 -      ms->set_progress (&d.Progress);
 -      ms->set_edit_rate (24);
 -      ms->set_intrinsic_duration (24);
 -      ms->set_duration (24);
 -      ms->set_channels (2);
 +      shared_ptr<dcp::SoundMXF> ms (new dcp::SoundMXF (dcp::Fraction (24, 1), 48000, 1));
        ms->set_metadata (mxf_meta);
-       shared_ptr<dcp::SoundMXFWriter> sound_writer = ms->start_write ("build/test/foo/audio.mxf", dcp::SMPTE);
 -      ms->create (wav);
++      shared_ptr<dcp::SoundMXFWriter> sound_writer = ms->start_write ("build/test/DCP/foo/audio.mxf", dcp::SMPTE);
 +
 +      SF_INFO info;
 +      info.format = 0;
 +      SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
 +      BOOST_CHECK (sndfile);
 +      float buffer[4096*6];
 +      float* channels[1];
 +      channels[0] = buffer;
 +      while (1) {
 +              sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
 +              sound_writer->write (channels, N);
 +              if (N < 4096) {
 +                      break;
 +              }
 +      }
 +      
 +      sound_writer->finalize ();
        
 -      cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (mp, ms, shared_ptr<libdcp::SubtitleAsset> ())));
 -      d.add_cpl (cpl);
 +      cpl->add (shared_ptr<dcp::Reel> (
 +                        new dcp::Reel (
 +                                shared_ptr<dcp::ReelMonoPictureAsset> (new dcp::ReelMonoPictureAsset (mp, 0)),
 +                                shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (ms, 0)),
 +                                shared_ptr<dcp::ReelSubtitleAsset> ()
 +                                )
 +                        ));
 +                
 +      d.add (cpl);
 +      d.add (mp);
 +      d.add (ms);
  
 -      d.write_xml (false, xml_meta);
 +      d.write_xml (dcp::SMPTE, xml_meta);
  
-       /* build/test/foo is checked against test/ref/DCP/foo by run-tests.sh */
+       /* build/test/DCP/foo is checked against test/ref/DCP/foo by run-tests.sh */
  }
index 030f0b58576b110dd0b7dd0094f2ae0cc9337ad3,d0e067ee138610f3cccb053036861a532b45b803..8dfeffa658d36a3e498345cb01c3cb65454e1bb2
@@@ -46,15 -45,15 +46,15 @@@ get_frame (dcp::DCP const & dcp
  /** Decrypt an encrypted test DCP and check that its first frame is the same as the unencrypted version */
  BOOST_AUTO_TEST_CASE (decryption_test)
  {
-       boost::filesystem::path plaintext_path = test_corpus;
+       boost::filesystem::path plaintext_path = private_test;
        plaintext_path /= "TONEPLATES-SMPTE-PLAINTEXT_TST_F_XX-XX_ITL-TD_51-XX_2K_WOE_20111001_WOE_OV";
 -      libdcp::DCP plaintext (plaintext_path.string ());
 +      dcp::DCP plaintext (plaintext_path.string ());
        plaintext.read ();
        BOOST_CHECK_EQUAL (plaintext.encrypted (), false);
  
-       boost::filesystem::path encrypted_path = test_corpus;
+       boost::filesystem::path encrypted_path = private_test;
        encrypted_path /= "TONEPLATES-SMPTE-ENCRYPTED_TST_F_XX-XX_ITL-TD_51-XX_2K_WOE_20111001_WOE_OV";
 -      libdcp::DCP encrypted (encrypted_path.string ());
 +      dcp::DCP encrypted (encrypted_path.string ());
        encrypted.read ();
        BOOST_CHECK_EQUAL (encrypted.encrypted (), true);
  
index a0633b072b2a0bff2784b1219b6a5ff5135bfdc5,40003413c87e4b7b81ac256ced2a4fa9f39e9925..b4a895443b49d138bfaa101e39a913aa5e9aeca7
@@@ -62,17 -56,18 +62,18 @@@ BOOST_AUTO_TEST_CASE (encryption_test
        
        boost::filesystem::remove_all ("build/test/DCP/bar");
        boost::filesystem::create_directories ("build/test/DCP/bar");
 -      libdcp::DCP d ("build/test/DCP/bar");
 +      dcp::DCP d ("build/test/DCP/bar");
  
 -      libdcp::CertificateChain chain;
 -      chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
 -      chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
 -      chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
+       /* Use test/ref/crypt so this test is repeatable */
-       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("build/test/signer/ca.self-signed.pem"))));
-       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("build/test/signer/intermediate.signed.pem"))));
-       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("build/test/signer/leaf.signed.pem"))));
 +      dcp::CertificateChain chain;
++      chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
++      chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
++      chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
  
 -      shared_ptr<libdcp::Signer> signer (
 -              new libdcp::Signer (
 +      shared_ptr<dcp::Signer> signer (
 +              new dcp::Signer (
                        chain,
-                       "build/test/signer/leaf.key"
+                       "test/ref/crypt/leaf.key"
                        )
                );
  
index fdb41a2c30d524391f279bf1abd945a9f1d670ae,3a982d1b6d8472651cb34d9834e5c407c76c1435..3b177b2184588d497715f324a8f970aa2a557e06
@@@ -33,24 -13,25 +33,25 @@@ using namespace dcp
  
  int
  main (int argc, char* argv[])
- try
  {
-       if (argc < 2) {
-               cerr << "Syntax: " << argv[0] << " <dcp>\n";
-               exit (EXIT_FAILURE);
-       }
-       DCP* dcp = new DCP (argv[1]);
-       dcp->read ();
-       
-       list<shared_ptr<CPL> > cpls = dcp->cpls ();
-       for (list<boost::shared_ptr<CPL> >::iterator i = cpls.begin(); i != cpls.end(); ++i) {
-               list<shared_ptr<Reel> > reels = (*i)->reels ();
-               for (list<shared_ptr<Reel> >::iterator j = reels.begin(); j != reels.end(); ++j) {
-                       if ((*j)->main_subtitle()) {
-                               (*j)->main_subtitle()->subtitle_content()->write_xml ();
+       try {
+               if (argc < 2) {
+                       cerr << "Syntax: " << argv[0] << " <dcp>\n";
+                       exit (EXIT_FAILURE);
+               }
+               
+               DCP* dcp = new DCP (argv[1]);
 -              dcp->read (false);
++              dcp->read ();
+               
+               list<shared_ptr<CPL> > cpls = dcp->cpls ();
+               for (list<boost::shared_ptr<CPL> >::iterator i = cpls.begin(); i != cpls.end(); ++i) {
+                       
+                       list<shared_ptr<Reel> > reels = (*i)->reels ();
+                       for (list<shared_ptr<Reel> >::iterator j = reels.begin(); j != reels.end(); ++j) {
+                               
+                               if ((*j)->main_subtitle()) {
 -                                      (*j)->main_subtitle()->write_xml ();
++                                      (*j)->main_subtitle()->subtitle_content()->write_xml ();
+                               }
                        }
                }
        }
diff --cc test/test.cc
Simple merge
diff --cc test/test.h
index bc6baccdd43d1be6eba6112979aefae5f53e4c4c,47a615d8df46bc7823ef1b3d2872647cacc0934b..f139fa283313ff14935fe355c07d3301d03313ad
@@@ -17,4 -17,6 +17,4 @@@
  
  */
  
- extern std::string test_corpus;
 -extern boost::filesystem::path j2c (int);
 -extern boost::filesystem::path wav (libdcp::Channel);
+ extern std::string private_test;
diff --cc wscript
index dc78f8cae1e7c20de71f4728e0e7ae2d383f63c0,b0295084226af90ffbfff99be9b77d0286fffe56..a26e357d558e5a7d9df524928fe38cca22c2366d
+++ b/wscript
@@@ -116,11 -112,11 +116,11 @@@ def build(bld)
      else:
          boost_lib_suffix = ''
  
 -    bld(source = 'libdcp.pc.in',
 -        version = VERSION,
 -        includedir = '%s/include' % bld.env.PREFIX,
 -        libs = "-L${libdir} -ldcp -lasdcp-libdcp -lkumu-libdcp -lcxml -lboost_system%s" % boost_lib_suffix,
 -        install_path = '${LIBDIR}/pkgconfig')
 +    bld(source='libdcp%s.pc.in' % bld.env.API_VERSION,
 +        version=VERSION,
 +        includedir='%s/include/libdcp%s' % (bld.env.PREFIX, bld.env.API_VERSION),
-         libs="-L${libdir} -ldcp%s -lasdcp-libdcp%s -lkumu-libdcp%s -lboost_system%s" % (API_VERSION, API_VERSION, API_VERSION, boost_lib_suffix),
++        libs="-L${libdir} -ldcp%s -lasdcp-libdcp%s -lkumu-libdcp%s -lcxml -lboost_system%s" % (API_VERSION, API_VERSION, API_VERSION, boost_lib_suffix),
 +        install_path='${LIBDIR}/pkgconfig')
  
      bld.recurse('src')
      bld.recurse('tools')