Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 18 Jun 2013 14:07:41 +0000 (15:07 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 18 Jun 2013 14:07:41 +0000 (15:07 +0100)
23 files changed:
1  2 
examples/make_dcp.cc
run-tests.sh
src/asset.cc
src/asset.h
src/cpl.cc
src/cpl.h
src/dcp.cc
src/dcp.h
src/dcp_time.cc
src/mxf_asset.cc
src/mxf_asset.h
src/picture_asset.cc
src/picture_asset.h
src/reel.cc
src/reel.h
src/sound_asset.cc
src/sound_asset.h
src/subtitle_asset.cc
src/util.cc
src/util.h
src/wscript
test/tests.cc
wscript

index 8061ca614d26dba3cf3188939748c65d079c9e56,0d6a8215c6ba13f36989ccb6f32cd7806745de14..2bcd4fc926725c5efcb762193c81a49a6361f024
@@@ -73,7 -75,7 +75,7 @@@ main (
           for 2K projectors.
        */
        boost::shared_ptr<libdcp::MonoPictureAsset> picture_asset (
-               new libdcp::MonoPictureAsset (video_frame, "My Film DCP", "video.mxf", 0, 24, 48, 1998, 1080, false)
 -              new libdcp::MonoPictureAsset (video_frame, "My Film DCP", "video.mxf", 0, 24, 48, libdcp::Size (1998, 1080))
++              new libdcp::MonoPictureAsset (video_frame, "My Film DCP", "video.mxf", 0, 24, 48, libdcp::Size (1998, 1080), false)
                );
  
        /* Now we will create a `sound asset', which is made up of a WAV file for each channel of audio.  Here we're using
@@@ -93,7 -95,7 +95,7 @@@
  
        /* Now we can create the sound asset using these files */
        boost::shared_ptr<libdcp::SoundAsset> sound_asset (
-               new libdcp::SoundAsset (sound_files, "My Film DCP", "audio.mxf", 0, 24, 48, 0, false)
 -              new libdcp::SoundAsset (sound_files, "My Film DCP", "audio.mxf", 0, 24, 48)
++              new libdcp::SoundAsset (sound_files, "My Film DCP", "audio.mxf", 0, 24, 48, false)
                );
  
        /* Now that we have the assets, we can create a Reel to put them in and add it to the CPL */
diff --cc run-tests.sh
Simple merge
diff --cc src/asset.cc
index fa6947d17d0f20136a92d119157c82a1e7f60b7f,84bdd2bd4bbe68dfeffcb2892e559e06ab77723f..c566a1e56b22d147da78eea129475fccad9c9187
@@@ -24,8 -24,8 +24,9 @@@
  #include <iostream>
  #include <fstream>
  #include <boost/filesystem.hpp>
 -#include <boost/function.hpp>
  #include <boost/lexical_cast.hpp>
++#include <boost/function.hpp>
 +#include <libxml++/nodes/element.h>
  #include "AS_DCP.h"
  #include "KM_util.h"
  #include "asset.h"
@@@ -92,3 -93,24 +94,23 @@@ Asset::digest () cons
        return _digest;
  }
  
 -
+ bool
+ Asset::equals (shared_ptr<const Asset> other, EqualityOptions, boost::function<void (NoteType, string)> note) const
+ {
+       if (_edit_rate != other->_edit_rate) {
+               note (ERROR, "MXF edit rates differ");
+               return false;
+       }
+       
+       if (_intrinsic_duration != other->_intrinsic_duration) {
+               note (ERROR, "MXF intrinsic durations differ");
+               return false;
+       }
+       if (_duration != other->_duration) {
+               note (ERROR, "MXF durations differ");
+               return false;
+       }
+       return true;
+ }
diff --cc src/asset.h
index 5436e05cde27f76f7afdc178f1477e11ec5e00e4,3bc713a38b4b29450bfae79e3111151b890cca0f..3dffa9abc3becc84b126d7ddeaf1dcfe64873711
@@@ -55,15 -53,15 +57,15 @@@ public
  
        virtual ~Asset() {}
  
 -      /** Write details of the asset to a CPL stream.
 -       *  @param s Stream.
 +      /** Write details of the asset to a CPL AssetList node.
 +       *  @param p Parent node.
         */
-       virtual void write_to_cpl (xmlpp::Element* p) const = 0;
+       virtual void write_to_cpl (xmlpp::Node *) const = 0;
  
 -      /** Write details of the asset to a PKL stream.
 -       *  @param s Stream.
 +      /** Write details of the asset to a PKL AssetList node.
 +       *  @param p Parent node.
         */
-       void write_to_pkl (xmlpp::Element* p) const;
+       void write_to_pkl (xmlpp::Node *) const;
  
        /** Write details of the asset to a ASSETMAP stream.
         *  @param s Stream.
diff --cc src/cpl.cc
index 0000000000000000000000000000000000000000,fd7056809009efb30ab5c9265f09ed336f49b046..1ca64f888e1b730d51b2fa10af7e8c1706554fff
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,298 +1,460 @@@
 -CPL::write_xml (XMLMetadata const & metadata) const
+ /*
+     Copyright (C) 2012 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 <fstream>
+ #include "cpl.h"
+ #include "parse/cpl.h"
+ #include "util.h"
+ #include "picture_asset.h"
+ #include "sound_asset.h"
+ #include "subtitle_asset.h"
+ #include "parse/asset_map.h"
+ #include "reel.h"
+ #include "metadata.h"
++#include "encryption.h"
+ using std::string;
+ using std::stringstream;
+ using std::ofstream;
+ using std::ostream;
+ using std::list;
+ using boost::shared_ptr;
+ using boost::lexical_cast;
+ using namespace libdcp;
+ CPL::CPL (string directory, string name, ContentKind content_kind, int length, int frames_per_second)
+       : _directory (directory)
+       , _name (name)
+       , _content_kind (content_kind)
+       , _length (length)
+       , _fps (frames_per_second)
+ {
+       _uuid = make_uuid ();
+ }
+ /** 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_map The corresponding asset map.
+  *  @param require_mxfs true to throw an exception if a required MXF file does not exist.
+  */
+ CPL::CPL (string directory, string file, shared_ptr<const libdcp::parse::AssetMap> asset_map, bool require_mxfs)
+       : _directory (directory)
+       , _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));
+       }
+       
+       /* Now cherry-pick the required bits into our own data structure */
+       
+       _name = cpl->annotation_text;
+       _content_kind = cpl->content_kind;
+       for (list<shared_ptr<libdcp::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) {
+                       try {
+                               picture.reset (new MonoPictureAsset (
+                                                      _directory,
+                                                      asset_map->asset_from_id (p->id)->chunks.front()->path
+                                                      )
+                                       );
+                               picture->set_entry_point (p->entry_point);
+                               picture->set_duration (p->duration);
+                       } catch (MXFFileError) {
+                               if (require_mxfs) {
+                                       throw;
+                               }
+                       }
+                       
+               } else {
+                       try {
+                               picture.reset (new StereoPictureAsset (
+                                                      _directory,
+                                                      asset_map->asset_from_id (p->id)->chunks.front()->path,
+                                                      _fps,
+                                                      p->duration
+                                                      )
+                                       );
+                               picture->set_entry_point (p->entry_point);
+                               picture->set_duration (p->duration);
+                               
+                       } catch (MXFFileError) {
+                               if (require_mxfs) {
+                                       throw;
+                               }
+                       }
+                       
+               }
+               
+               if ((*i)->asset_list->main_sound) {
+                       
+                       try {
+                               sound.reset (new SoundAsset (
+                                                    _directory,
+                                                    asset_map->asset_from_id ((*i)->asset_list->main_sound->id)->chunks.front()->path
+                                                    )
+                                       );
+                               sound->set_entry_point ((*i)->asset_list->main_sound->entry_point);
+                               sound->set_duration ((*i)->asset_list->main_sound->duration);
+                       } catch (MXFFileError) {
+                               if (require_mxfs) {
+                                       throw;
+                               }
+                       }
+               }
+               if ((*i)->asset_list->main_subtitle) {
+                       
+                       subtitle.reset (new SubtitleAsset (
+                                               _directory,
+                                               asset_map->asset_from_id ((*i)->asset_list->main_subtitle->id)->chunks.front()->path
+                                               )
+                               );
+                       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)));
+       }
+ }
+ void
+ CPL::add_reel (shared_ptr<const Reel> reel)
+ {
+       _reels.push_back (reel);
+ }
+ void
++CPL::write_xml (shared_ptr<Encryption> crypt, XMLMetadata const & metadata) const
+ {
+       boost::filesystem::path p;
+       p /= _directory;
+       stringstream s;
+       s << _uuid << "_cpl.xml";
+       p /= s.str();
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("CompositionPlaylist", "http://www.smpte-ra.org/schemas/429-7/2006/CPL");
++
++      if (crypt) {
++              root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
++      }
++      
+       root->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
+       root->add_child("AnnotationText")->add_child_text (_name);
+       root->add_child("IssueDate")->add_child_text (metadata.issue_date);
+       root->add_child("Creator")->add_child_text (metadata.creator);
+       root->add_child("ContentTitleText")->add_child_text (_name);
+       root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
+       {
+               xmlpp::Node* cv = root->add_child ("ContentVersion");
+               cv->add_child ("Id")->add_child_text ("urn:uri:" + _uuid + "_" + metadata.issue_date);
+               cv->add_child ("LabelText")->add_child_text (_uuid + "_" + metadata.issue_date);
+       }
+       root->add_child("RatingList");
+       xmlpp::Node* reel_list = root->add_child ("ReelList");
+       
+       for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
+               (*i)->write_to_cpl (reel_list);
+       }
++      if (crypt) {
++              sign (root, crypt->certificates, crypt->signer_key);
++      }
++
+       doc.write_to_file_formatted (p.string (), "UTF-8");
+       _digest = make_digest (p.string ());
+       _length = boost::filesystem::file_size (p.string ());
+ }
+ void
+ CPL::write_to_pkl (xmlpp::Node* node) const
+ {
+       xmlpp::Node* asset = node->add_child ("Asset");
+       asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
+       asset->add_child("Hash")->add_child_text (_digest);
+       asset->add_child("Size")->add_child_text (lexical_cast<string> (_length));
+       asset->add_child("Type")->add_child_text ("text/xml");
+ }
+ list<shared_ptr<const Asset> >
+ CPL::assets () const
+ {
+       list<shared_ptr<const Asset> > a;
+       for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
+               if ((*i)->main_picture ()) {
+                       a.push_back ((*i)->main_picture ());
+               }
+               if ((*i)->main_sound ()) {
+                       a.push_back ((*i)->main_sound ());
+               }
+               if ((*i)->main_subtitle ()) {
+                       a.push_back ((*i)->main_subtitle ());
+               }
+       }
+       return a;
+ }
+ void
+ CPL::write_to_assetmap (xmlpp::Node* node) const
+ {
+       xmlpp::Node* asset = node->add_child ("Asset");
+       asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
+       xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
+       xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
+       chunk->add_child("Path")->add_child_text (_uuid + "_cpl.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> (_length));
+ }
+       
+       
+       
+ bool
+ CPL::equals (CPL const & other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const
+ {
+       if (_name != other._name && !opt.cpl_names_can_differ) {
+               stringstream s;
+               s << "names differ: " << _name << " vs " << other._name << "\n";
+               note (ERROR, s.str ());
+               return false;
+       }
+       if (_content_kind != other._content_kind) {
+               note (ERROR, "content kinds differ");
+               return false;
+       }
+       if (_fps != other._fps) {
+               note (ERROR, "frames per second differ");
+               return false;
+       }
+       if (_length != other._length) {
+               note (ERROR, "lengths differ");
+               return false;
+       }
+       if (_reels.size() != other._reels.size()) {
+               note (ERROR, "reel counts differ");
+               return false;
+       }
+       
+       list<shared_ptr<const Reel> >::const_iterator a = _reels.begin ();
+       list<shared_ptr<const Reel> >::const_iterator b = other._reels.begin ();
+       
+       while (a != _reels.end ()) {
+               if (!(*a)->equals (*b, opt, note)) {
+                       return false;
+               }
+               ++a;
+               ++b;
+       }
+       return true;
+ }
++
++shared_ptr<xmlpp::Document>
++CPL::make_kdm (
++      CertificateChain const & certificates,
++      string const & signer_key,
++      shared_ptr<const Certificate> recipient_cert,
++      boost::posix_time::ptime from,
++      boost::posix_time::ptime until
++      ) const
++{
++      assert (recipient_cert);
++      
++      shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
++      xmlpp::Element* root = doc->create_root_node ("DCinemaSecurityMessage");
++      root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-3/2006/ETM", "");
++      root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
++      root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
++
++      {
++              xmlpp::Element* authenticated_public = root->add_child("AuthenticatedPublic");
++              authenticated_public->set_attribute("Id", "ID_AuthenticatedPublic");
++              xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPublic", authenticated_public->get_attribute("Id")->cobj());
++              
++              authenticated_public->add_child("MessageId")->add_child_text("urn:uuid:" + make_uuid());
++              authenticated_public->add_child("MessageType")->add_child_text("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
++              authenticated_public->add_child("AnnotationText")->add_child_text(Metadata::instance()->product_name);
++              authenticated_public->add_child("IssueDate")->add_child_text(Metadata::instance()->issue_date);
++
++              {
++                      xmlpp::Element* signer = authenticated_public->add_child("Signer");
++                      signer->add_child("X509IssuerName", "ds")->add_child_text (
++                              Certificate::name_for_xml (recipient_cert->issuer())
++                              );
++                      signer->add_child("X509SerialNumber", "ds")->add_child_text (
++                              recipient_cert->serial()
++                              );
++              }
++
++              {
++                      xmlpp::Element* required_extensions = authenticated_public->add_child("RequiredExtensions");
++
++                      {
++                              xmlpp::Element* kdm_required_extensions = required_extensions->add_child("KDMRequiredExtensions");
++                              kdm_required_extensions->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-1/2006/KDM");
++                              {
++                                      xmlpp::Element* recipient = kdm_required_extensions->add_child("Recipient");
++                                      {
++                                              xmlpp::Element* serial_element = recipient->add_child("X509IssuerSerial");
++                                              serial_element->add_child("X509IssuerName", "ds")->add_child_text (
++                                                      Certificate::name_for_xml (recipient_cert->issuer())
++                                                      );
++                                              serial_element->add_child("X509SerialNumber", "ds")->add_child_text (
++                                                      recipient_cert->serial()
++                                                      );
++                                      }
++
++                                      recipient->add_child("X509SubjectName")->add_child_text (Certificate::name_for_xml (recipient_cert->subject()));
++                              }
++
++                              kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text("urn:uuid:" + _uuid);
++                              kdm_required_extensions->add_child("ContentTitleText")->add_child_text(_name);
++                              kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text(certificates.leaf()->thumbprint());
++                              kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text("XXX");
++                              kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text("XXX");
++
++                              {
++                                      xmlpp::Element* authorized_device_info = kdm_required_extensions->add_child("AuthorizedDeviceInfo");
++                                      authorized_device_info->add_child("DeviceListIdentifier")->add_child_text("urn:uuid:" + make_uuid());
++                                      authorized_device_info->add_child("DeviceListDescription")->add_child_text(recipient_cert->subject());
++                                      {
++                                              xmlpp::Element* device_list = authorized_device_info->add_child("DeviceList");
++                                              device_list->add_child("CertificateThumbprint")->add_child_text(recipient_cert->thumbprint());
++                                      }
++                              }
++
++                              {
++                                      xmlpp::Element* key_id_list = kdm_required_extensions->add_child("KeyIdList");
++                                      list<shared_ptr<const Asset> > a = assets();
++                                      for (list<shared_ptr<const Asset> >::iterator i = a.begin(); i != a.end(); ++i) {
++                                              /* XXX: non-MXF assets? */
++                                              shared_ptr<const MXFAsset> mxf = boost::dynamic_pointer_cast<const MXFAsset> (*i);
++                                              if (mxf) {
++                                                      mxf->add_typed_key_id (key_id_list);
++                                              }
++                                      }
++                              }
++
++                              {
++                                      xmlpp::Element* forensic_mark_flag_list = kdm_required_extensions->add_child("ForensicMarkFlagList");
++                                      forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( 
++                                              "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"
++                                              );
++                                      forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( 
++                                              "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"
++                                              );
++                              }
++                      }
++              }
++                                       
++              authenticated_public->add_child("NonCriticalExtensions");
++      }
++
++      {
++              xmlpp::Element* authenticated_private = root->add_child("AuthenticatedPrivate");
++              authenticated_private->set_attribute ("Id", "ID_AuthenticatedPrivate");
++              xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPrivate", authenticated_private->get_attribute("Id")->cobj());
++              {
++                      xmlpp::Element* encrypted_key = authenticated_private->add_child ("EncryptedKey", "enc");
++                      {
++                              xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
++                              encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
++                              encryption_method->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
++                      }
++
++                      xmlpp::Element* cipher_data = authenticated_private->add_child ("CipherData", "enc");
++                      cipher_data->add_child("CipherValue", "enc")->add_child_text("XXX");
++              }
++      }
++      
++      /* XXX: x2 one for each mxf? */
++
++      {
++              xmlpp::Element* signature = root->add_child("Signature", "ds");
++              
++              {
++                      xmlpp::Element* signed_info = signature->add_child("SignedInfo", "ds");
++                      signed_info->add_child("CanonicalizationMethod", "ds")->set_attribute(
++                              "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
++                              );
++                      signed_info->add_child("SignatureMethod", "ds")->set_attribute(
++                              "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
++                              );
++                      {
++                              xmlpp::Element* reference = signed_info->add_child("Reference", "ds");
++                              reference->set_attribute("URI", "#ID_AuthenticatedPublic");
++                              reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
++                              reference->add_child("DigestValue", "ds");
++                      }
++                      
++                      {                               
++                              xmlpp::Element* reference = signed_info->add_child("Reference", "ds");
++                              reference->set_attribute("URI", "#ID_AuthenticatedPrivate");
++                              reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
++                              reference->add_child("DigestValue", "ds");
++                      }
++              }
++              
++              add_signature_value (signature, certificates, signer_key, "ds");
++      }
++
++      return doc;
++}
diff --cc src/cpl.h
index 0000000000000000000000000000000000000000,0c86b91e0547a941ea3a80fe7e3152f103a6f5d3..f9814337d7f6948b8711224ebf0ad66edd7c75c2
mode 000000,100644..100644
--- /dev/null
+++ b/src/cpl.h
@@@ -1,0 -1,100 +1,111 @@@
 -
+ /*
+     Copyright (C) 2012 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 <list>
+ #include <boost/shared_ptr.hpp>
+ #include <boost/function.hpp>
++#include <boost/date_time/posix_time/posix_time.hpp>
+ #include <libxml++/libxml++.h>
+ #include "types.h"
++#include "certificates.h"
+ namespace libdcp {
+ namespace parse {
+       class AssetMap;
+ }
+       
+ class Asset;
+ class Reel;
+ class XMLMetadata;
 -      void write_xml (XMLMetadata const &) const;
++      class Encryption;
++      
+ /** @brief A CPL within a DCP */
+ class CPL
+ {
+ public:
+       CPL (std::string directory, std::string name, ContentKind content_kind, int length, int frames_per_second);
+       CPL (std::string directory, std::string file, boost::shared_ptr<const parse::AssetMap> asset_map, bool require_mxfs = true);
+       void add_reel (boost::shared_ptr<const Reel> reel);
+       
+       /** @return the length in frames */
+       int length () const {
+               return _length;
+       }
+       /** @return the type of the content, used by media servers
+        *  to categorise things (e.g. feature, trailer, etc.)
+        */
+       ContentKind content_kind () const {
+               return _content_kind;
+       }
+       std::list<boost::shared_ptr<const Reel> > reels () const {
+               return _reels;
+       }
+       /** @return the CPL's name, as will be presented on projector
+        *  media servers and theatre management systems.
+        */
+       std::string name () const {
+               return _name;
+       }
+       /** @return the number of frames per second */
+       int frames_per_second () const {
+               return _fps;
+       }
+       std::list<boost::shared_ptr<const Asset> > assets () const;
+       
+       bool equals (CPL const & other, EqualityOptions options, boost::function<void (NoteType, std::string)> note) const;
+       
++      void write_xml (XMLMetadata const &, boost::shared_ptr<Encryption>) const;
+       void write_to_assetmap (xmlpp::Node *) const;
+       void write_to_pkl (xmlpp::Node *) const;
++
++      boost::shared_ptr<xmlpp::Document> make_kdm (
++              CertificateChain const &,
++              std::string const &,
++              boost::shared_ptr<const Certificate>,
++              boost::posix_time::ptime from,
++              boost::posix_time::ptime until
++              ) const;
+       
+ private:
+       std::string _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::list<boost::shared_ptr<const Reel> > _reels;
+       /** our UUID */
+       std::string _uuid;
+       /** a SHA1 digest of our XML */
+       mutable std::string _digest;
+ };
+ }
diff --cc src/dcp.cc
index 73c8a2cd422433c2b65fdac993f23698b4cd618a,c7634b5d3385d1a9cafd5b847dffd41dd4540d66..fdd7a1f5a756b565e8e9d50f6d8c6641a17148f2
  #include <cassert>
  #include <iostream>
  #include <boost/filesystem.hpp>
 +#include <boost/lexical_cast.hpp>
  #include <boost/algorithm/string.hpp>
+ #include <boost/lexical_cast.hpp>
  #include <libxml++/libxml++.h>
 +#include <xmlsec/xmldsig.h>
 +#include <xmlsec/app.h>
  #include "dcp.h"
  #include "asset.h"
  #include "sound_asset.h"
  #include "util.h"
  #include "metadata.h"
  #include "exceptions.h"
- #include "cpl_file.h"
- #include "pkl_file.h"
- #include "asset_map.h"
+ #include "parse/pkl.h"
+ #include "parse/asset_map.h"
  #include "reel.h"
+ #include "cpl.h"
++#include "encryption.h"
  
  using std::string;
  using std::list;
@@@ -60,21 -59,21 +63,21 @@@ DCP::DCP (string directory
  }
  
  void
- DCP::write_xml (shared_ptr<Encryption> crypt) const
 -DCP::write_xml (XMLMetadata const & metadata) const
++DCP::write_xml (XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const
  {
        for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
-               (*i)->write_xml (crypt);
 -              (*i)->write_xml (metadata);
++              (*i)->write_xml (metadata, crypt);
        }
  
        string pkl_uuid = make_uuid ();
-       string pkl_path = write_pkl (pkl_uuid, crypt);
 -      string pkl_path = write_pkl (pkl_uuid, metadata);
++      string pkl_path = write_pkl (pkl_uuid, metadata, crypt);
        
        write_volindex ();
-       write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path));
+       write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path), metadata);
  }
  
  std::string
