X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fsmpte_subtitle_asset.cc;h=e52cd2effa684f20ca08a620d691d2ddeea117b2;hb=5e6d5830bc03e073c2a064cd2e715637f0a05dfc;hp=dc0c86ada581f7daf18483cfef13db77c5ecf19a;hpb=d228090b2f095d9233c1762a5e75968ca5377bf5;p=libdcp.git diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index dc0c86ad..e52cd2ef 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -1,20 +1,34 @@ /* - Copyright (C) 2012-2015 Carl Hetherington + Copyright (C) 2012-2016 Carl Hetherington - This program is free software; you can redistribute it and/or modify + This file is part of libdcp. + + libdcp 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, + libdcp 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. - + along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. */ /** @file src/smpte_subtitle_asset.cc @@ -23,22 +37,22 @@ #include "smpte_subtitle_asset.h" #include "smpte_load_font_node.h" -#include "font_node.h" #include "exceptions.h" #include "xml.h" #include "raw_convert.h" #include "dcp_assert.h" #include "util.h" -#include "AS_DCP.h" -#include "KM_util.h" +#include "compose.hpp" +#include "encryption_context.h" +#include "decryption_context.h" +#include +#include #include #include #include using std::string; using std::list; -using std::stringstream; -using std::cout; using std::vector; using std::map; using boost::shared_ptr; @@ -49,37 +63,59 @@ using boost::dynamic_pointer_cast; using namespace dcp; SMPTESubtitleAsset::SMPTESubtitleAsset () - : _edit_rate (24, 1) + : _intrinsic_duration (0) + , _edit_rate (24, 1) , _time_code_rate (24) { - + } -/** Construct a SMPTESubtitleAsset by reading an MXF file. +/** Construct a SMPTESubtitleAsset by reading an MXF or XML file. * @param file Filename. */ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) : SubtitleAsset (file) { - ASDCP::TimedText::MXFReader reader; - Kumu::Result_t r = reader.OpenRead (file.string().c_str ()); - if (ASDCP_FAILURE (r)) { - boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r)); - } - - /* Read the subtitle XML */ - - string s; - reader.ReadTimedTextResource (s, 0, 0); - stringstream t; - t << s; shared_ptr xml (new cxml::Document ("SubtitleReel")); - xml->read_stream (t); - - ASDCP::WriterInfo info; - reader.FillWriterInfo (info); - _id = read_writer_info (info); + shared_ptr reader (new ASDCP::TimedText::MXFReader ()); + Kumu::Result_t r = reader->OpenRead (_file->string().c_str ()); + if (!ASDCP_FAILURE (r)) { + /* 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 (new DecryptionContext ())); + } + } else { + /* 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 ( + DCPReadError ( + String::compose ( + "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3", + file, static_cast (r), e.what () + ) + ) + ); + } + } +} + +void +SMPTESubtitleAsset::parse_xml (shared_ptr xml) +{ _load_font_nodes = type_children (xml, "LoadFont"); _content_title_text = xml->string_child ("ContentTitleText"); @@ -105,29 +141,38 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) _start_time = Time (xml->string_child ("StartTime"), _time_code_rate); } - shared_ptr subtitle_list = xml->optional_node_child ("SubtitleList"); + /* Now we need to drop down to xmlpp */ - list f = subtitle_list->node_children ("Font"); - list > font_nodes; - BOOST_FOREACH (cxml::NodePtr& i, f) { - font_nodes.push_back (shared_ptr (new FontNode (i, _time_code_rate))); + list ps; + xmlpp::Node::NodeList c = xml->node()->get_children (); + for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) { + xmlpp::Element const * e = dynamic_cast (*i); + if (e && e->get_name() == "SubtitleList") { + parse_subtitles (e, ps, _time_code_rate, SMPTE); + } } - - parse_subtitles (xml, font_nodes); - /* Read fonts */ + /* Guess intrinsic duration */ + _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); +} + +void +SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr reader, shared_ptr dec) +{ + ASDCP::TimedText::TimedTextDescriptor descriptor; + reader->FillTimedTextDescriptor (descriptor); + + /* Load fonts */ - ASDCP::TimedText::TimedTextDescriptor text_descriptor; - reader.FillTimedTextDescriptor (text_descriptor); for ( - ASDCP::TimedText::ResourceList_t::const_iterator i = text_descriptor.ResourceList.begin(); - i != text_descriptor.ResourceList.end(); + 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); + reader->ReadAncillaryResource (i->ResourceID, buffer, dec->decryption()); char id[64]; Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id)); @@ -135,21 +180,58 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) shared_array data (new uint8_t[buffer.Size()]); memcpy (data.get(), buffer.RoData(), buffer.Size()); - /* The IDs in the MXF have a 9 character prefix of unknown origin and meaning... */ - string check_id = string (id).substr (9); - list >::const_iterator j = _load_font_nodes.begin (); - while (j != _load_font_nodes.end() && (*j)->urn != check_id) { + while (j != _load_font_nodes.end() && (*j)->urn != id) { ++j; } if (j != _load_font_nodes.end ()) { - _fonts[(*j)->id] = FontData (data, buffer.Size ()); + _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ()))); } } } - - + + /* Get intrinsic duration */ + _intrinsic_duration = descriptor.ContainerDuration; +} + +void +SMPTESubtitleAsset::set_key (Key key) +{ + /* See if we already have a key; if we do, and we have a file, we'll already + have read that file. + */ + bool const had_key = static_cast (_key); + + MXF::set_key (key); + + if (!_key_id || !_file || had_key) { + /* Either we don't have any data to read, it wasn't + encrypted, or we've already read it, so we don't + need to do anything else. + */ + return; + } + + /* Our data was encrypted; now we can decrypt it */ + + shared_ptr 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)", static_cast (r)) + ) + ); + } + + string s; + shared_ptr dec (new DecryptionContext (key)); + reader->ReadTimedTextResource (s, dec->decryption()); + shared_ptr xml (new cxml::Document ("SubtitleReel")); + xml->read_string (s); + parse_xml (xml); + read_mxf_descriptor (reader, dec); } list > @@ -168,7 +250,7 @@ SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file) return !ASDCP_FAILURE (r); } -Glib::ustring +string SMPTESubtitleAsset::xml_as_string () const { xmlpp::Document doc; @@ -176,7 +258,7 @@ SMPTESubtitleAsset::xml_as_string () const root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst"); root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs"); - root->add_child("ID", "dcst")->add_child_text (_id); + root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _id); root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text); if (_annotation_text) { root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ()); @@ -191,33 +273,38 @@ SMPTESubtitleAsset::xml_as_string () const root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ()); root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert (_time_code_rate)); if (_start_time) { - root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string ()); + root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE)); } BOOST_FOREACH (shared_ptr i, _load_font_nodes) { xmlpp::Element* load_font = root->add_child("LoadFont", "dcst"); - load_font->add_child_text (i->urn); + load_font->add_child_text ("urn:uuid:" + i->urn); load_font->set_attribute ("ID", i->id); } - - subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, "dcst"); - - return doc.write_to_string_formatted ("UTF-8"); + + subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE); + + return doc.write_to_string ("UTF-8"); } /** Write this content to a MXF file */ void SMPTESubtitleAsset::write (boost::filesystem::path p) const { + EncryptionContext enc (key (), SMPTE); + ASDCP::WriterInfo writer_info; fill_writer_info (&writer_info, _id, SMPTE); - + ASDCP::TimedText::TimedTextDescriptor descriptor; descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator); descriptor.EncodingName = "UTF-8"; BOOST_FOREACH (shared_ptr i, _load_font_nodes) { - map::const_iterator j = _fonts.find (i->id); + list::const_iterator j = _fonts.begin (); + while (j != _fonts.end() && j->load_id != i->id) { + ++j; + } if (j != _fonts.end ()) { ASDCP::TimedText::TimedTextResourceDescriptor res; unsigned int c; @@ -227,10 +314,10 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const descriptor.ResourceList.push_back (res); } } - + descriptor.NamespaceName = "dcst"; memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen); - descriptor.ContainerDuration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); + descriptor.ContainerDuration = _intrinsic_duration; ASDCP::TimedText::MXFWriter writer; ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor); @@ -239,18 +326,21 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const } /* XXX: no encryption */ - r = writer.WriteTimedTextResource (xml_as_string ()); + r = writer.WriteTimedTextResource (xml_as_string (), enc.encryption(), enc.hmac()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r)); } BOOST_FOREACH (shared_ptr i, _load_font_nodes) { - map::const_iterator j = _fonts.find (i->id); + list::const_iterator j = _fonts.begin (); + while (j != _fonts.end() && j->load_id != i->id) { + ++j; + } if (j != _fonts.end ()) { ASDCP::TimedText::FrameBuffer buffer; - buffer.SetData (j->second.data.get(), j->second.size); - buffer.Size (j->second.size); - r = writer.WriteAncillaryResource (buffer); + buffer.SetData (j->data.data().get(), j->data.size()); + buffer.Size (j->data.size()); + r = writer.WriteAncillaryResource (buffer, enc.encryption(), enc.hmac()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r)); } @@ -297,7 +387,7 @@ SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions note (DCP_ERROR, "Subtitle content title texts differ"); return false; } - + if (_language != other->_language) { note (DCP_ERROR, "Subtitle languages differ"); return false; @@ -307,17 +397,21 @@ SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions note (DCP_ERROR, "Subtitle annotation texts differ"); return false; } - + if (_issue_date != other->_issue_date) { - note (DCP_ERROR, "Subtitle issue dates differ"); - return false; + if (options.issue_dates_can_differ) { + note (DCP_NOTE, "Subtitle issue dates differ"); + } else { + note (DCP_ERROR, "Subtitle issue dates differ"); + return false; + } } - + if (_reel_number != other->_reel_number) { note (DCP_ERROR, "Subtitle reel numbers differ"); return false; } - + if (_edit_rate != other->_edit_rate) { note (DCP_ERROR, "Subtitle edit rates differ"); return false; @@ -327,7 +421,7 @@ SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions note (DCP_ERROR, "Subtitle time code rates differ"); return false; } - + if (_start_time != other->_start_time) { note (DCP_ERROR, "Subtitle start times differ"); return false; @@ -337,8 +431,16 @@ SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions } void -SMPTESubtitleAsset::add_font (string id, boost::filesystem::path file) +SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file) +{ + string const uuid = make_uuid (); + _fonts.push_back (Font (load_id, uuid, file)); + _load_font_nodes.push_back (shared_ptr (new SMPTELoadFontNode (load_id, uuid))); +} + +void +SMPTESubtitleAsset::add (dcp::SubtitleString s) { - add_font_data (id, file); - _load_font_nodes.push_back (shared_ptr (new SMPTELoadFontNode (id, make_uuid ()))); + SubtitleAsset::add (s); + _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); }