Support reading of encrypted subtitles.
authorCarl Hetherington <cth@carlh.net>
Sun, 14 Aug 2016 22:59:16 +0000 (23:59 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 14 Aug 2016 22:59:16 +0000 (23:59 +0100)
15 files changed:
src/asset_reader.cc
src/asset_reader.h
src/mono_picture_frame.cc
src/mono_picture_frame.h
src/mxf.h
src/reel.cc
src/reel_subtitle_asset.cc
src/reel_subtitle_asset.h
src/smpte_subtitle_asset.cc
src/smpte_subtitle_asset.h
src/sound_frame.cc
src/sound_frame.h
src/stereo_picture_frame.cc
src/stereo_picture_frame.h
src/wscript

index 9ada75f0431647d31f9a76623587bcba4b20e414..73c5d010c48807944f9d1efdbc48ee44dc5944b2 100644 (file)
 #include "asset_reader.h"
 #include "mxf.h"
 #include "exceptions.h"
+#include "decryption_context.h"
 #include <asdcp/AS_DCP.h>
 
 using namespace dcp;
 
 AssetReader::AssetReader (MXF const * mxf)
-       : _decryption_context (0)
+       : _decryption_context (new DecryptionContext (mxf->key ()))
 {
-       if (mxf->key()) {
-               _decryption_context = new ASDCP::AESDecContext;
-               if (ASDCP_FAILURE (_decryption_context->InitKey (mxf->key()->value ()))) {
-                       throw MiscError ("could not set up decryption context");
-               }
-       }
-}
 
-AssetReader::~AssetReader ()
-{
-       delete _decryption_context;
 }
index 716e854d1aebe23a587c08ffff9da9e679f387d2..09169a8e0b173ec3ab01c60f8bfc2b0b10150fe2 100644 (file)
 #define LIBDCP_ASSET_READER_H
 
 #include <boost/noncopyable.hpp>
-
-namespace ASDCP {
-       class AESDecContext;
-}
+#include <boost/shared_ptr.hpp>
 
 namespace dcp {
 
 class MXF;
+class DecryptionContext;
 
 class AssetReader : public boost::noncopyable
 {
 public:
        explicit AssetReader (MXF const * mxf);
-       virtual ~AssetReader ();
+       virtual ~AssetReader () {}
 
 protected:
-       ASDCP::AESDecContext* _decryption_context;
+       boost::shared_ptr<DecryptionContext> _decryption_context;
 };
 
 }
index fd095a1fdf5e8953461b1959811beda3737ca74a..79527280afd2def374d2d7b78e245e6906713da4 100644 (file)
@@ -42,6 +42,7 @@
 #include "colour_conversion.h"
 #include "compose.hpp"
 #include "j2k.h"
+#include "decryption_context.h"
 #include <asdcp/KM_fileio.h>
 #include <asdcp/AS_DCP.h>
 
@@ -73,12 +74,12 @@ MonoPictureFrame::MonoPictureFrame (boost::filesystem::path path)
  *  @param n Frame within the asset, not taking EntryPoint into account.
  *  @param c Context for decryption, or 0.
  */
-MonoPictureFrame::MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, ASDCP::AESDecContext* c)
+MonoPictureFrame::MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, shared_ptr<DecryptionContext> c)
 {
        /* XXX: unfortunate guesswork on this buffer size */
        _buffer = new ASDCP::JP2K::FrameBuffer (4 * Kumu::Megabyte);
 
-       if (ASDCP_FAILURE (reader->ReadFrame (n, *_buffer, c))) {
+       if (ASDCP_FAILURE (reader->ReadFrame (n, *_buffer, c->decryption()))) {
                boost::throw_exception (DCPReadError (String::compose ("could not read video frame %1", n)));
        }
 }
index ea15f6a3268efae50ef18ab9bb71071c353bfef4..cb4afd6f5ffc1e7b339b0cadff2359899b4d3c54 100644 (file)
@@ -54,6 +54,7 @@ namespace ASDCP {
 namespace dcp {
 
 class OpenJPEGImage;
+class DecryptionContext;
 
 /** @class MonoPictureFrame
  *  @brief A single frame of a 2D (monoscopic) picture asset.
@@ -74,7 +75,7 @@ public:
 private:
        friend class MonoPictureAssetReader;
 
-       MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, ASDCP::AESDecContext *);
+       MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, boost::shared_ptr<DecryptionContext>);
 
        ASDCP::JP2K::FrameBuffer* _buffer;
 };
index 89e4f6352be687119d3a1d3f4f9addd0d2a5fa8b..73ada433510b640041beba4afdf2556a5a2dfe96 100644 (file)
--- a/src/mxf.h
+++ b/src/mxf.h
@@ -80,7 +80,7 @@ public:
                return _key_id;
        }
 
-       void set_key (Key);
+       virtual void set_key (Key);
 
        /** @return encryption/decryption key, if one has been set */
        boost::optional<Key> key () const {
index 78be2293b46c8664ce47a155a8de547fa48d0c07..50c780f47ed247814130914f3bca93912480e1b8 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2016 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -45,6 +45,7 @@
 #include "decrypted_kdm_key.h"
 #include "decrypted_kdm.h"
 #include "interop_subtitle_asset.h"
+#include "smpte_subtitle_asset.h"
 #include "reel_atmos_asset.h"
 #include <libxml++/nodes/element.h>
 
@@ -184,6 +185,12 @@ Reel::add (DecryptedKDM const & kdm)
                if (_main_sound && i->id() == _main_sound->key_id()) {
                        _main_sound->asset()->set_key (i->key ());
                }
+               if (_main_subtitle && i->id() == _main_subtitle->key_id()) {
+                       shared_ptr<SMPTESubtitleAsset> s = dynamic_pointer_cast<SMPTESubtitleAsset> (_main_subtitle->asset());
+                       if (s) {
+                               s->set_key (i->key ());
+                       }
+               }
                if (_atmos && i->id() == _atmos->key_id()) {
                        _atmos->asset()->set_key (i->key ());
                }
index 0553f4ba3351904479be874905f1b57c05b543ef..c7e4042065230ce791cb940182cbf12bb5e99df7 100644 (file)
 
 #include "subtitle_asset.h"
 #include "reel_subtitle_asset.h"
+#include "smpte_subtitle_asset.h"
+#include <libxml++/libxml++.h>
 
 using std::string;
 using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::optional;
 using namespace dcp;
 
 ReelSubtitleAsset::ReelSubtitleAsset (boost::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelAsset (asset, edit_rate, intrinsic_duration, entry_point)
+       , ReelMXF (dynamic_pointer_cast<SMPTESubtitleAsset>(asset) ? dynamic_pointer_cast<SMPTESubtitleAsset>(asset)->key_id() : optional<string>())
 {
 
 }
 
 ReelSubtitleAsset::ReelSubtitleAsset (boost::shared_ptr<const cxml::Node> node)
        : ReelAsset (node)
+       , ReelMXF (node)
 {
        node->ignore_child ("Language");
        node->done ();
@@ -60,3 +66,23 @@ ReelSubtitleAsset::cpl_node_name () const
 {
        return "MainSubtitle";
 }
+
+string
+ReelSubtitleAsset::key_type () const
+{
+       return "MDSK";
+}
+
+void
+ReelSubtitleAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const
+{
+       ReelAsset::write_to_cpl (node, standard);
+
+        if (key_id ()) {
+               /* Find <MainSubtitle> */
+               xmlpp::Node* ms = find_child (node, cpl_node_name ());
+               /* Find <Hash> */
+               xmlpp::Node* hash = find_child (ms, "Hash");
+               ms->add_child_before (hash, "KeyId")->add_child_text ("urn:uuid:" + key_id().get ());
+       }
+}
index 91dcd53b1a98e3548c7060cab7bc7416f63d79c7..1d76ba9e32eafeecadd2cab95427814784dcfbeb 100644 (file)
@@ -39,6 +39,7 @@
 #define LIBDCP_REEL_SUBTITLE_ASSET_H
 
 #include "reel_asset.h"
+#include "reel_mxf.h"
 #include "subtitle_asset.h"
 
 namespace dcp {
@@ -46,19 +47,22 @@ namespace dcp {
 class SubtitleAsset;
 
 /** @class ReelSubtitleAsset
- *  @brief Part of a Reel's description which refers to a subtitle XML file.
+ *  @brief Part of a Reel's description which refers to a subtitle XML/MXF file.
  */
-class ReelSubtitleAsset : public ReelAsset
+class ReelSubtitleAsset : public ReelAsset, public ReelMXF
 {
 public:
        ReelSubtitleAsset (boost::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t instrinsic_duration, int64_t entry_point);
        explicit ReelSubtitleAsset (boost::shared_ptr<const cxml::Node>);
 
+       void write_to_cpl (xmlpp::Node* node, Standard standard) const;
+
        boost::shared_ptr<SubtitleAsset> asset () const {
                return asset_of_type<SubtitleAsset> ();
        }
 
 private:
+       std::string key_type () const;
        std::string cpl_node_name () const;
 };
 
index e0934ab5b71e69a2300f69e559c9d0d3d6844ccf..239fff74c6184b0967e0b24073ef4eb0020dd86c 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -45,6 +45,7 @@
 #include "util.h"
 #include "compose.hpp"
 #include "encryption_context.h"
+#include "decryption_context.h"
 #include <asdcp/AS_DCP.h>
 #include <asdcp/KM_util.h>
 #include <libxml++/libxml++.h>
@@ -79,19 +80,26 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
        shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
 
        shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
-       Kumu::Result_t r = reader->OpenRead (file.string().c_str ());
-
+       Kumu::Result_t r = reader->OpenRead (_file.string().c_str ());
        if (!ASDCP_FAILURE (r)) {
-               string s;
-               reader->ReadTimedTextResource (s, 0, 0);
-               xml->read_string (s);
+               /* MXF-wrapped */
                ASDCP::WriterInfo info;
                reader->FillWriterInfo (info);
                _id = read_writer_info (info);
+               if (!_key_id) {
+                       /* Not encrypted; read it in now */
+                       string s;
+                       reader->ReadTimedTextResource (s);
+                       xml->read_string (s);
+                       parse_xml (xml);
+                       read_mxf_descriptor (reader, shared_ptr<DecryptionContext> (new DecryptionContext ()));
+               }
        } else {
-               reader.reset ();
+               /* Plain XML */
                try {
+                       xml.reset (new cxml::Document ("SubtitleReel"));
                        xml->read_file (file);
+                       parse_xml (xml);
                        _id = remove_urn_uuid (xml->string_child ("Id"));
                } catch (cxml::Error& e) {
                        boost::throw_exception (
@@ -101,7 +109,11 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
                                );
                }
        }
+}
 
+void
+SMPTESubtitleAsset::parse_xml (shared_ptr<cxml::Document> xml)
+{
        _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
 
        _content_title_text = xml->string_child ("ContentTitleText");
@@ -141,45 +153,80 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
 
        parse_subtitles (xml, font_nodes, subtitle_nodes);
 
-       if (reader) {
-               ASDCP::TimedText::TimedTextDescriptor descriptor;
-               reader->FillTimedTextDescriptor (descriptor);
+       /* Guess intrinsic duration */
+       _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
+}
+
+void
+SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
+{
+       ASDCP::TimedText::TimedTextDescriptor descriptor;
+       reader->FillTimedTextDescriptor (descriptor);
 
-               /* Load fonts */
+       /* Load fonts */
 
-               for (
-                       ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
-                       i != descriptor.ResourceList.end();
-                       ++i) {
+       for (
+               ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
+               i != descriptor.ResourceList.end();
+               ++i) {
 
-                       if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
-                               ASDCP::TimedText::FrameBuffer buffer;
-                               buffer.Capacity (10 * 1024 * 1024);
-                               reader->ReadAncillaryResource (i->ResourceID, buffer);
+               if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
+                       ASDCP::TimedText::FrameBuffer buffer;
+                       buffer.Capacity (10 * 1024 * 1024);
+                       reader->ReadAncillaryResource (i->ResourceID, buffer, dec->decryption());
 
-                               char id[64];
-                               Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
+                       char id[64];
+                       Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
 
-                               shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
-                               memcpy (data.get(), buffer.RoData(), buffer.Size());
+                       shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
+                       memcpy (data.get(), buffer.RoData(), buffer.Size());
 
-                               list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
-                               while (j != _load_font_nodes.end() && (*j)->urn != id) {
-                                       ++j;
-                               }
+                       list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
+                       while (j != _load_font_nodes.end() && (*j)->urn != id) {
+                               ++j;
+                       }
 
-                               if (j != _load_font_nodes.end ()) {
-                                       _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
-                               }
+                       if (j != _load_font_nodes.end ()) {
+                               _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
                        }
                }
+       }
 
-               /* Get intrinsic duration */
-               _intrinsic_duration = descriptor.ContainerDuration;
-       } else {
-               /* Guess intrinsic duration */
-               _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
+       /* Get intrinsic duration */
+       _intrinsic_duration = descriptor.ContainerDuration;
+}
+
+void
+SMPTESubtitleAsset::set_key (Key key)
+{
+       MXF::set_key (key);
+
+       if (!_key_id || _file.empty()) {
+               /* Either we don't have any data to read, or it wasn't
+                  encrypted, so we don't need to do anything else.
+               */
+               return;
        }
+
+       /* Our data was encrypted; now we can decrypt it */
+
+       shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
+       Kumu::Result_t r = reader->OpenRead (_file.string().c_str ());
+       if (ASDCP_FAILURE (r)) {
+               boost::throw_exception (
+                       DCPReadError (
+                               String::compose ("Could not read encrypted subtitle MXF (%1)", _file, static_cast<int> (r))
+                               )
+                       );
+       }
+
+       string s;
+       shared_ptr<DecryptionContext> dec (new DecryptionContext (key));
+       reader->ReadTimedTextResource (s, dec->decryption());
+       shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
+       xml->read_string (s);
+       parse_xml (xml);
+       read_mxf_descriptor (reader, dec);
 }
 
 list<shared_ptr<LoadFontNode> >
index ffe5b6d9bdf23f99cf050d256de001853b2d4508..004531a2f18a5514b834662a499ef0661d7823e5 100644 (file)
@@ -49,6 +49,7 @@ namespace ASDCP {
 namespace dcp {
 
 class SMPTELoadFontNode;
+class DecryptionContext;
 
 /** @class SMPTESubtitleAsset
  *  @brief A set of subtitles to be read and/or written in the SMPTE format.
@@ -74,6 +75,7 @@ public:
        void write (boost::filesystem::path path) const;
        void add (SubtitleString);
        void add_font (std::string id, boost::filesystem::path file);
+       void set_key (Key key);
 
        void set_content_title_text (std::string t) {
                _content_title_text = t;
@@ -159,6 +161,8 @@ protected:
 
 private:
        void read_fonts (boost::shared_ptr<ASDCP::TimedText::MXFReader>);
+       void parse_xml (boost::shared_ptr<cxml::Document> xml);
+       void read_mxf_descriptor (boost::shared_ptr<ASDCP::TimedText::MXFReader> reader, boost::shared_ptr<DecryptionContext> dec);
 
        /** The total length of this content in video frames.  The amount of
         *  content presented may be less than this.
index 83b053b2c92efdc85b5a7503487fc012dc4ab60e..132d25bddf861a21acb2d37c61d77921d2ce19a5 100644 (file)
 
 #include "sound_frame.h"
 #include "exceptions.h"
+#include "decryption_context.h"
 #include <asdcp/AS_DCP.h>
 #include <asdcp/KM_fileio.h>
 
-using namespace std;
 using namespace dcp;
+using boost::shared_ptr;
 
-SoundFrame::SoundFrame (ASDCP::PCM::MXFReader* reader, int n, ASDCP::AESDecContext* c)
+SoundFrame::SoundFrame (ASDCP::PCM::MXFReader* reader, int n, shared_ptr<DecryptionContext> c)
 {
        /* XXX: unfortunate guesswork on this buffer size */
        _buffer = new ASDCP::PCM::FrameBuffer (1 * Kumu::Megabyte);
 
-       if (ASDCP_FAILURE (reader->ReadFrame (n, *_buffer, c))) {
+       if (ASDCP_FAILURE (reader->ReadFrame (n, *_buffer, c->decryption()))) {
                boost::throw_exception (DCPReadError ("could not read audio frame"));
        }
 }
index 4c061fdc7ed5b1bcb654a0479107a182de5d33bd..3cd3246b71009cd85b4f9e2cee92882bc7db481d 100644 (file)
@@ -53,6 +53,8 @@ namespace ASDCP {
 
 namespace dcp {
 
+class DecryptionContext;
+
 /** @class SoundFrame
  *  @brief One &lsquo;frame&rsquo; of sound data from a SoundAsset.
  */
@@ -67,7 +69,7 @@ public:
 private:
        friend class SoundAssetReader;
 
-       SoundFrame (ASDCP::PCM::MXFReader* reader, int n, ASDCP::AESDecContext *);
+       SoundFrame (ASDCP::PCM::MXFReader* reader, int n, boost::shared_ptr<DecryptionContext>);
 
        /** a buffer to hold the frame */
        ASDCP::PCM::FrameBuffer* _buffer;
index 82f84f3f97489ebdc2b437c90601567b10e2699b..e645c7df613412ee1973727409fa4dba396866bc 100644 (file)
@@ -38,6 +38,7 @@
 #include "colour_conversion.h"
 #include "compose.hpp"
 #include "j2k.h"
+#include "decryption_context.h"
 #include <asdcp/AS_DCP.h>
 #include <asdcp/KM_fileio.h>
 
@@ -49,12 +50,12 @@ using namespace dcp;
  *  @param reader Reader for the MXF file.
  *  @param n Frame within the asset, not taking EntryPoint into account.
  */
-StereoPictureFrame::StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, ASDCP::AESDecContext* c)
+StereoPictureFrame::StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, shared_ptr<DecryptionContext> c)
 {
        /* XXX: unfortunate guesswork on this buffer size */
        _buffer = new ASDCP::JP2K::SFrameBuffer (4 * Kumu::Megabyte);
 
-       if (ASDCP_FAILURE (reader->ReadFrame (n, *_buffer, c))) {
+       if (ASDCP_FAILURE (reader->ReadFrame (n, *_buffer, c->decryption()))) {
                boost::throw_exception (DCPReadError (String::compose ("could not read video frame %1 of %2", n)));
        }
 }
index fd0e70fd79da410131e3e93d1137cb360d81ba3c..65e1cea38ee04fa6dfd46e484412fc27a5ca217c 100644 (file)
@@ -49,6 +49,7 @@ namespace ASDCP {
 namespace dcp {
 
 class OpenJPEGImage;
+class DecryptionContext;
 
 /** A single frame of a 3D (stereoscopic) picture asset */
 class StereoPictureFrame : public boost::noncopyable
@@ -70,7 +71,7 @@ public:
 private:
        friend class StereoPictureAssetReader;
 
-       StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, ASDCP::AESDecContext *);
+       StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, boost::shared_ptr<DecryptionContext>);
 
        ASDCP::JP2K::SFrameBuffer* _buffer;
 };
index d76b1a5085eae557dbe47419140b8c2a90eed957..be7d4621e7b12448865e16ca0bf3aad556ed3b0e 100644 (file)
@@ -47,6 +47,7 @@ def build(bld):
              dcp.cc
              dcp_time.cc
              decrypted_kdm.cc
+             decryption_context.cc
              decrypted_kdm_key.cc
              encrypted_kdm.cc
              encryption_context.cc