- DCP::write_pkl (string pkl_uuid, shared_ptr<Encryption> crypt) const
 -DCP::write_pkl (string pkl_uuid, XMLMetadata const & metadata) const
++DCP::write_pkl (string pkl_uuid, XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const
  {
        assert (!_cpls.empty ());
        
        p /= s.str();
  
        xmlpp::Document doc;
 -      xmlpp::Element* root = doc.create_root_node ("PackingList", "http://www.smpte-ra.org/schemas/429-8/2007/PKL");
 +      xmlpp::Element* pkl = doc.create_root_node("PackingList", "http://www.smpte-ra.org/schemas/429-8/2007/PKL");
 +      if (crypt) {
 +              pkl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
 +      }
  
 -      root->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
 +      pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
        /* XXX: this is a bit of a hack */
 -      root->add_child("AnnotationText")->add_child_text (_cpls.front()->name());
 -      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);
 -
 -      xmlpp::Node* asset_list = root->add_child ("AssetList");
 +      pkl->add_child("AnnotationText")->add_child_text(_cpls.front()->name());
-       pkl->add_child("IssueDate")->add_child_text (Metadata::instance()->issue_date);
-       pkl->add_child("Issuer")->add_child_text (Metadata::instance()->issuer);
-       pkl->add_child("Creator")->add_child_text (Metadata::instance()->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);
-               }
++      pkl->add_child("IssueDate")->add_child_text (metadata.issue_date);
++      pkl->add_child("Issuer")->add_child_text (metadata.issuer);
++      pkl->add_child("Creator")->add_child_text (metadata.creator);
  
-               for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
-                       (*i)->write_to_pkl (asset_list);
-               }
++      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);
+       }
 -
++      
+       for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
+               (*i)->write_to_pkl (asset_list);
        }
  
-       doc.write_to_file_formatted (p.string(), "UTF-8");
 +      if (crypt) {
 +              sign (pkl, crypt->certificates, crypt->signer_key);
 +      }
 +              
+       doc.write_to_file_formatted (p.string (), "UTF-8");
        return p.string ();
  }
  
@@@ -280,7 -265,7 +275,6 @@@ DCP::equals (DCP const & other, Equalit
        return true;
  }
  
--
  void
  DCP::add_cpl (shared_ptr<CPL> cpl)
  {
@@@ -308,421 -293,4 +302,3 @@@ DCP::assets () cons
        a.unique ();
        return a;
  }
