2 Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "raw_convert.h"
36 #include "exceptions.h"
37 #include "compose.hpp"
38 #include "dcp_assert.h"
39 #include <libxml++/libxml++.h>
40 #include <boost/algorithm/string.hpp>
51 using namespace boost;
53 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
55 return (a.width == b.width && a.height == b.height);
58 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
64 /** Construct a Fraction from a string of the form <numerator> <denominator>
67 Fraction::Fraction (string s)
70 split (b, s, is_any_of (" "));
72 boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
74 numerator = raw_convert<int> (b[0]);
75 denominator = raw_convert<int> (b[1]);
79 Fraction::as_string () const
81 return String::compose ("%1 %2", numerator, denominator);
85 dcp::operator== (Fraction const & a, Fraction const & b)
87 return (a.numerator == b.numerator && a.denominator == b.denominator);
91 dcp::operator!= (Fraction const & a, Fraction const & b)
93 return (a.numerator != b.numerator || a.denominator != b.denominator);
97 /** Construct a Colour, initialising it to black. */
106 /** Construct a Colour from R, G and B. The values run between
109 Colour::Colour (int r_, int g_, int b_)
117 /** Construct a Colour from an ARGB hex string; the alpha value is ignored.
118 * @param argb_hex A string of the form AARRGGBB, where e.g. RR is a two-character
121 Colour::Colour (string argb_hex)
124 if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
125 boost::throw_exception (XMLError ("could not parse colour string"));
129 /** @return An ARGB string of the form AARRGGBB, where e.g. RR is a two-character
130 * hex value. The alpha value will always be FF (ie 255; maximum alpha).
133 Colour::to_argb_string () const
136 snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
140 /** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
144 Colour::to_rgb_string () const
147 snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
151 /** operator== for Colours.
152 * @param a First colour to compare.
153 * @param b Second colour to compare.
156 dcp::operator== (Colour const & a, Colour const & b)
158 return (a.r == b.r && a.g == b.g && a.b == b.b);
161 /** operator!= for Colours.
162 * @param a First colour to compare.
163 * @param b Second colour to compare.
166 dcp::operator!= (Colour const & a, Colour const & b)
173 dcp::effect_to_string (Effect e)
184 boost::throw_exception (MiscError ("unknown effect type"));
188 dcp::string_to_effect (string s)
192 } else if (s == "border") {
193 return Effect::BORDER;
194 } else if (s == "shadow") {
195 return Effect::SHADOW;
198 boost::throw_exception (ReadError ("unknown subtitle effect type"));
203 dcp::halign_to_string (HAlign h)
214 boost::throw_exception (MiscError ("unknown subtitle halign type"));
218 dcp::string_to_halign (string s)
222 } else if (s == "center") {
223 return HAlign::CENTER;
224 } else if (s == "right") {
225 return HAlign::RIGHT;
228 boost::throw_exception (ReadError ("unknown subtitle halign type"));
232 dcp::valign_to_string (VAlign v)
243 boost::throw_exception (MiscError ("unknown subtitle valign type"));
247 dcp::string_to_valign (string s)
251 } else if (s == "center") {
252 return VAlign::CENTER;
253 } else if (s == "bottom") {
254 return VAlign::BOTTOM;
257 boost::throw_exception (ReadError ("unknown subtitle valign type"));
261 dcp::direction_to_string (Direction v)
274 boost::throw_exception (MiscError ("unknown subtitle direction type"));
278 dcp::string_to_direction (string s)
280 if (s == "ltr" || s == "horizontal") {
281 return Direction::LTR;
282 } else if (s == "rtl") {
283 return Direction::RTL;
284 } else if (s == "ttb" || s == "vertical") {
285 return Direction::TTB;
286 } else if (s == "btt") {
287 return Direction::BTT;
290 boost::throw_exception (ReadError ("unknown subtitle direction type"));
293 /** Convert a content kind to a string which can be used in a
294 * <ContentKind> node.
295 * @param kind ContentKind.
299 dcp::content_kind_to_string (ContentKind kind)
302 case ContentKind::FEATURE:
304 case ContentKind::SHORT:
306 case ContentKind::TRAILER:
308 case ContentKind::TEST:
310 case ContentKind::TRANSITIONAL:
311 return "transitional";
312 case ContentKind::RATING:
314 case ContentKind::TEASER:
316 case ContentKind::POLICY:
318 case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
320 case ContentKind::ADVERTISEMENT:
321 return "advertisement";
322 case ContentKind::EPISODE:
324 case ContentKind::PROMO:
331 /** Convert a string from a <ContentKind> node to a libdcp ContentKind.
332 * Reasonably tolerant about varying case.
333 * @param kind Content kind string.
334 * @return libdcp ContentKind.
337 dcp::content_kind_from_string (string kind)
339 transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
341 if (kind == "feature") {
342 return ContentKind::FEATURE;
343 } else if (kind == "short") {
344 return ContentKind::SHORT;
345 } else if (kind == "trailer") {
346 return ContentKind::TRAILER;
347 } else if (kind == "test") {
348 return ContentKind::TEST;
349 } else if (kind == "transitional") {
350 return ContentKind::TRANSITIONAL;
351 } else if (kind == "rating") {
352 return ContentKind::RATING;
353 } else if (kind == "teaser") {
354 return ContentKind::TEASER;
355 } else if (kind == "policy") {
356 return ContentKind::POLICY;
357 } else if (kind == "psa") {
358 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
359 } else if (kind == "advertisement") {
360 return ContentKind::ADVERTISEMENT;
361 } else if (kind == "episode") {
362 return ContentKind::EPISODE;
363 } else if (kind == "promo") {
364 return ContentKind::PROMO;
367 throw BadContentKindError (kind);
372 dcp::marker_to_string (dcp::Marker m)
401 dcp::marker_from_string (string s)
405 } else if (s == "LFOC") {
407 } else if (s == "FFTC") {
409 } else if (s == "LFTC") {
411 } else if (s == "FFOI") {
413 } else if (s == "LFOI") {
415 } else if (s == "FFEC") {
417 } else if (s == "LFEC") {
419 } else if (s == "FFMC") {
421 } else if (s == "LFMC") {
428 Rating::Rating (cxml::ConstNodePtr node)
430 agency = node->string_child("Agency");
431 label = node->string_child("Label");
436 Rating::as_xml (xmlpp::Element* parent) const
438 parent->add_child("Agency")->add_child_text(agency);
439 parent->add_child("Label")->add_child_text(label);
443 dcp::operator== (Rating const & a, Rating const & b)
445 return a.agency == b.agency && a.label == b.label;
448 ContentVersion::ContentVersion ()
449 : id ("urn:uuid:" + make_uuid())
455 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
457 id = node->string_child("Id");
458 label_text = node->string_child("LabelText");
462 ContentVersion::ContentVersion (string label_text_)
463 : id ("urn:uuid:" + make_uuid())
464 , label_text (label_text_)
471 ContentVersion::as_xml (xmlpp::Element* parent) const
473 xmlpp::Node* cv = parent->add_child("ContentVersion");
474 cv->add_child("Id")->add_child_text(id);
475 cv->add_child("LabelText")->add_child_text(label_text);
479 Luminance::Luminance (cxml::ConstNodePtr node)
481 _unit = string_to_unit (node->string_attribute("units"));
482 _value = raw_convert<float> (node->content());
486 Luminance::Luminance (float value, Unit unit)
494 Luminance::set_value (float v)
497 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
505 Luminance::as_xml (xmlpp::Element* parent, string ns) const
507 xmlpp::Element* lum = parent->add_child("Luminance", ns);
508 lum->set_attribute("units", unit_to_string(_unit));
509 lum->add_child_text(raw_convert<string>(_value, 3));
514 Luminance::unit_to_string (Unit u)
517 case Unit::CANDELA_PER_SQUARE_METRE:
518 return "candela-per-square-metre";
519 case Unit::FOOT_LAMBERT:
520 return "foot-lambert";
530 Luminance::string_to_unit (string u)
532 if (u == "candela-per-square-metre") {
533 return Unit::CANDELA_PER_SQUARE_METRE;
534 } else if (u == "foot-lambert") {
535 return Unit::FOOT_LAMBERT;
538 throw XMLError (String::compose("Invalid luminance unit %1", u));
543 dcp::operator== (Luminance const& a, Luminance const& b)
545 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
549 MainSoundConfiguration::MainSoundConfiguration (string s)
551 vector<string> parts;
552 boost::split (parts, s, boost::is_any_of("/"));
553 if (parts.size() != 2) {
554 throw MainSoundConfigurationError (s);
557 if (parts[0] == "51") {
558 _field = MCASoundField::FIVE_POINT_ONE;
559 } else if (parts[0] == "71") {
560 _field = MCASoundField::SEVEN_POINT_ONE;
562 throw MainSoundConfigurationError (s);
565 vector<string> channels;
566 boost::split (channels, parts[1], boost::is_any_of(","));
568 if (channels.size() > 16) {
569 throw MainSoundConfigurationError (s);
572 for (auto i: channels) {
574 _channels.push_back(optional<Channel>());
576 _channels.push_back(mca_id_to_channel(i));
582 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
585 _channels.resize (channels);
590 MainSoundConfiguration::to_string () const
593 if (_field == MCASoundField::FIVE_POINT_ONE) {
599 for (auto i: _channels) {
603 c += channel_to_mca_id(*i, _field) + ",";
607 if (c.length() > 0) {
608 c = c.substr(0, c.length() - 1);
616 MainSoundConfiguration::mapping (int index) const
618 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
619 return _channels[index];
624 MainSoundConfiguration::set_mapping (int index, Channel c)
626 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
627 _channels[index] = c;
632 dcp::status_to_string (Status s)
649 dcp::string_to_status (string s)
652 return Status::FINAL;
653 } else if (s == "temp") {
655 } else if (s == "pre") {
664 dcp::mca_id_to_channel (string id)
667 return Channel::LEFT;
668 } else if (id == "R") {
669 return Channel::RIGHT;
670 } else if (id == "C") {
671 return Channel::CENTRE;
672 } else if (id == "LFE") {
674 } else if (id == "Ls" || id == "Lss") {
676 } else if (id == "Rs" || id == "Rss") {
678 } else if (id == "HI") {
680 } else if (id == "VIN") {
682 } else if (id == "Lrs") {
684 } else if (id == "Rrs") {
686 } else if (id == "DBOX") {
687 return Channel::MOTION_DATA;
688 } else if (id == "FSKSync") {
689 return Channel::SYNC_SIGNAL;
690 } else if (id == "SLVS") {
691 return Channel::SIGN_LANGUAGE;
694 throw UnknownChannelIdError (id);
699 dcp::channel_to_mca_id (Channel c, MCASoundField field)
706 case Channel::CENTRE:
711 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
713 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
722 case Channel::MOTION_DATA:
724 case Channel::SYNC_SIGNAL:
726 case Channel::SIGN_LANGUAGE:
737 dcp::channel_to_mca_name (Channel c, MCASoundField field)
744 case Channel::CENTRE:
749 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
751 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
753 return "Hearing Impaired";
755 return "Visually Impaired-Narrative";
757 return "Left Rear Surround";
759 return "Right Rear Surround";
760 case Channel::MOTION_DATA:
761 return "D-BOX Motion Code Primary Stream";
762 case Channel::SYNC_SIGNAL:
764 case Channel::SIGN_LANGUAGE:
765 return "Sign Language Video Stream";
775 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
777 static byte_t sync_signal[] = {
778 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
781 static byte_t sign_language[] = {
782 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
787 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
789 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
790 case Channel::CENTRE:
791 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
793 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
795 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
797 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
799 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
801 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
803 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
805 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
806 case Channel::MOTION_DATA:
807 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
808 case Channel::SYNC_SIGNAL:
809 return ASDCP::UL(sync_signal);
810 case Channel::SIGN_LANGUAGE:
811 return ASDCP::UL(sign_language);
821 dcp::used_audio_channels ()
824 c.push_back (Channel::LEFT);
825 c.push_back (Channel::RIGHT);
826 c.push_back (Channel::CENTRE);
827 c.push_back (Channel::LFE);
828 c.push_back (Channel::LS);
829 c.push_back (Channel::RS);
830 c.push_back (Channel::HI);
831 c.push_back (Channel::VI);
832 c.push_back (Channel::BSL);
833 c.push_back (Channel::BSR);
834 c.push_back (Channel::MOTION_DATA);
835 c.push_back (Channel::SYNC_SIGNAL);
836 c.push_back (Channel::SIGN_LANGUAGE);