Small tidy-ups and comments.
[libdcp.git] / src / smpte_subtitle_asset.cc
1 /*
2     Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "smpte_subtitle_asset.h"
21 #include "smpte_load_font_node.h"
22 #include "font_node.h"
23 #include "exceptions.h"
24 #include "xml.h"
25 #include "AS_DCP.h"
26 #include "KM_util.h"
27 #include "raw_convert.h"
28 #include <libxml++/libxml++.h>
29 #include <boost/foreach.hpp>
30 #include <boost/algorithm/string.hpp>
31
32 using std::string;
33 using std::list;
34 using std::stringstream;
35 using std::cout;
36 using std::vector;
37 using boost::shared_ptr;
38 using boost::split;
39 using boost::is_any_of;
40 using namespace dcp;
41
42 SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file, bool mxf)
43         : SubtitleAsset (file)
44 {
45         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
46         
47         if (mxf) {
48                 ASDCP::TimedText::MXFReader reader;
49                 Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
50                 if (ASDCP_FAILURE (r)) {
51                         boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r));
52                 }
53         
54                 string s;
55                 reader.ReadTimedTextResource (s, 0, 0);
56                 stringstream t;
57                 t << s;
58                 xml->read_stream (t);
59
60                 ASDCP::WriterInfo info;
61                 reader.FillWriterInfo (info);
62                 
63                 char buffer[64];
64                 Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
65                 _id = buffer;
66         } else {
67                 xml->read_file (file);
68                 _id = xml->string_child("Id").substr (9);
69         }
70         
71         _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
72
73         _content_title_text = xml->string_child ("ContentTitleText");
74         _annotation_text = xml->optional_string_child ("AnnotationText");
75         _issue_date = LocalTime (xml->string_child ("IssueDate"));
76         _reel_number = xml->optional_number_child<int> ("ReelNumber");
77         _language = xml->optional_string_child ("Language");
78
79         /* This is supposed to be two numbers, but a single number has been seen in the wild */
80         string const er = xml->string_child ("EditRate");
81         vector<string> er_parts;
82         split (er_parts, er, is_any_of (" "));
83         if (er_parts.size() == 1) {
84                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
85         } else if (er_parts.size() == 2) {
86                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
87         } else {
88                 throw XMLError ("malformed EditRate " + er);
89         }
90
91         _time_code_rate = xml->number_child<int> ("TimeCodeRate");
92         if (xml->optional_string_child ("StartTime")) {
93                 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
94         }
95
96         shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
97
98         list<cxml::NodePtr> f = subtitle_list->node_children ("Font");
99         list<shared_ptr<dcp::FontNode> > font_nodes;
100         BOOST_FOREACH (cxml::NodePtr& i, f) {
101                 font_nodes.push_back (shared_ptr<FontNode> (new FontNode (i, _time_code_rate)));
102         }
103         
104         parse_subtitles (xml, font_nodes);
105 }
106
107 list<shared_ptr<LoadFontNode> >
108 SMPTESubtitleAsset::load_font_nodes () const
109 {
110         list<shared_ptr<LoadFontNode> > lf;
111         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
112         return lf;
113 }
114
115 bool
116 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
117 {
118         ASDCP::TimedText::MXFReader reader;
119         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
120         return !ASDCP_FAILURE (r);
121 }
122
123 Glib::ustring
124 SMPTESubtitleAsset::xml_as_string () const
125 {
126         xmlpp::Document doc;
127         xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
128         root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst");
129         root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
130
131         root->add_child("ID", "dcst")->add_child_text (_id);
132         root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
133         if (_annotation_text) {
134                 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
135         }
136         root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
137         if (_reel_number) {
138                 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
139         }
140         if (_language) {
141                 root->add_child("Language", "dcst")->add_child_text (_language.get ());
142         }
143         root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
144         root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
145         if (_start_time) {
146                 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string ());
147         }
148
149         BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
150                 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
151                 load_font->add_child_text (i->urn);
152                 load_font->set_attribute ("ID", i->id);
153         }
154         
155         subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, "dcst");
156         
157         return doc.write_to_string_formatted ("UTF-8");
158 }
159
160 /** Write this content to a MXF file */
161 void
162 SMPTESubtitleAsset::write (boost::filesystem::path p) const
163 {
164         ASDCP::WriterInfo writer_info;
165         fill_writer_info (&writer_info, _id, SMPTE);
166         
167         ASDCP::TimedText::TimedTextDescriptor descriptor;
168         descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
169         descriptor.EncodingName = "UTF-8";
170         descriptor.ResourceList.clear ();
171         descriptor.NamespaceName = "dcst";
172         memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen);
173         descriptor.ContainerDuration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
174
175         /* XXX: fonts into descriptor? */
176         
177         ASDCP::TimedText::MXFWriter writer;
178         Kumu::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor);
179         if (ASDCP_FAILURE (r)) {
180                 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
181         }
182
183         /* XXX: no encryption */
184         writer.WriteTimedTextResource (xml_as_string ());
185
186         writer.Finalize ();
187
188         _file = p;
189 }
190
191 bool
192 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
193 {
194         /* XXX */
195         return false;
196 }
197