- CPL::CPL (string directory, string name, ContentKind content_kind, int length, int frames_per_second)
-       : _directory (directory)
-       , _name (name)
-       , _content_kind (content_kind)
-       , _length (length)
-       , _fps (frames_per_second)
- {
-       _uuid = make_uuid ();
- }
- /** 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_map The corresponding asset map.
-  *  @param require_mxfs true to throw an exception if a required MXF file does not exist.
-  */
- CPL::CPL (string directory, string file, shared_ptr<const AssetMap> asset_map, bool require_mxfs)
-       : _directory (directory)
-       , _content_kind (FEATURE)
-       , _length (0)
-       , _fps (0)
- {
-       /* Read the XML */
-       shared_ptr<CPLFile> cpl;
-       try {
-               cpl.reset (new CPLFile (file));
-       } catch (FileError& e) {
-               throw FileError ("could not load CPL file", file);
-       }
-       
-       /* Now cherry-pick the required bits into our own data structure */
-       
-       _name = cpl->annotation_text;
-       _content_kind = cpl->content_kind;
-       for (list<shared_ptr<CPLReel> >::iterator i = cpl->reels.begin(); i != cpl->reels.end(); ++i) {
-               shared_ptr<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) {
-                       try {
-                               picture.reset (new MonoPictureAsset (
-                                                      _directory,
-                                                      asset_map->asset_from_id (p->id)->chunks.front()->path,
-                                                      _fps,
-                                                      (*i)->asset_list->main_picture->entry_point,
-                                                      (*i)->asset_list->main_picture->duration
-                                                      )
-                                       );
-                       } catch (MXFFileError) {
-                               if (require_mxfs) {
-                                       throw;
-                               }
-                       }
-                       
-               } else {
-                       try {
-                               picture.reset (new StereoPictureAsset (
-                                                      _directory,
-                                                      asset_map->asset_from_id (p->id)->chunks.front()->path,
-                                                      _fps,
-                                                      p->entry_point,
-                                                      p->duration
-                                                      )
-                                       );
-                       } catch (MXFFileError) {
-                               if (require_mxfs) {
-                                       throw;
-                               }
-                       }
-                       
-               }
-               
-               if ((*i)->asset_list->main_sound) {
-                       
-                       try {
-                               sound.reset (new SoundAsset (
-                                                    _directory,
-                                                    asset_map->asset_from_id ((*i)->asset_list->main_sound->id)->chunks.front()->path,
-                                                    _fps,
-                                                    (*i)->asset_list->main_sound->entry_point,
-                                                    (*i)->asset_list->main_sound->duration
-                                                    )
-                                       );
-                       } catch (MXFFileError) {
-                               if (require_mxfs) {
-                                       throw;
-                               }
-                       }
-               }
-               if ((*i)->asset_list->main_subtitle) {
-                       
-                       subtitle.reset (new SubtitleAsset (
-                                               _directory,
-                                               asset_map->asset_from_id ((*i)->asset_list->main_subtitle->id)->chunks.front()->path
-                                               )
-                               );
-               }
-                       
-               _reels.push_back (shared_ptr<Reel> (new Reel (picture, sound, subtitle)));
-       }
- }
- void
- CPL::add_reel (shared_ptr<const Reel> reel)
- {
-       _reels.push_back (reel);
- }
- void
- CPL::write_xml (shared_ptr<Encryption> crypt) const
- {
-       boost::filesystem::path p;
-       p /= _directory;
-       stringstream s;
-       s << _uuid << "_cpl.xml";
-       p /= s.str();
-       xmlpp::Document doc;
-       xmlpp::Element* cpl = doc.create_root_node("CompositionPlaylist", "http://www.smpte-ra.org/schemas/429-7/2006/CPL");
-       if (crypt) {
-               cpl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
-       }
-       cpl->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
-       cpl->add_child("AnnotationText")->add_child_text (_name);
-       cpl->add_child("IssueDate")->add_child_text (Metadata::instance()->issue_date);
-       cpl->add_child("Creator")->add_child_text (Metadata::instance()->creator);
-       cpl->add_child("ContentTitleText")->add_child_text (_name);
-       cpl->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
-       {
-               xmlpp::Element* cv = cpl->add_child ("ContentVersion");
-               cv->add_child("Id")->add_child_text ("urn:uri:" + _uuid + "_" + Metadata::instance()->issue_date);
-               cv->add_child("LabelText")->add_child_text (_uuid + "_" + Metadata::instance()->issue_date);
-       }
-       cpl->add_child("RatingList");
-       xmlpp::Element* reel_list = cpl->add_child("ReelList");
-       for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
-               (*i)->write_to_cpl (reel_list);
-       }
-       if (crypt) {
-               sign (cpl, crypt->certificates, crypt->signer_key);
-       }
-       doc.write_to_file_formatted (p.string(), "UTF-8");
-       _digest = make_digest (p.string ());
-       _length = boost::filesystem::file_size (p.string ());
- }
- void
- CPL::write_to_pkl (xmlpp::Element* p) const
- {
-       xmlpp::Element* asset = p->add_child("Asset");
-       asset->add_child("Id")->add_child_text("urn:uuid:" + _uuid);
-       asset->add_child("Hash")->add_child_text(_digest);
-       asset->add_child("Size")->add_child_text(boost::lexical_cast<string> (_length));
-       asset->add_child("Type")->add_child_text("text/xml");
- }
- list<shared_ptr<const Asset> >
- CPL::assets () const
- {
-       list<shared_ptr<const Asset> > a;
-       for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
-               if ((*i)->main_picture ()) {
-                       a.push_back ((*i)->main_picture ());
-               }
-               if ((*i)->main_sound ()) {
-                       a.push_back ((*i)->main_sound ());
-               }
-               if ((*i)->main_subtitle ()) {
-                       a.push_back ((*i)->main_subtitle ());
-               }
-       }
-       return a;
- }
- void
- CPL::write_to_assetmap (ostream& s) const
- {
-       s << "    <Asset>\n"
-         << "      <Id>urn:uuid:" << _uuid << "</Id>\n"
-         << "      <ChunkList>\n"
-         << "        <Chunk>\n"
-         << "          <Path>" << _uuid << "_cpl.xml</Path>\n"
-         << "          <VolumeIndex>1</VolumeIndex>\n"
-         << "          <Offset>0</Offset>\n"
-         << "          <Length>" << _length << "</Length>\n"
-         << "        </Chunk>\n"
-         << "      </ChunkList>\n"
-         << "    </Asset>\n";
- }
-       
-       
-       
- bool
- CPL::equals (CPL const & other, EqualityOptions opt, list<string>& notes) const
- {
-       if (_name != other._name) {
-               notes.push_back ("names differ");
-               return false;
-       }
-       if (_content_kind != other._content_kind) {
-               notes.push_back ("content kinds differ");
-               return false;
-       }
-       if (_fps != other._fps) {
-               notes.push_back ("frames per second differ");
-               return false;
-       }
-       if (_length != other._length) {
-               notes.push_back ("lengths differ");
-               return false;
-       }
-       if (_reels.size() != other._reels.size()) {
-               notes.push_back ("reel counts differ");
-               return false;
-       }
-       
-       list<shared_ptr<const Reel> >::const_iterator a = _reels.begin ();
-       list<shared_ptr<const Reel> >::const_iterator b = other._reels.begin ();
-       
-       while (a != _reels.end ()) {
-               if (!(*a)->equals (*b, opt, notes)) {
-                       return false;
-               }
-               ++a;
-               ++b;
-       }
-       return true;
- }
- shared_ptr<xmlpp::Document>
- CPL::make_kdm (
-       CertificateChain const & certificates,
-       string const & signer_key,
-       shared_ptr<const Certificate> recipient_cert,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime until
-       ) const
- {
-       assert (recipient_cert);
-       
-       shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
-       xmlpp::Element* root = doc->create_root_node ("DCinemaSecurityMessage");
-       root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-3/2006/ETM", "");
-       root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
-       root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
-       {
-               xmlpp::Element* authenticated_public = root->add_child("AuthenticatedPublic");
-               authenticated_public->set_attribute("Id", "ID_AuthenticatedPublic");
-               xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPublic", authenticated_public->get_attribute("Id")->cobj());
-               
-               authenticated_public->add_child("MessageId")->add_child_text("urn:uuid:" + make_uuid());
-               authenticated_public->add_child("MessageType")->add_child_text("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
-               authenticated_public->add_child("AnnotationText")->add_child_text(Metadata::instance()->product_name);
-               authenticated_public->add_child("IssueDate")->add_child_text(Metadata::instance()->issue_date);
-               {
-                       xmlpp::Element* signer = authenticated_public->add_child("Signer");
-                       signer->add_child("X509IssuerName", "ds")->add_child_text (
-                               Certificate::name_for_xml (recipient_cert->issuer())
-                               );
-                       signer->add_child("X509SerialNumber", "ds")->add_child_text (
-                               recipient_cert->serial()
-                               );
-               }
-               {
-                       xmlpp::Element* required_extensions = authenticated_public->add_child("RequiredExtensions");
-                       {
-                               xmlpp::Element* kdm_required_extensions = required_extensions->add_child("KDMRequiredExtensions");
-                               kdm_required_extensions->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-1/2006/KDM");
-                               {
-                                       xmlpp::Element* recipient = kdm_required_extensions->add_child("Recipient");
-                                       {
-                                               xmlpp::Element* serial_element = recipient->add_child("X509IssuerSerial");
-                                               serial_element->add_child("X509IssuerName", "ds")->add_child_text (
-                                                       Certificate::name_for_xml (recipient_cert->issuer())
-                                                       );
-                                               serial_element->add_child("X509SerialNumber", "ds")->add_child_text (
-                                                       recipient_cert->serial()
-                                                       );
-                                       }
-                                       recipient->add_child("X509SubjectName")->add_child_text (Certificate::name_for_xml (recipient_cert->subject()));
-                               }
-                               kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text("urn:uuid:" + _uuid);
-                               kdm_required_extensions->add_child("ContentTitleText")->add_child_text(_name);
-                               kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text(certificates.leaf()->thumbprint());
-                               kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text("XXX");
-                               kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text("XXX");
-                               {
-                                       xmlpp::Element* authorized_device_info = kdm_required_extensions->add_child("AuthorizedDeviceInfo");
-                                       authorized_device_info->add_child("DeviceListIdentifier")->add_child_text("urn:uuid:" + make_uuid());
-                                       authorized_device_info->add_child("DeviceListDescription")->add_child_text(recipient_cert->subject());
-                                       {
-                                               xmlpp::Element* device_list = authorized_device_info->add_child("DeviceList");
-                                               device_list->add_child("CertificateThumbprint")->add_child_text(recipient_cert->thumbprint());
-                                       }
-                               }
-                               {
-                                       xmlpp::Element* key_id_list = kdm_required_extensions->add_child("KeyIdList");
-                                       list<shared_ptr<const Asset> > a = assets();
-                                       for (list<shared_ptr<const Asset> >::iterator i = a.begin(); i != a.end(); ++i) {
-                                               /* XXX: non-MXF assets? */
-                                               shared_ptr<const MXFAsset> mxf = boost::dynamic_pointer_cast<const MXFAsset> (*i);
-                                               if (mxf) {
-                                                       mxf->add_typed_key_id (key_id_list);
-                                               }
-                                       }
-                               }
-                               {
-                                       xmlpp::Element* forensic_mark_flag_list = kdm_required_extensions->add_child("ForensicMarkFlagList");
-                                       forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( 
-                                               "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"
-                                               );
-                                       forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( 
-                                               "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"
-                                               );
-                               }
-                       }
-               }
-                                        
-               authenticated_public->add_child("NonCriticalExtensions");
-       }
-       {
-               xmlpp::Element* authenticated_private = root->add_child("AuthenticatedPrivate");
-               authenticated_private->set_attribute ("Id", "ID_AuthenticatedPrivate");
-               xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPrivate", authenticated_private->get_attribute("Id")->cobj());
-               {
-                       xmlpp::Element* encrypted_key = authenticated_private->add_child ("EncryptedKey", "enc");
-                       {
-                               xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
-                               encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
-                               encryption_method->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
-                       }
-                       xmlpp::Element* cipher_data = authenticated_private->add_child ("CipherData", "enc");
-                       cipher_data->add_child("CipherValue", "enc")->add_child_text("XXX");
-               }
-       }
-       
-       /* XXX: x2 one for each mxf? */
-       {
-               xmlpp::Element* signature = root->add_child("Signature", "ds");
-               
-               {
-                       xmlpp::Element* signed_info = signature->add_child("SignedInfo", "ds");
-                       signed_info->add_child("CanonicalizationMethod", "ds")->set_attribute(
-                               "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
-                               );
-                       signed_info->add_child("SignatureMethod", "ds")->set_attribute(
-                               "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
-                               );
-                       {
-                               xmlpp::Element* reference = signed_info->add_child("Reference", "ds");
-                               reference->set_attribute("URI", "#ID_AuthenticatedPublic");
-                               reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
-                               reference->add_child("DigestValue", "ds");
-                       }
-                       
-                       {                               
-                               xmlpp::Element* reference = signed_info->add_child("Reference", "ds");
-                               reference->set_attribute("URI", "#ID_AuthenticatedPrivate");
-                               reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
-                               reference->add_child("DigestValue", "ds");
-                       }
-               }
-               
-               add_signature_value (signature, certificates, signer_key, "ds");
-       }
--
-       return doc;
- }
diff --cc src/dcp.h
index 5a9150196c9b5088173a50b5575328f4c4024213,eea043dd8ff1de9e87c1461f8b3b185d5b57ee82..42c48387cee6591808c34cee14c33e429d4c1813
+++ b/src/dcp.h
  #include <vector>
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
- #include <boost/date_time/posix_time/posix_time.hpp>
  #include "types.h"
 +#include "certificates.h"
  
  namespace xmlpp {
 -      class Node;
 +      class Document;
 +      class Element;
  }
  
  /** @brief Namespace for everything in libdcp */
@@@ -46,93 -43,10 +45,11 @@@ class PictureAsset
  class SoundAsset;
  class SubtitleAsset;
  class Reel;
- class AssetMap;
+ class CPL;
+ class XMLMetadata;
++class Encryption;
  
- class Encryption
- {
- public:
-       Encryption (CertificateChain c, std::string const & k)
-               : certificates (c)
-               , signer_key (k)
-       {}
-       CertificateChain certificates;
-       std::string signer_key;
- };
- /** @brief A CPL within a DCP */
- class CPL
- {
- public:
-       CPL (std::string directory, std::string name, ContentKind content_kind, int length, int frames_per_second);
-       CPL (std::string directory, std::string file, boost::shared_ptr<const AssetMap> asset_map, bool require_mxfs = true);
-       void add_reel (boost::shared_ptr<const Reel> reel);
-       
-       /** @return the length in frames */
-       int length () const {
-               return _length;
-       }
-       /** @return the type of the content, used by media servers
-        *  to categorise things (e.g. feature, trailer, etc.)
-        */
-       ContentKind content_kind () const {
-               return _content_kind;
-       }
-       std::list<boost::shared_ptr<const Reel> > reels () const {
-               return _reels;
-       }
-       /** @return the CPL's name, as will be presented on projector
-        *  media servers and theatre management systems.
-        */
-       std::string name () const {
-               return _name;
-       }
-       /** @return the number of frames per second */
-       int frames_per_second () const {
-               return _fps;
-       }
-       std::list<boost::shared_ptr<const Asset> > assets () const;
-       
-       bool equals (CPL const & other, EqualityOptions options, std::list<std::string>& notes) const;
-       
-       void write_xml (boost::shared_ptr<Encryption>) const;
-       void write_to_assetmap (std::ostream& s) const;
-       void write_to_pkl (xmlpp::Element* p) const;
-       boost::shared_ptr<xmlpp::Document> make_kdm (
-               CertificateChain const &,
-               std::string const &,
-               boost::shared_ptr<const Certificate>,
-               boost::posix_time::ptime from,
-               boost::posix_time::ptime until
-               ) const;
-       
- private:
-       std::string _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::list<boost::shared_ptr<const Reel> > _reels;
-       /** our UUID */
-       std::string _uuid;
-       /** a SHA1 digest of our XML */
-       mutable std::string _digest;
- };
- /** @class DCP dcp.h libdcp/dcp.h
+ /** @class DCP
   *  @brief A class to create or read a DCP.
   */
        
@@@ -160,7 -74,7 +77,7 @@@ public
        /** Write the required XML files to the directory that was
         *  passed into the constructor.
         */
-       void write_xml (boost::shared_ptr<Encryption> crypt = boost::shared_ptr<Encryption> ()) const;
 -      void write_xml (XMLMetadata const &) const;
++      void write_xml (XMLMetadata const &, boost::shared_ptr<Encryption> crypt = boost::shared_ptr<Encryption> ()) const;
  
        /** Compare this DCP with another, according to various options.
         *  @param other DCP to compare this one to.
@@@ -190,7 -103,7 +106,7 @@@ private
        /** Write the PKL file.
         *  @param pkl_uuid UUID to use.
         */
-       std::string write_pkl (std::string pkl_uuid, boost::shared_ptr<Encryption>) const;
 -      std::string write_pkl (std::string pkl_uuid, XMLMetadata const &) const;
++      std::string write_pkl (std::string pkl_uuid, XMLMetadata const &, boost::shared_ptr<Encryption>) const;
        
        /** Write the VOLINDEX file */
        void write_volindex () const;
diff --cc src/dcp_time.cc
Simple merge
index 6ba42d754f5ed7a15edd7ebbddf4bd03edd53175,cfb02c8599421745992fc12e7eb819539339cf33..d52ab2cc45f318a36aba60e3bc1dbf3c763eb76a
@@@ -39,62 -36,30 +39,68 @@@ using boost::shared_ptr
  using boost::dynamic_pointer_cast;
  using namespace libdcp;
  
- MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted)
+ MXFAsset::MXFAsset (string directory, string file_name)
        : Asset (directory, file_name)
 -MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration)
+       , _progress (0)
++      , _encrypted (false)
++      , _encryption_context (0)
+ {
+ }
++MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration, bool encrypted)
+       : Asset (directory, file_name, edit_rate, intrinsic_duration)
        , _progress (progress)
-       , _fps (fps)
-       , _entry_point (entry_point)
-       , _length (length)
 +      , _encrypted (encrypted)
 +      , _encryption_context (0)
  {
 -      
 +      if (_encrypted) {
 +              _key_id = make_uuid ();
 +              uint8_t key_buffer[ASDCP::KeyLen];
 +              Kumu::FortunaRNG rng;
 +              rng.FillRandom (key_buffer, ASDCP::KeyLen);
 +              char key_string[ASDCP::KeyLen * 4];
 +              Kumu::bin2hex (key_buffer, ASDCP::KeyLen, key_string, ASDCP::KeyLen * 4);
 +              _key_value = key_string;
 +                      
 +              _encryption_context = new ASDCP::AESEncContext;
 +              if (ASDCP_FAILURE (_encryption_context->InitKey (key_buffer))) {
 +                      throw MiscError ("could not set up encryption context");
 +              }
 +
 +              uint8_t cbc_buffer[ASDCP::CBC_BLOCK_SIZE];
 +              
 +              if (ASDCP_FAILURE (_encryption_context->SetIVec (rng.FillRandom (cbc_buffer, ASDCP::CBC_BLOCK_SIZE)))) {
 +                      throw MiscError ("could not set up CBC initialization vector");
 +              }
 +      }
 +}
 +
 +MXFAsset::~MXFAsset ()
 +{
 +      delete _encryption_context;
  }
 +
  void
- MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info) const
+ MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info, string uuid, MXFMetadata const & metadata)
  {
-       writer_info->ProductVersion = Metadata::instance()->product_version;
-       writer_info->CompanyName = Metadata::instance()->company_name;
-       writer_info->ProductName = Metadata::instance()->product_name.c_str();
+       writer_info->ProductVersion = metadata.product_version;
+       writer_info->CompanyName = metadata.company_name;
+       writer_info->ProductName = metadata.product_name.c_str();
  
        writer_info->LabelSetType = ASDCP::LS_MXF_SMPTE;
        unsigned int c;
-       Kumu::hex2bin (_uuid.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c);
+       Kumu::hex2bin (uuid.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c);
        assert (c == Kumu::UUID_Length);
 +
 +      if (_encrypted) {
 +              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
@@@ -107,33 -76,11 +117,19 @@@ MXFAsset::equals (shared_ptr<const Asse
        }
        
        if (_file_name != other_mxf->_file_name) {
-               notes.push_back ("MXF names differ");
-               return false;
-       }
-       if (_fps != other_mxf->_fps) {
-               notes.push_back ("MXF frames per second differ");
-               return false;
+               note (ERROR, "MXF names differ");
+               if (!opt.mxf_names_can_differ) {
+                       return false;
+               }
        }
        
-       if (_length != other_mxf->_length) {
-               notes.push_back ("MXF lengths differ");
-               return false;
-       }
        return true;
  }
- int
- MXFAsset::length () const
- {
-       return _length;
- }
 +
 +void
 +MXFAsset::add_typed_key_id (xmlpp::Element* parent) const
 +{
 +      xmlpp::Element* typed_key_id = parent->add_child("TypedKeyId");
 +      typed_key_id->add_child("KeyType")->add_child_text(key_type ());
 +      typed_key_id->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id);
 +}
diff --cc src/mxf_asset.h
index 93fa901304d4de9c1bc423766e8e4819173f1515,55aa889a10d091c22e5212735a57ae2de6557172..a23a052dafd91643b3c533eb22738a2b9efd06ea
@@@ -35,44 -33,36 +37,45 @@@ class MXFAsset : public Asse
  {
  public:
        /** Construct an MXFAsset.
+        *  This class will not write anything to disk in this constructor, but subclasses may.
+        *
+        *  @param directory Directory where MXF file is.
+        *  @param file_name Name of MXF file.
+        */
+       MXFAsset (std::string directory, std::string file_name);
+       
+       /** Construct an MXFAsset.
+        *  This class will not write anything to disk in this constructor, but subclasses may.
+        *
         *  @param directory Directory where MXF file is.
         *  @param file_name Name of MXF file.
-        *  @param progress Signal to inform of progress.
-        *  @param fps Frames per second.
-        *  @param entry_point The entry point of this MXF; ie the first frame that should be used.
-        *  @param length Length in frames.
-        *  @param encrypted true if the MXF should be encrypted.
+        *  @param progress Signal to use to inform of progress, or 0.
+        *  @param edit_rate Edit rate in frames per second (usually equal to the video frame rate).
+        *  @param intrinsic_duration Duration of the whole asset in frames.
         */
-       MXFAsset (
-               std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted
-               );
 -      MXFAsset (std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration);
++      MXFAsset (std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration, bool encrypted);
 +
 +      ~MXFAsset ();
  
-       virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const;
-       
-       int length () const;
-       void add_typed_key_id (xmlpp::Element *) const;
+       virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const;
  
- protected:
-       virtual std::string key_type () const = 0;
-       
        /** Fill in a ADSCP::WriteInfo struct.
         *  @param w struct to fill in.
+        *  @param uuid uuid to use.
         */
-       void fill_writer_info (ASDCP::WriterInfo* w) const;
 -      static void fill_writer_info (ASDCP::WriterInfo* w, std::string uuid, MXFMetadata const & metadata);
++      void fill_writer_info (ASDCP::WriterInfo* w, std::string uuid, MXFMetadata const & metadata);
  
-       /** Signal to emit to report progress */
++      void add_typed_key_id (xmlpp::Element *) const;
++      
+ protected:
++      virtual std::string key_type () const = 0;
+       
+       /** Signal to emit to report progress, or 0 */
        boost::signals2::signal<void (float)>* _progress;
-       /** Frames per second */
-       int _fps;
-       int _entry_point;
-       /** Length in frames */
-       int _length;
 +      bool _encrypted;
 +      ASDCP::AESEncContext* _encryption_context;
 +      std::string _key_value;
 +      std::string _key_id;
  };
  
  }
index f783bb3972added3c5aa02f9e7a2d594de9728ec,b5f59f3be0ce04aa3dd19b757c92e62c9657cccc..c6a95c74509b7969bafcd1904e7bede9a64c3b27
@@@ -42,37 -41,40 +42,44 @@@ using std::ostream
  using std::list;
  using std::vector;
  using std::max;
 +using std::stringstream;
+ using std::pair;
+ using std::make_pair;
+ using std::istream;
+ using std::cout;
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
  using boost::lexical_cast;
  using namespace libdcp;
  
- PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted)
-       : MXFAsset (directory, mxf_name, progress, fps, entry_point, length, encrypted)
-       , _width (0)
-       , _height (0)
 -PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, Size size)
 -      : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration)
++PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, bool encrypted, Size size)
++      : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted)
+       , _size (size)
+ {
+ }
+ PictureAsset::PictureAsset (string directory, string mxf_name)
+       : MXFAsset (directory, mxf_name)
  {
  
  }
  
  void
- PictureAsset::write_to_cpl (xmlpp::Element* parent) const
+ PictureAsset::write_to_cpl (xmlpp::Node* node) const
  {
-       xmlpp::Element* main_picture = parent->add_child("MainPicture");
-       main_picture->add_child("Id")->add_child_text("urn:uuid:" + _uuid);
-       main_picture->add_child("AnnotationText")->add_child_text(_file_name);
-       main_picture->add_child("EditRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1");
-       main_picture->add_child("IntrinsicDuration")->add_child_text(boost::lexical_cast<string> (_length));
-       main_picture->add_child("EntryPoint")->add_child_text("0");
-       main_picture->add_child("Duration")->add_child_text(boost::lexical_cast<string> (_length));
+       xmlpp::Node* mp = node->add_child ("MainPicture");
+       mp->add_child ("Id")->add_child_text ("urn:uuid:" + _uuid);
+       mp->add_child ("AnnotationText")->add_child_text (_file_name);
+       mp->add_child ("EditRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1");
+       mp->add_child ("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration));
+       mp->add_child ("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point));
+       mp->add_child ("Duration")->add_child_text (lexical_cast<string> (_duration));
 +      if (_encrypted) {
-               main_picture->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id);
++              mp->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id);
 +      }
-       main_picture->add_child("FrameRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1");
-       stringstream sar;
-       sar << _width << " " << _height;
-       main_picture->add_child("ScreenAspectRatio")->add_child_text(sar.str());
+       mp->add_child ("FrameRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1");
+       mp->add_child ("ScreenAspectRatio")->add_child_text (lexical_cast<string> (_size.width) + " " + lexical_cast<string> (_size.height));
  }
  
  bool
@@@ -142,15 -144,13 +149,14 @@@ MonoPictureAsset::MonoPictureAsset 
        string mxf_name,
        boost::signals2::signal<void (float)>* progress,
        int fps,
-       int length,
-       int width,
-       int height,
-       bool encrypted)
-       : PictureAsset (directory, mxf_name, progress, fps, 0, length, encrypted)
+       int intrinsic_duration,
++      bool encrypted,
+       Size size,
+       MXFMetadata const & metadata
+       )
 -      : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, size)
++      : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted, size)
  {
-       _width = width;
-       _height = height;
-       construct (get_path);
+       construct (get_path, metadata);
  }
  
  MonoPictureAsset::MonoPictureAsset (
        string mxf_name,
        boost::signals2::signal<void (float)>* progress,
        int fps,
-       int length,
-       int width,
-       int height,
-       bool encrypted)
-       : PictureAsset (directory, mxf_name, progress, fps, 0, length, encrypted)
+       int intrinsic_duration,
++      bool encrypted,
+       Size size,
+       MXFMetadata const & metadata
+       )
 -      : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, size)
++      : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted, size)
+ {
+       construct (boost::bind (&MonoPictureAsset::path_from_list, this, _1, files), metadata);
+ }
+ MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name, int fps, Size size)
 -      : PictureAsset (directory, mxf_name, 0, fps, 0, size)
++      : PictureAsset (directory, mxf_name, 0, fps, 0, false, size)
  {
-       _width = width;
-       _height = height;
-       construct (boost::bind (&MonoPictureAsset::path_from_list, this, _1, files));
  }
  
- MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name, int fps, int entry_point, int length)
-       : PictureAsset (directory, mxf_name, 0, fps, entry_point, length, false)
+ MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name)
+       : PictureAsset (directory, mxf_name)
  {
        ASDCP::JP2K::MXFReader reader;
        if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) {
@@@ -213,11 -220,11 +227,11 @@@ MonoPictureAsset::construct (boost::fun
                string const path = get_path (i);
  
                if (ASDCP_FAILURE (j2k_parser.OpenReadFrame (path.c_str(), frame_buffer))) {
-                       throw FileError ("could not open JPEG2000 file for reading", path);
+                       boost::throw_exception (FileError ("could not open JPEG2000 file for reading", path));
                }
  
 -              if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, 0, 0))) {
 +              if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) {
-                       throw MiscError ("error in writing video MXF");
+                       boost::throw_exception (MXFFileError ("error in writing video MXF", this->path().string()));
                }
  
                if (_progress) {
@@@ -369,8 -386,8 +393,8 @@@ PictureAsset::frame_buffer_equals 
  }
  
  
- StereoPictureAsset::StereoPictureAsset (string directory, string mxf_name, int fps, int entry_point, int length)
-       : PictureAsset (directory, mxf_name, 0, fps, entry_point, length, false)
+ StereoPictureAsset::StereoPictureAsset (string directory, string mxf_name, int fps, int intrinsic_duration)
 -      : PictureAsset (directory, mxf_name, 0, fps, intrinsic_duration, Size (0, 0))
++      : PictureAsset (directory, mxf_name, 0, fps, intrinsic_duration, false, Size (0, 0))
  {
        ASDCP::JP2K::MXFSReader reader;
        if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) {
  shared_ptr<const StereoPictureFrame>
  StereoPictureAsset::get_frame (int n) const
  {
-       return shared_ptr<const StereoPictureFrame> (new StereoPictureFrame (path().string(), n + _entry_point));
+       return shared_ptr<const StereoPictureFrame> (new StereoPictureFrame (path().string(), n));
+ }
+ shared_ptr<MonoPictureAssetWriter>
+ MonoPictureAsset::start_write (bool overwrite, MXFMetadata const & metadata)
+ {
+       /* XXX: can't we use shared_ptr here? */
+       return shared_ptr<MonoPictureAssetWriter> (new MonoPictureAssetWriter (this, overwrite, metadata));
+ }
+ FrameInfo::FrameInfo (istream& s)
+ {
+       s >> offset >> size >> hash;
+ }
+ void
+ FrameInfo::write (ostream& s)
+ {
+       s << offset << " " << size << " " << hash;
+ }
+ struct MonoPictureAssetWriter::ASDCPState
+ {
+       ASDCPState()
+               : frame_buffer (4 * Kumu::Megabyte)
+       {}
+       
+       ASDCP::JP2K::CodestreamParser j2k_parser;
+       ASDCP::JP2K::FrameBuffer frame_buffer;
+       ASDCP::JP2K::MXFWriter mxf_writer;
+       ASDCP::WriterInfo writer_info;
+       ASDCP::JP2K::PictureDescriptor picture_descriptor;
+ };
+ /** @param a Asset to write to.  `a' must not be deleted while
+  *  this writer class still exists, or bad things will happen.
+  */
+ MonoPictureAssetWriter::MonoPictureAssetWriter (MonoPictureAsset* a, bool overwrite, MXFMetadata const & m)
+       : _state (new MonoPictureAssetWriter::ASDCPState)
+       , _asset (a)
+       , _frames_written (0)
+       , _started (false)
+       , _finalized (false)
+       , _overwrite (overwrite)
+       , _metadata (m)
+ {
+ }
+ void
+ MonoPictureAssetWriter::start (uint8_t* data, int size)
+ {
+       if (ASDCP_FAILURE (_state->j2k_parser.OpenReadFrame (data, size, _state->frame_buffer))) {
+               boost::throw_exception (MiscError ("could not parse J2K frame"));
+       }
+       _state->j2k_parser.FillPictureDescriptor (_state->picture_descriptor);
+       _state->picture_descriptor.EditRate = ASDCP::Rational (_asset->edit_rate(), 1);
+       
 -      MXFAsset::fill_writer_info (&_state->writer_info, _asset->uuid(), _metadata);
++      _asset->fill_writer_info (&_state->writer_info, _asset->uuid(), _metadata);
+       
+       if (ASDCP_FAILURE (_state->mxf_writer.OpenWrite (
+                                  _asset->path().string().c_str(),
+                                  _state->writer_info,
+                                  _state->picture_descriptor,
+                                  16384,
+                                  _overwrite)
+                   )) {
+               
+               boost::throw_exception (MXFFileError ("could not open MXF file for writing", _asset->path().string()));
+       }
+       _started = true;
+ }
+ FrameInfo
+ MonoPictureAssetWriter::write (uint8_t* data, int size)
+ {
+       assert (!_finalized);
+       if (!_started) {
+               start (data, size);
+       }
+       if (ASDCP_FAILURE (_state->j2k_parser.OpenReadFrame (data, size, _state->frame_buffer))) {
+               boost::throw_exception (MiscError ("could not parse J2K frame"));
+       }
+       uint64_t const before_offset = _state->mxf_writer.Tell ();
+       string hash;
+       if (ASDCP_FAILURE (_state->mxf_writer.WriteFrame (_state->frame_buffer, 0, 0, &hash))) {
+               boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string()));
+       }
+       ++_frames_written;
+       return FrameInfo (before_offset, _state->mxf_writer.Tell() - before_offset, hash);
+ }
+ void
+ MonoPictureAssetWriter::fake_write (int size)
+ {
+       assert (_started);
+       assert (!_finalized);
+       if (ASDCP_FAILURE (_state->mxf_writer.FakeWriteFrame (size))) {
+               boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string()));
+       }
+       ++_frames_written;
+ }
+ void
+ MonoPictureAssetWriter::finalize ()
+ {
+       assert (!_finalized);
+       
+       if (ASDCP_FAILURE (_state->mxf_writer.Finalize())) {
+               boost::throw_exception (MXFFileError ("error in finalizing video MXF", _asset->path().string()));
+       }
+       _finalized = true;
+       _asset->set_intrinsic_duration (_frames_written);
+       _asset->set_duration (_frames_written);
  }
 +
 +string
 +PictureAsset::key_type () const
 +{
 +      return "MDIK";
 +}
index 96bf5659db44d9ae784e2d70d39043393c1d9e3d,8536bacc9f6005e0eeb7a36eeff9773f7bad2cc5..08f892ed34e2f868b2a7937cf707aaa60fdc6d25
@@@ -34,39 -39,109 +39,111 @@@ class StereoPictureFrame
  class PictureAsset : public MXFAsset
  {
  public:
-       PictureAsset (
-               std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted
-               );
+       /** Construct a PictureAsset.
+        *  This class will not write anything to disk in this constructor, but subclasses may.
+        *  
+        *  @param directory Directory where MXF file is.
+        *  @param mxf_name Name of MXF file.
+        */
+       PictureAsset (std::string directory, std::string mxf_name);
+       /** Construct a PictureAsset.
+        *  This class will not write anything to disk in this constructor, but subclasses may.
+        *
+        *  @param directory Directory where MXF file is.
+        *  @param mxf_name Name of MXF file.
+        *  @param progress Signal to use to inform of progres, or 0.
+        *  @param fps Video Frames per second.
+        *  @param intrinsic_duration Duration of all the frames in the asset.
+        *  @param size Size of video frame images in pixels.
+        */
 -      PictureAsset (std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, Size size);
++      PictureAsset (std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, bool encrypted, Size);
        
-       /** Write details of the asset to a CPL AssetList node.
-        *  @param p Parent node.
+       /** Write details of this asset to a CPL XML node.
+        *  @param node Node.
         */
-       void write_to_cpl (xmlpp::Element* p) const;
+       void write_to_cpl (xmlpp::Node* node) const;
  
-       bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const;
+       bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const;
  
-       int width () const {
-               return _width;
-       }
-       int height () const {
-               return _height;
+       Size size () const {
+               return _size;
        }
  
  protected:    
  
++      std::string key_type () const;
++      
        bool frame_buffer_equals (
-               int frame, EqualityOptions opt, std::list<std::string>& notes,
+               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;
+       /** picture size in pixels */
+       Size _size;
+ };
+ class MonoPictureAsset;
+ struct FrameInfo
+ {
+       FrameInfo (uint64_t o, uint64_t s, std::string h)
+               : offset (o)
+               , size (s)
+               , hash (h)
+       {}
+       FrameInfo (std::istream& s);
+       void write (std::ostream& s);
        
-       /** picture width in pixels */
-       int _width;
-       /** picture height in pixels */
-       int _height;
+       uint64_t offset;
+       uint64_t size;
+       std::string hash;
+ };
+ /** A helper class for writing to MonoPictureAssets progressively (i.e. writing frame-by-frame,
+  *  rather than giving libdcp all the frames in one go).
+  *
+  *  Objects of this class can only be created with MonoPictureAsset::start_write().
+  *
+  *  Frames can be written to the MonoPictureAsset by calling write() with a JPEG2000 image
+  *  (a verbatim .j2 file).  finalize() must be called after the last frame has been written.
+  *  The action of finalize() can't be done in MonoPictureAssetWriter's destructor as it may
+  *  throw an exception.
+  */
+ class MonoPictureAssetWriter
+ {
+ public:
+       FrameInfo write (uint8_t* data, int size);
+       void fake_write (int size);
+       void finalize ();
  
  private:
-       std::string key_type () const;
+       friend class MonoPictureAsset;
+       MonoPictureAssetWriter (MonoPictureAsset *, bool, MXFMetadata const &);
+       void start (uint8_t *, int);
+       /* no copy construction */
+       MonoPictureAssetWriter (MonoPictureAssetWriter const &);
+       MonoPictureAssetWriter& operator= (MonoPictureAssetWriter const &);
+       /* do this with an opaque pointer so we don't have to include
+          ASDCP headers
+       */
+          
+       struct ASDCPState;
+       boost::shared_ptr<ASDCPState> _state;
+       MonoPictureAsset* _asset;
+       /** Number of picture frames written to the asset so far */
+       int _frames_written;
+       bool _started;
+       /** true if finalize() has been called */
+       bool _finalized;
+       bool _overwrite;
+       MXFMetadata _metadata;
  };
  
  /** A 2D (monoscopic) picture asset */
@@@ -79,11 -155,9 +157,10 @@@ public
         *  @param directory Directory in which to create MXF file.
         *  @param mxf_name Name of MXF file to create.
         *  @param progress Signal to inform of progress.
-        *  @param fps Frames per second.
-        *  @param length Length in frames.
-        *  @param width Width of images in pixels.
-        *  @param height Height of images in pixels.
+        *  @param fps Video frames per second.
+        *  @param intrinsic_duration Length of the whole asset in frames.
+        *  @param size Size of images in pixels.
 +       *  @param encrypted true if asset should be encrypted.
         */
        MonoPictureAsset (
                std::vector<std::string> const & files,
                std::string mxf_name,
                boost::signals2::signal<void (float)>* progress,
                int fps,
-               int length,
-               int width,
-               int height,
-               bool encrypted
+               int intrinsic_duration,
++              bool encrypted,
+               Size size,
+               MXFMetadata const & metadata = MXFMetadata ()
                );
  
-       /** Construct a PictureAsset, generating the MXF from the JPEG2000 files.
+       /** Construct a MonoPictureAsset, generating the MXF from the JPEG2000 files.
         *  This may take some time; progress is indicated by emission of the Progress signal.
+        *
         *  @param get_path Functor which returns a JPEG2000 file path for a given frame (frames counted from 0).
         *  @param directory Directory in which to create MXF file.
         *  @param mxf_name Name of MXF file to create.
         *  @param progress Signal to inform of progress.
-        *  @param fps Frames per second.
-        *  @param length Length in frames.
-        *  @param width Width of images in pixels.
-        *  @param height Height of images in pixels.
+        *  @param fps Video frames per second.
+        *  @param intrinsic_duration Length of the whole asset in frames.
+        *  @param size Size of images in pixels.
 +       *  @param encrypted true if asset should be encrypted.
         */
        MonoPictureAsset (
                boost::function<std::string (int)> get_path,
                std::string mxf_name,
                boost::signals2::signal<void (float)>* progress,
                int fps,
-               int length,
-               int width,
-               int height,
-               bool encrypted
+               int intrinsic_duration,
++              bool encrypted,
+               Size size,
+               MXFMetadata const & metadata = MXFMetadata ()
                );
  
-       MonoPictureAsset (std::string directory, std::string mxf_name, int fps, int entry_point, int length);
-       
+       /** Construct a MonoPictureAsset, reading the MXF from disk.
+        *  @param directory Directory that the MXF is in.
+        *  @param mxf_name The filename of the MXF within `directory'.
+        */
+       MonoPictureAsset (std::string directory, std::string mxf_name);
+       /** Construct a MonoPictureAsset for progressive writing using
+        *  start_write() and a MonoPictureAssetWriter.
+        *
+        *  @param directory Directory to put the MXF in.
+        *  @param mxf_name Filename of the MXF within this directory.
+        *  @param fps Video frames per second.
+        *  @param size Size in pixels that the picture frames will be.
+        */
+       MonoPictureAsset (std::string directory, std::string mxf_name, int fps, Size size);
+       /** Start a progressive write to a MonoPictureAsset */
+       boost::shared_ptr<MonoPictureAssetWriter> start_write (bool, MXFMetadata const & metadata = MXFMetadata ());
        boost::shared_ptr<const MonoPictureFrame> get_frame (int n) const;
-       bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const;
+       bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const;
  
  private:
        std::string path_from_list (int f, std::vector<std::string> const & files) const;
diff --cc src/reel.cc
Simple merge
diff --cc src/reel.h
index 0ad5ace2bc54f1a7582e53c05e95f2b9ef229f0e,49a756adc1206bccb63e57a51d11c424b48dc4a8..00278a143e4c0d13f6a3a29995d6bc9a2801ff7d
  
  #include <list>
  #include <boost/shared_ptr.hpp>
+ #include <boost/function.hpp>
+ #include <libxml++/libxml++.h>
  #include "types.h"
  
 +namespace xmlpp {
 +      class Node;
 +}
 +
  namespace libdcp {
  
  class PictureAsset;
index d964a46d80601e56c74179ade40ca974fccf0ca2,f59d82ad647b8f957b456a88e375f8d181433bb8..45a656462c8ab6e54f6374d8332f5b892199dcba
@@@ -47,15 -46,12 +47,14 @@@ SoundAsset::SoundAsset 
        string directory,
        string mxf_name,
        boost::signals2::signal<void (float)>* progress,
 -      int fps, int intrinsic_duration,
 +      int fps,
-       int length,
-       int start_frame,
-       bool encrypted
++      int intrinsic_duration,
++      bool encrypted,
+       MXFMetadata const & metadata
        )
-       : MXFAsset (directory, mxf_name, progress, fps, 0, length, encrypted)
 -      : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration)
++      : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted)
        , _channels (files.size ())
        , _sampling_rate (0)
-       , _start_frame (start_frame)
  {
        assert (_channels);
        
@@@ -67,16 -63,12 +66,15 @@@ SoundAsset::SoundAsset 
        string directory,
        string mxf_name,
        boost::signals2::signal<void (float)>* progress,
 -      int fps, int intrinsic_duration, int channels,
 +      int fps,
-       int length,
-       int start_frame,
++      int intrinsic_duration,
 +      int channels,
-       bool encrypted
++      bool encrypted,
+       MXFMetadata const & metadata
        )
-       : MXFAsset (directory, mxf_name, progress, fps, 0, length, encrypted)
 -      : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration)
++      : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted)
        , _channels (channels)
        , _sampling_rate (0)
-       , _start_frame (start_frame)
  {
        assert (_channels);
        
@@@ -101,6 -91,17 +97,17 @@@ SoundAsset::SoundAsset (string director
  
        _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
        _channels = desc.ChannelCount;
 -      : MXFAsset (directory, mxf_name, 0, fps, 0)
+       _edit_rate = desc.EditRate.Numerator;
+       assert (desc.EditRate.Denominator == 1);
+       _intrinsic_duration = desc.ContainerDuration;
+ }
+ SoundAsset::SoundAsset (string directory, string mxf_name, int fps, int channels, int sampling_rate)
++      : MXFAsset (directory, mxf_name, 0, fps, 0, false)
+       , _channels (channels)
+       , _sampling_rate (sampling_rate)
+ {
  }
  
  string
@@@ -204,8 -196,8 +202,8 @@@ SoundAsset::construct (boost::function<
                        offset += sample_size;
                }
  
 -              if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, 0, 0))) {
 +              if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) {
-                       throw MiscError ("could not write audio MXF frame");
+                       boost::throw_exception (MiscError ("could not write audio MXF frame"));
                }
  
                if (_progress) {
  }
  
  void
- SoundAsset::write_to_cpl (xmlpp::Element* parent) const
+ SoundAsset::write_to_cpl (xmlpp::Node* node) const
  {
-       xmlpp::Element* main_sound = parent->add_child("MainSound");
-       main_sound->add_child("Id")->add_child_text("urn:uuid:" + _uuid);
-       main_sound->add_child("AnnotationText")->add_child_text(_file_name);
-       main_sound->add_child("EditRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1");
-       main_sound->add_child("IntrinsicDuration")->add_child_text(boost::lexical_cast<string> (_length));
-       main_sound->add_child("EntryPoint")->add_child_text("0");
-       main_sound->add_child("Duration")->add_child_text(boost::lexical_cast<string> (_length));
+       xmlpp::Node* ms = node->add_child ("MainSound");
+       ms->add_child ("Id")->add_child_text ("urn:uuid:" + _uuid);
+       ms->add_child ("AnnotationText")->add_child_text (_file_name);
+       ms->add_child ("EditRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1");
+       ms->add_child ("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration));
+       ms->add_child ("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point));
+       ms->add_child ("Duration")->add_child_text (lexical_cast<string> (_duration));
 +      if (_encrypted) {
-               main_sound->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id);
++              ms->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id);
 +      }
  }
  
  bool
@@@ -310,11 -299,104 +308,110 @@@ SoundAsset::equals (shared_ptr<const As
  shared_ptr<const SoundFrame>
  SoundAsset::get_frame (int n) const
  {
-       return shared_ptr<const SoundFrame> (new SoundFrame (path().string(), n + _entry_point));
+       /* XXX: should add on entry point here? */
+       return shared_ptr<const SoundFrame> (new SoundFrame (path().string(), n));
+ }
+ shared_ptr<SoundAssetWriter>
+ SoundAsset::start_write (MXFMetadata const & metadata)
+ {
+       /* XXX: can't we use a shared_ptr here? */
+       return shared_ptr<SoundAssetWriter> (new SoundAssetWriter (this, metadata));
+ }
+ struct SoundAssetWriter::ASDCPState
+ {
+       ASDCP::PCM::MXFWriter mxf_writer;
+       ASDCP::PCM::FrameBuffer frame_buffer;
+       ASDCP::WriterInfo writer_info;
+       ASDCP::PCM::AudioDescriptor audio_desc;
+ };
+ SoundAssetWriter::SoundAssetWriter (SoundAsset* a, MXFMetadata const & m)
+       : _state (new SoundAssetWriter::ASDCPState)
+       , _asset (a)
+       , _finalized (false)
+       , _frames_written (0)
+       , _frame_buffer_offset (0)
+       , _metadata (m)
+ {
+       /* Derived from ASDCP::Wav::SimpleWaveHeader::FillADesc */
+       _state->audio_desc.EditRate = ASDCP::Rational (_asset->edit_rate(), 1);
+       _state->audio_desc.AudioSamplingRate = ASDCP::Rational (_asset->sampling_rate(), 1);
+       _state->audio_desc.Locked = 0;
+       _state->audio_desc.ChannelCount = _asset->channels ();
+       _state->audio_desc.QuantizationBits = 24;
+       _state->audio_desc.BlockAlign = 3 * _asset->channels();
+       _state->audio_desc.AvgBps = _asset->sampling_rate() * _state->audio_desc.BlockAlign;
+       _state->audio_desc.LinkedTrackID = 0;
+       _state->audio_desc.ChannelFormat = ASDCP::PCM::CF_NONE;
+       
+       _state->frame_buffer.Capacity (ASDCP::PCM::CalcFrameBufferSize (_state->audio_desc));
+       _state->frame_buffer.Size (ASDCP::PCM::CalcFrameBufferSize (_state->audio_desc));
+       memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity());
+       
 -      MXFAsset::fill_writer_info (&_state->writer_info, _asset->uuid (), _metadata);
++      _asset->fill_writer_info (&_state->writer_info, _asset->uuid (), _metadata);
+       
+       if (ASDCP_FAILURE (_state->mxf_writer.OpenWrite (_asset->path().string().c_str(), _state->writer_info, _state->audio_desc))) {
+               boost::throw_exception (FileError ("could not open audio MXF for writing", _asset->path().string()));
+       }
+ }
+ void
+ SoundAssetWriter::write (float const * const * data, int frames)
+ {
+       for (int i = 0; i < frames; ++i) {
+               byte_t* out = _state->frame_buffer.Data() + _frame_buffer_offset;
+               /* Write one sample per channel */
+               for (int j = 0; j < _asset->channels(); ++j) {
+                       int32_t const s = data[j][i] * (1 << 23);
+                       *out++ = (s & 0xff);
+                       *out++ = (s & 0xff00) >> 8;
+                       *out++ = (s & 0xff0000) >> 16;
+               }
+               _frame_buffer_offset += 3 * _asset->channels();
+               assert (_frame_buffer_offset <= int (_state->frame_buffer.Capacity()));
+               /* Finish the MXF frame if required */
+               if (_frame_buffer_offset == int (_state->frame_buffer.Capacity())) {
+                       write_current_frame ();
+                       _frame_buffer_offset = 0;
+                       memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity());
+               }
+       }
+ }
+ void
+ SoundAssetWriter::write_current_frame ()
+ {
+       if (ASDCP_FAILURE (_state->mxf_writer.WriteFrame (_state->frame_buffer, 0, 0))) {
+               boost::throw_exception (MiscError ("could not write audio MXF frame"));
+       }
+       ++_frames_written;
+ }
+ void
+ SoundAssetWriter::finalize ()
+ {
+       if (_frame_buffer_offset > 0) {
+               write_current_frame ();
+       }
+       
+       if (ASDCP_FAILURE (_state->mxf_writer.Finalize())) {
+               boost::throw_exception (MiscError ("could not finalise audio MXF"));
+       }
+       _finalized = true;
+       _asset->set_intrinsic_duration (_frames_written);
+       _asset->set_duration (_frames_written);
  }
 +
 +string
 +SoundAsset::key_type () const
 +{
 +      return "MDAK";
 +}
index 801220387fdc75f0fe664578d010996125d46dda,7b9c65a74ffe67b3a2f8cadd92bf78c1739ceee9..a587b55101fbd7ec030df5999cc5a56e8679941c
@@@ -43,9 -76,8 +76,11 @@@ public
         *  @param mxf_name Name of MXF file to create.
         *  @param progress Signal to inform of progress.
         *  @param fps Frames per second.
 +       *  @param length Length in frames.
 +       *  @param start_frame Frame in the source to start writing from.
+        *  @param intrinsic_duration Length of the whole asset in frames.
 +       *  @param encrypted true if asset should be encrypted.
+        *  Note that this is different to entry_point in that the asset will contain no data before start_frame.
         */
        SoundAsset (
                std::vector<std::string> const & files,
@@@ -53,9 -85,8 +88,9 @@@
                std::string mxf_name,
                boost::signals2::signal<void (float)>* progress,
                int fps,
-               int length,
-               int start_frame,
-               bool encrypted
+               int intrinsic_duration,
++              bool encrypted,
+               MXFMetadata const & metadata = MXFMetadata ()
                );
  
        /** Construct a SoundAsset, generating the MXF from some WAV files.
         *  @param mxf_name Name of MXF file to create.
         *  @param progress Signal to inform of progress.
         *  @param fps Frames per second.
-        *  @param length Length in frames.
-        *  @param start_frame Frame in the source to start writing from.
+        *  @param intrinsic_duration Length of the whole asset in frames.
         *  @param channels Number of audio channels.
 +       *  @param encrypted true if asset should be encrypted.
         */
        SoundAsset (
                boost::function<std::string (Channel)> get_path,
                std::string mxf_name,
                boost::signals2::signal<void (float)>* progress,
                int fps,
-               int length,
-               int start_frame,
+               int intrinsic_duration,
                int channels,
-               bool encrypted
++              bool encrypted,
+               MXFMetadata const & metadata = MXFMetadata ()
+               );
+       SoundAsset (
+               std::string directory,
+               std::string mxf_name
                );
  
        SoundAsset (
        }
  
  private:
-       
-       void construct (boost::function<std::string (Channel)> get_path);
 +      std::string key_type () const;
+       void construct (boost::function<std::string (Channel)> get_path, MXFMetadata const &);
        std::string path_from_channel (Channel channel, std::vector<std::string> const & files);
  
        /** Number of channels in the asset */
index 998abc07a3663dd4c3926d52b9667051bbefceb5,ca91e2c7991fad20478ced70fb3989579ac16001..80cc1ea66f0f22dc02ea0fa0d1680606f4ffb77f
  #include <fstream>
  #include <boost/lexical_cast.hpp>
  #include <boost/algorithm/string.hpp>
 +#include <libxml++/nodes/element.h>
  #include "subtitle_asset.h"
  #include "util.h"
+ #include "xml.h"
  
  using std::string;
  using std::list;
diff --cc src/util.cc
index 8277b2bf0893f46f17b1b94c24c1cf8e87c8f3db,6bee0dc76d34ff62ad460719bf4b81811ae20c35..0c63c3055e25b143e60593503cafdce3aef17fd9
  #include <iostream>
  #include <iomanip>
  #include <boost/filesystem.hpp>
+ #include <boost/lexical_cast.hpp>
  #include <openssl/sha.h>
 +#include <libxml++/nodes/element.h>
 +#include <libxml++/document.h>
 +#include <xmlsec/xmldsig.h>
 +#include <xmlsec/dl.h>
 +#include <xmlsec/app.h>
  #include "KM_util.h"
  #include "KM_fileio.h"
  #include "AS_DCP.h"
  #include "exceptions.h"
  #include "types.h"
  #include "argb_frame.h"
- #include "lut.h"
 +#include "certificates.h"
+ #include "gamma_lut.h"
  
  using std::string;
 +using std::cout;
  using std::stringstream;
  using std::min;
  using std::max;
 +using std::list;
  using boost::shared_ptr;
+ using boost::lexical_cast;
  using namespace libdcp;
  
  /** Create a UUID.
@@@ -285,130 -294,13 +301,140 @@@ libdcp::empty_or_white_space (string s
        return true;
  }
  
 +void
 +libdcp::init ()
 +{
 +      if (xmlSecInit() < 0) {
 +              throw MiscError ("could not initialise xmlsec");
 +      }
 +
 +#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
 +      if (xmlSecCryptoDLLoadLibrary (BAD_CAST XMLSEC_CRYPTO) < 0) {
 +              throw MiscError ("unable to load default xmlsec-crypto library");
 +      }
 +#endif
 +      
 +      if (xmlSecCryptoAppInit (0) < 0) {
 +              throw MiscError ("could not initialise crypto library");
 +      }
 +      
 +      if (xmlSecCryptoInit() < 0) {
 +              throw MiscError ("could not initialise xmlsec-crypto");
 +      }
 +}
 +
 +void
 +libdcp::add_signature_value (xmlpp::Element* parent, CertificateChain const & certificates, string const & signer_key, string const & ns)
 +{
 +      parent->add_child("SignatureValue", ns);
 +      
 +      xmlpp::Element* key_info = parent->add_child("KeyInfo", ns);
 +      list<shared_ptr<Certificate> > c = certificates.leaf_to_root ();
 +      for (list<shared_ptr<Certificate> >::iterator i = c.begin(); i != c.end(); ++i) {
 +              xmlpp::Element* data = key_info->add_child("X509Data", ns);
 +              
 +              {
 +                      xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
 +                      serial->add_child("X509IssuerName", ns)->add_child_text(
 +                              Certificate::name_for_xml ((*i)->issuer())
 +                              );
 +                      serial->add_child("X509SerialNumber", ns)->add_child_text((*i)->serial());
 +              }
 +              
 +              data->add_child("X509Certificate", ns)->add_child_text((*i)->certificate());
 +      }
 +
 +      xmlSecKeysMngrPtr keys_manager = xmlSecKeysMngrCreate();
 +      if (!keys_manager) {
 +              throw MiscError ("could not create keys manager");
 +      }
 +      if (xmlSecCryptoAppDefaultKeysMngrInit (keys_manager) < 0) {
 +              throw MiscError ("could not initialise keys manager");
 +      }
 +      
 +      xmlSecKeyPtr const key = xmlSecCryptoAppKeyLoad (signer_key.c_str(), xmlSecKeyDataFormatPem, 0, 0, 0);
 +      if (key == 0) {
 +              throw MiscError ("could not load signer key");
 +              }
 +      
 +      if (xmlSecCryptoAppDefaultKeysMngrAdoptKey (keys_manager, key) < 0) {
 +              xmlSecKeyDestroy (key);
 +              throw MiscError ("could not use signer key");
 +      }
 +      
 +      xmlSecDSigCtx signature_context;
 +      
 +      if (xmlSecDSigCtxInitialize (&signature_context, keys_manager) < 0) {
 +              throw MiscError ("could not initialise XMLSEC context");
 +      }
 +      
 +      if (xmlSecDSigCtxSign (&signature_context, parent->cobj()) < 0) {
 +              throw MiscError ("could not sign");
 +      }
 +      
 +      xmlSecDSigCtxFinalize (&signature_context);
 +      xmlSecKeysMngrDestroy (keys_manager);
 +}
 +
 +
 +void
 +libdcp::add_signer (xmlpp::Element* parent, CertificateChain const & certificates, string const & ns)
 +{
 +      xmlpp::Element* signer = parent->add_child("Signer");
 +
 +      {
 +              xmlpp::Element* data = signer->add_child("X509Data", ns);
 +              
 +              {
 +                      xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", ns);
 +                      serial_element->add_child("X509IssuerName", ns)->add_child_text (
 +                              Certificate::name_for_xml (certificates.leaf()->issuer())
 +                              );
 +                      serial_element->add_child("X509SerialNumber", ns)->add_child_text (
 +                              certificates.leaf()->serial()
 +                              );
 +              }
 +              
 +              data->add_child("X509SubjectName", ns)->add_child_text (Certificate::name_for_xml (certificates.leaf()->subject()));
 +      }
 +}
 +
 +void
 +libdcp::sign (xmlpp::Element* parent, CertificateChain const & certificates, string const & signer_key)
 +{
 +      add_signer (parent, certificates, "dsig");
 +
 +      xmlpp::Element* signature = parent->add_child("Signature", "dsig");
 +      
 +      {
 +              xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
 +              signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
 +              signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
 +              {
 +                      xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
 +                      reference->set_attribute ("URI", "");
 +                      {
 +                              xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
 +                              transforms->add_child("Transform", "dsig")->set_attribute (
 +                                      "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
 +                                      );
 +                      }
 +                      reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
 +                      /* This will be filled in by the signing later */
 +                      reference->add_child("DigestValue", "dsig");
 +              }
 +      }
 +      
 +      add_signature_value (signature, certificates, signer_key, "dsig");
 +}
 +
+ bool libdcp::operator== (libdcp::Size const & a, libdcp::Size const & b)
+ {
+       return (a.width == b.width && a.height == b.height);
+ }
+ bool libdcp::operator!= (libdcp::Size const & a, libdcp::Size const & b)
+ {
+       return !(a == b);
+ }
diff --cc src/util.h
index ddc5a3223b929a3c23804261ef472e4e9d6f533b,6332ddc08f157a4f3ec50754f94b7a20408dc137..8b5f76bb79577056c0e39adb200105fc93352e9d
@@@ -33,20 -33,35 +37,42 @@@ namespace xmlpp 
  namespace libdcp {
  
  class ARGBFrame;
 +class CertificateChain;
+ class GammaLUT;
+ class XYZsRGBLUT;
+ struct Size {
+       Size ()
+               : width (0)
+               , height (0)
+       {}
+       Size (int w, int h)
+               : width (w)
+               , height (h)
+       {}
        
+       int width;
+       int height;
+ };
+       
+ extern bool operator== (Size const & a, Size const & b);
+ extern bool operator!= (Size const & a, Size const & b);
  extern std::string make_uuid ();
  extern std::string make_digest (std::string filename);
  extern std::string content_kind_to_string (ContentKind kind);
  extern ContentKind content_kind_from_string (std::string kind);
  extern bool empty_or_white_space (std::string s);
  extern opj_image_t* decompress_j2k (uint8_t* data, int64_t size, int reduce);
- extern boost::shared_ptr<ARGBFrame> xyz_to_rgb (opj_image_t* xyz_frame);
+ extern boost::shared_ptr<ARGBFrame> xyz_to_rgb (opj_image_t* xyz_frame, boost::shared_ptr<const GammaLUT>, boost::shared_ptr<const GammaLUT>);
  
 +extern void init ();
 +
 +extern void sign (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & signer_key);
 +extern void add_signature_value (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & signer_key, std::string const & ns);
 +extern void add_signer (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & ns);
 +      
  }
+ #endif
diff --cc src/wscript
index 0922cb56c7de682ddd37344631505fc87d55eef5,37151e516cbd6ee950f41c07ac9fc9c81136bae3..93d6d5c1cef519a2b8ebcebc9d9ead5a8b44ad8d
@@@ -7,17 -7,14 +7,16 @@@ def build(bld)
      obj.name = 'libdcp'
      obj.target = 'dcp'
      obj.export_includes = ['.']
-     obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG XMLSEC1'
 -    obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 OPENSSL SIGC++ LIBXML++ OPENJPEG CXML'
++    obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1'
      obj.use = 'libkumu-libdcp libasdcp-libdcp'
      obj.source = """
                   asset.cc
-                  asset_map.cc
+                  dcp.cc        
 +                 certificates.cc
-                  cpl_file.cc
 +                 crypt_chain.cc
-                  dcp.cc        
+                  cpl.cc
                   dcp_time.cc
-                  lut.cc
+                  gamma_lut.cc
                   metadata.cc
                   mxf_asset.cc
                   picture_asset.cc
@@@ -37,8 -35,7 +37,9 @@@
  
      headers = """
                asset.h
 +              certificates.h
+               cpl.h
 +              crypt_chain.h
                dcp.h
                dcp_time.h
                exceptions.h
diff --cc test/tests.cc
index e2f1f2912ea58ba0de44656cb62b61d3d038cef2,d8b7e5d460309ac31688551774ef6bc53052fc45..ad31cb8610226a44fe62e4c5cf748b48aaf00bba
  #include "picture_asset.h"
  #include "sound_asset.h"
  #include "reel.h"
 +#include "certificates.h"
 +#include "crypt_chain.h"
+ #include "gamma_lut.h"
+ #include "cpl.h"
  
  #define BOOST_TEST_DYN_LINK
  #define BOOST_TEST_MODULE libdcp_test
@@@ -58,17 -57,16 +60,18 @@@ wav (libdcp::Channel
  
  BOOST_AUTO_TEST_CASE (dcp_test)
  {
 +      libdcp::init ();
 +      
        Kumu::libdcp_test = true;
        
-       libdcp::Metadata* t = libdcp::Metadata::instance ();
-       t->issuer = "OpenDCP 0.0.25";
-       t->creator = "OpenDCP 0.0.25";
-       t->company_name = "OpenDCP";
-       t->product_name = "OpenDCP";
-       t->product_version = "0.0.25";
-       t->issue_date = "2012-07-17T04:45:18+00:00";
+       libdcp::XMLMetadata xml_meta;
+       xml_meta.issuer = "OpenDCP 0.0.25";
+       xml_meta.creator = "OpenDCP 0.0.25";
+       xml_meta.issue_date = "2012-07-17T04:45:18+00:00";
+       libdcp::MXFMetadata mxf_meta;
+       mxf_meta.company_name = "OpenDCP";
+       mxf_meta.product_name = "OpenDCP";
+       mxf_meta.product_version = "0.0.25";
        boost::filesystem::remove_all ("build/test/foo");
        boost::filesystem::create_directories ("build/test/foo");
        libdcp::DCP d ("build/test/foo");
@@@ -81,9 -79,8 +84,9 @@@
                                                         &d.Progress,
                                                         24,
                                                         24,
-                                                        32,
-                                                        32,
-                                                        false
+                                                        libdcp::Size (32, 32),
++                                                       false,
+                                                        mxf_meta
                                                         ));
  
        shared_ptr<libdcp::SoundAsset> ms (new libdcp::SoundAsset (
                                                   &(d.Progress),
                                                   24,
                                                   24,
-                                                  false
 +                                                 0,
 +                                                 2,
+                                                  2,
++                                                 false,
+                                                  mxf_meta
                                                   ));
        
        cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (mp, ms, shared_ptr<libdcp::SubtitleAsset> ())));
@@@ -110,8 -106,8 +115,8 @@@ BOOST_AUTO_TEST_CASE (error_test
        vector<string> p;
        p.push_back ("frobozz");
  
-       BOOST_CHECK_THROW (new libdcp::MonoPictureAsset (p, "build/test/bar", "video.mxf", &d.Progress, 24, 24, 32, 32, false), libdcp::FileError);
-       BOOST_CHECK_THROW (new libdcp::SoundAsset (p, "build/test/bar", "audio.mxf", &d.Progress, 24, 24, 0, false), libdcp::FileError);
 -      BOOST_CHECK_THROW (new libdcp::MonoPictureAsset (p, "build/test/bar", "video.mxf", &d.Progress, 24, 24, libdcp::Size (32, 32)), libdcp::FileError);
 -      BOOST_CHECK_THROW (new libdcp::SoundAsset (p, "build/test/bar", "audio.mxf", &d.Progress, 24, 24), libdcp::FileError);
++      BOOST_CHECK_THROW (new libdcp::MonoPictureAsset (p, "build/test/bar", "video.mxf", &d.Progress, 24, 24, false, libdcp::Size (32, 32)), libdcp::FileError);
++      BOOST_CHECK_THROW (new libdcp::SoundAsset (p, "build/test/bar", "audio.mxf", &d.Progress, 24, 24, false), libdcp::FileError);
  }
  
  BOOST_AUTO_TEST_CASE (read_dcp)
@@@ -589,104 -585,67 +594,169 @@@ BOOST_AUTO_TEST_CASE (color
        
  }
  
 +BOOST_AUTO_TEST_CASE (encryption)
 +{
 +      Kumu::libdcp_test = true;
 +      
 +      libdcp::Metadata* t = libdcp::Metadata::instance ();
 +      t->issuer = "OpenDCP 0.0.25";
 +      t->creator = "OpenDCP 0.0.25";
 +      t->company_name = "OpenDCP";
 +      t->product_name = "OpenDCP";
 +      t->product_version = "0.0.25";
 +      t->issue_date = "2012-07-17T04:45:18+00:00";
 +      boost::filesystem::remove_all ("build/test/bar");
 +      boost::filesystem::create_directories ("build/test/bar");
 +      libdcp::DCP d ("build/test/bar");
 +
 +      libdcp::CertificateChain chain;
 +      chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/ca.self-signed.pem")));
 +      chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/intermediate.signed.pem")));
 +      chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/leaf.signed.pem")));
 +
 +      shared_ptr<libdcp::Encryption> crypt (
 +              new libdcp::Encryption (
 +                      chain,
 +                      "test/data/signer.key"
 +                      )
 +              );
 +
 +      shared_ptr<libdcp::CPL> cpl (new libdcp::CPL ("build/test/bar", "A Test DCP", libdcp::FEATURE, 24, 24));
 +      
 +      shared_ptr<libdcp::MonoPictureAsset> mp (new libdcp::MonoPictureAsset (
 +                                                       j2c,
 +                                                       "build/test/bar",
 +                                                       "video.mxf",
 +                                                       &d.Progress,
 +                                                       24,
 +                                                       24,
 +                                                       32,
 +                                                       32,
 +                                                       true
 +                                                       ));
 +
 +      shared_ptr<libdcp::SoundAsset> ms (new libdcp::SoundAsset (
 +                                                 wav,
 +                                                 "build/test/bar",
 +                                                 "audio.mxf",
 +                                                 &(d.Progress),
 +                                                 24,
 +                                                 24,
 +                                                 0,
 +                                                 2,
 +                                                 true
 +                                                 ));
 +      
 +      cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (mp, ms, shared_ptr<libdcp::SubtitleAsset> ())));
 +      d.add_cpl (cpl);
 +
 +      d.write_xml (crypt);
 +
 +      shared_ptr<xmlpp::Document> kdm = cpl->make_kdm (
 +              crypt->certificates,
 +              crypt->signer_key,
 +              crypt->certificates.leaf(),
 +              boost::posix_time::time_from_string ("2013-01-01 00:00:00"),
 +              boost::posix_time::time_from_string ("2013-01-08 00:00:00")
 +              );
 +
 +      kdm->write_to_file_formatted ("build/test/bar.kdm.xml", "UTF-8");
 +}
 +
 +BOOST_AUTO_TEST_CASE (certificates)
 +{
 +      libdcp::CertificateChain c;
 +
 +      c.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/ca.self-signed.pem")));
 +      c.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/intermediate.signed.pem")));
 +      c.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/leaf.signed.pem")));
 +      
 +      BOOST_CHECK_EQUAL (
 +              c.root()->issuer(),
 +              "/O=example.org/OU=example.org/CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION/dnQualifier=rTeK7x+nopFkyphflooz6p2ZM7A="
 +              );
 +      
 +      BOOST_CHECK_EQUAL (
 +              libdcp::Certificate::name_for_xml (c.root()->issuer()),
 +              "dnQualifier=rTeK7x\\+nopFkyphflooz6p2ZM7A=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
 +              );
 +
 +      BOOST_CHECK_EQUAL (c.root()->serial(), "5");
 +
 +      BOOST_CHECK_EQUAL (
 +              libdcp::Certificate::name_for_xml (c.root()->subject()),
 +              "dnQualifier=rTeK7x\\+nopFkyphflooz6p2ZM7A=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
 +              );
 +}
 +
 +BOOST_AUTO_TEST_CASE (crypt_chain)
 +{
 +      boost::filesystem::remove_all ("build/test/crypt");
 +      boost::filesystem::create_directory ("build/test/crypt");
 +      libdcp::make_crypt_chain ("build/test/crypt");
 +}
++
+ BOOST_AUTO_TEST_CASE (recovery)
+ {
+       Kumu::libdcp_test = true;
+       string const picture = "test/data/32x32_red_square.j2c";
+       int const size = boost::filesystem::file_size (picture);
+       uint8_t* data = new uint8_t[size];
+       {
+               FILE* f = fopen (picture.c_str(), "rb");
+               BOOST_CHECK (f);
+               fread (data, 1, size, f);
+               fclose (f);
+       }
+ #ifdef LIBDCP_POSIX
+       /* XXX: fix this posix-only stuff */
+       Kumu::ResetTestRNG ();
+ #endif        
+       
+       boost::filesystem::remove_all ("build/test/baz");
+       boost::filesystem::create_directories ("build/test/baz");
+       shared_ptr<libdcp::MonoPictureAsset> mp (new libdcp::MonoPictureAsset ("build/test/baz", "video1.mxf", 24, libdcp::Size (32, 32)));
+       shared_ptr<libdcp::MonoPictureAssetWriter> writer = mp->start_write (false);
+       int written_size = 0;
+       for (int i = 0; i < 24; ++i) {
+               libdcp::FrameInfo info = writer->write (data, size);
+               written_size = info.size;
+       }
+       writer->finalize ();
+       writer.reset ();
+       boost::filesystem::copy_file ("build/test/baz/video1.mxf", "build/test/baz/video2.mxf");
+       boost::filesystem::resize_file ("build/test/baz/video2.mxf", 16384 + 353 * 11);
+       {
+               FILE* f = fopen ("build/test/baz/video2.mxf", "r+");
+               rewind (f);
+               char zeros[256];
+               memset (zeros, 0, 256);
+               fwrite (zeros, 1, 256, f);
+               fclose (f);
+       }
+ #ifdef LIBDCP_POSIX   
+       Kumu::ResetTestRNG ();
+ #endif        
+       mp.reset (new libdcp::MonoPictureAsset ("build/test/baz", "video2.mxf", 24, libdcp::Size (32, 32)));
+       writer = mp->start_write (true);
+       writer->write (data, size);
+       for (int i = 1; i < 4; ++i) {
+               writer->fake_write (written_size);
+       }
+       for (int i = 4; i < 24; ++i) {
+               writer->write (data, size);
+       }
+       
+       writer->finalize ();
+ }
diff --cc wscript
index b68c475a07eb9944b4fd4b539292cb71810872ed,5b4ec52c00105331520ce8e13fa554da62c283b7..3bc5bd824c5deeb5e690b179f06538cd669cb2b3
+++ b/wscript
@@@ -27,24 -26,27 +26,31 @@@ def configure(conf)
      else:
          conf.env.append_value('CXXFLAGS', '-DLIBDCP_POSIX')
  
+     if not conf.options.osx:
+         conf.env.append_value('CXXFLAGS', ['-Wno-unused-result'])
      conf.check_cfg(package = 'openssl', args = '--cflags --libs', uselib_store = 'OPENSSL', mandatory = True)
      conf.check_cfg(package = 'libxml++-2.6', args = '--cflags --libs', uselib_store = 'LIBXML++', mandatory = True)
-     openjpeg_fragment = """
-                       #include <stdio.h>\n
-                       #include <openjpeg.h>\n
-                       int main () {\n
-                       void* p = (void *) opj_image_create;\n
-                       return 0;\n
-                       }
-                       """
-     if conf.options.static_openjpeg:
-         conf.check_cc(fragment = openjpeg_fragment, msg = 'Checking for library openjpeg', stlib = 'openjpeg', uselib_store = 'OPENJPEG')
 +    conf.check_cfg(package = 'xmlsec1', args = '--cflags --libs', uselib_store = 'XMLSEC1', mandatory = True)
 +    # Remove erroneous escaping of quotes from xmlsec1 defines
 +    conf.env.DEFINES_XMLSEC1 = [f.replace('\\', '') for f in conf.env.DEFINES_XMLSEC1]
 +
+     if conf.options.static:
+         conf.check_cc(fragment = """
+                        #include <stdio.h>\n
+                        #include <openjpeg.h>\n
+                        int main () {\n
+                        void* p = (void *) opj_image_create;\n
+                        return 0;\n
+                        }
+                        """,
+                        msg = 'Checking for library openjpeg', stlib = 'openjpeg', uselib_store = 'OPENJPEG', mandatory = True)
+         
+         conf.env.HAVE_CXML = 1
+         conf.env.STLIB_CXML = ['cxml']
      else:
-         conf.check_cc(fragment = openjpeg_fragment, msg = 'Checking for library openjpeg', lib = 'openjpeg', uselib_store = 'OPENJPEG')
+         conf.check_cfg(package = 'libopenjpeg', args = '--cflags --libs', uselib_store = 'OPENJPEG', mandatory = True)
+         conf.check_cfg(package = 'libcxml', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
  
      if conf.options.target_windows:
          boost_lib_suffix = '-mt'
                     msg = 'Checking for boost signals2 library',
                     uselib_store = 'BOOST_SIGNALS2')
  
-     lut.make_luts()
 +    conf.check_cxx(fragment = """
 +                            #include <boost/date_time.hpp>\n
 +                            int main() { boost::gregorian::day_clock::local_day(); }\n
 +                            """,
 +                   msg = 'Checking for boost datetime library',
 +                   libpath = '/usr/local/lib',
 +                   lib = ['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
 +                   uselib_store = 'BOOST_DATETIME')
 +
      conf.recurse('test')
      conf.recurse('asdcplib')