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>
41 #include <boost/foreach.hpp>
52 using namespace boost;
54 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
56 return (a.width == b.width && a.height == b.height);
59 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
65 /** Construct a Fraction from a string of the form <numerator> <denominator>
68 Fraction::Fraction (string s)
71 split (b, s, is_any_of (" "));
73 boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
75 numerator = raw_convert<int> (b[0]);
76 denominator = raw_convert<int> (b[1]);
80 Fraction::as_string () const
82 return String::compose ("%1 %2", numerator, denominator);
86 dcp::operator== (Fraction const & a, Fraction const & b)
88 return (a.numerator == b.numerator && a.denominator == b.denominator);
92 dcp::operator!= (Fraction const & a, Fraction const & b)
94 return (a.numerator != b.numerator || a.denominator != b.denominator);
98 /** Construct a Colour, initialising it to black. */
107 /** Construct a Colour from R, G and B. The values run between
110 Colour::Colour (int r_, int g_, int b_)
118 /** Construct a Colour from an ARGB hex string; the alpha value is ignored.
119 * @param argb_hex A string of the form AARRGGBB, where e.g. RR is a two-character
122 Colour::Colour (string argb_hex)
125 if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
126 boost::throw_exception (XMLError ("could not parse colour string"));
130 /** @return An ARGB string of the form AARRGGBB, where e.g. RR is a two-character
131 * hex value. The alpha value will always be FF (ie 255; maximum alpha).
134 Colour::to_argb_string () const
137 snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
141 /** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
145 Colour::to_rgb_string () const
148 snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
152 /** operator== for Colours.
153 * @param a First colour to compare.
154 * @param b Second colour to compare.
157 dcp::operator== (Colour const & a, Colour const & b)
159 return (a.r == b.r && a.g == b.g && a.b == b.b);
162 /** operator!= for Colours.
163 * @param a First colour to compare.
164 * @param b Second colour to compare.
167 dcp::operator!= (Colour const & a, Colour const & b)
174 dcp::effect_to_string (Effect e)
185 boost::throw_exception (MiscError ("unknown effect type"));
189 dcp::string_to_effect (string s)
193 } else if (s == "border") {
194 return Effect::BORDER;
195 } else if (s == "shadow") {
196 return Effect::SHADOW;
199 boost::throw_exception (ReadError ("unknown subtitle effect type"));
204 dcp::halign_to_string (HAlign h)
215 boost::throw_exception (MiscError ("unknown subtitle halign type"));
219 dcp::string_to_halign (string s)
223 } else if (s == "center") {
224 return HAlign::CENTER;
225 } else if (s == "right") {
226 return HAlign::RIGHT;
229 boost::throw_exception (ReadError ("unknown subtitle halign type"));
233 dcp::valign_to_string (VAlign v)
244 boost::throw_exception (MiscError ("unknown subtitle valign type"));
248 dcp::string_to_valign (string s)
252 } else if (s == "center") {
253 return VAlign::CENTER;
254 } else if (s == "bottom") {
255 return VAlign::BOTTOM;
258 boost::throw_exception (ReadError ("unknown subtitle valign type"));
262 dcp::direction_to_string (Direction v)
275 boost::throw_exception (MiscError ("unknown subtitle direction type"));
279 dcp::string_to_direction (string s)
281 if (s == "ltr" || s == "horizontal") {
282 return Direction::LTR;
283 } else if (s == "rtl") {
284 return Direction::RTL;
285 } else if (s == "ttb" || s == "vertical") {
286 return Direction::TTB;
287 } else if (s == "btt") {
288 return Direction::BTT;
291 boost::throw_exception (ReadError ("unknown subtitle direction type"));
294 /** Convert a content kind to a string which can be used in a
295 * <ContentKind> node.
296 * @param kind ContentKind.
300 dcp::content_kind_to_string (ContentKind kind)
303 case ContentKind::FEATURE:
305 case ContentKind::SHORT:
307 case ContentKind::TRAILER:
309 case ContentKind::TEST:
311 case ContentKind::TRANSITIONAL:
312 return "transitional";
313 case ContentKind::RATING:
315 case ContentKind::TEASER:
317 case ContentKind::POLICY:
319 case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
321 case ContentKind::ADVERTISEMENT:
322 return "advertisement";
323 case ContentKind::EPISODE:
325 case ContentKind::PROMO:
332 /** Convert a string from a <ContentKind> node to a libdcp ContentKind.
333 * Reasonably tolerant about varying case.
334 * @param kind Content kind string.
335 * @return libdcp ContentKind.
338 dcp::content_kind_from_string (string kind)
340 transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
342 if (kind == "feature") {
343 return ContentKind::FEATURE;
344 } else if (kind == "short") {
345 return ContentKind::SHORT;
346 } else if (kind == "trailer") {
347 return ContentKind::TRAILER;
348 } else if (kind == "test") {
349 return ContentKind::TEST;
350 } else if (kind == "transitional") {
351 return ContentKind::TRANSITIONAL;
352 } else if (kind == "rating") {
353 return ContentKind::RATING;
354 } else if (kind == "teaser") {
355 return ContentKind::TEASER;
356 } else if (kind == "policy") {
357 return ContentKind::POLICY;
358 } else if (kind == "psa") {
359 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
360 } else if (kind == "advertisement") {
361 return ContentKind::ADVERTISEMENT;
362 } else if (kind == "episode") {
363 return ContentKind::EPISODE;
364 } else if (kind == "promo") {
365 return ContentKind::PROMO;
368 throw BadContentKindError (kind);
373 dcp::marker_to_string (dcp::Marker m)
402 dcp::marker_from_string (string s)
406 } else if (s == "LFOC") {
408 } else if (s == "FFTC") {
410 } else if (s == "LFTC") {
412 } else if (s == "FFOI") {
414 } else if (s == "LFOI") {
416 } else if (s == "FFEC") {
418 } else if (s == "LFEC") {
420 } else if (s == "FFMC") {
422 } else if (s == "LFMC") {
429 Rating::Rating (cxml::ConstNodePtr node)
431 agency = node->string_child("Agency");
432 label = node->string_child("Label");
437 Rating::as_xml (xmlpp::Element* parent) const
439 parent->add_child("Agency")->add_child_text(agency);
440 parent->add_child("Label")->add_child_text(label);
444 dcp::operator== (Rating const & a, Rating const & b)
446 return a.agency == b.agency && a.label == b.label;
449 ContentVersion::ContentVersion ()
450 : id ("urn:uuid:" + make_uuid())
456 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
458 id = node->string_child("Id");
459 label_text = node->string_child("LabelText");
463 ContentVersion::ContentVersion (string label_text_)
464 : id ("urn:uuid:" + make_uuid())
465 , label_text (label_text_)
472 ContentVersion::as_xml (xmlpp::Element* parent) const
474 xmlpp::Node* cv = parent->add_child("ContentVersion");
475 cv->add_child("Id")->add_child_text(id);
476 cv->add_child("LabelText")->add_child_text(label_text);
480 Luminance::Luminance (cxml::ConstNodePtr node)
482 _unit = string_to_unit (node->string_attribute("units"));
483 _value = raw_convert<float> (node->content());
487 Luminance::Luminance (float value, Unit unit)
495 Luminance::set_value (float v)
498 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
506 Luminance::as_xml (xmlpp::Element* parent, string ns) const
508 xmlpp::Element* lum = parent->add_child("Luminance", ns);
509 lum->set_attribute("units", unit_to_string(_unit));
510 lum->add_child_text(raw_convert<string>(_value, 3));
515 Luminance::unit_to_string (Unit u)
518 case Unit::CANDELA_PER_SQUARE_METRE:
519 return "candela-per-square-metre";
520 case Unit::FOOT_LAMBERT:
521 return "foot-lambert";
531 Luminance::string_to_unit (string u)
533 if (u == "candela-per-square-metre") {
534 return Unit::CANDELA_PER_SQUARE_METRE;
535 } else if (u == "foot-lambert") {
536 return Unit::FOOT_LAMBERT;
539 throw XMLError (String::compose("Invalid luminance unit %1", u));
544 dcp::operator== (Luminance const& a, Luminance const& b)
546 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
550 MainSoundConfiguration::MainSoundConfiguration (string s)
552 vector<string> parts;
553 boost::split (parts, s, boost::is_any_of("/"));
554 if (parts.size() != 2) {
555 throw MainSoundConfigurationError (s);
558 if (parts[0] == "51") {
559 _field = MCASoundField::FIVE_POINT_ONE;
560 } else if (parts[0] == "71") {
561 _field = MCASoundField::SEVEN_POINT_ONE;
563 throw MainSoundConfigurationError (s);
566 vector<string> channels;
567 boost::split (channels, parts[1], boost::is_any_of(","));
569 if (channels.size() > 16) {
570 throw MainSoundConfigurationError (s);
573 BOOST_FOREACH (string i, channels) {
575 _channels.push_back(optional<Channel>());
577 _channels.push_back(mca_id_to_channel(i));
583 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
586 _channels.resize (channels);
591 MainSoundConfiguration::to_string () const
594 if (_field == MCASoundField::FIVE_POINT_ONE) {
600 for (auto i: _channels) {
604 c += channel_to_mca_id(*i, _field) + ",";
608 if (c.length() > 0) {
609 c = c.substr(0, c.length() - 1);
617 MainSoundConfiguration::mapping (int index) const
619 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
620 return _channels[index];
625 MainSoundConfiguration::set_mapping (int index, Channel c)
627 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
628 _channels[index] = c;
633 dcp::status_to_string (Status s)
650 dcp::string_to_status (string s)
653 return Status::FINAL;
654 } else if (s == "temp") {
656 } else if (s == "pre") {
665 dcp::mca_id_to_channel (string id)
668 return Channel::LEFT;
669 } else if (id == "R") {
670 return Channel::RIGHT;
671 } else if (id == "C") {
672 return Channel::CENTRE;
673 } else if (id == "LFE") {
675 } else if (id == "Ls" || id == "Lss") {
677 } else if (id == "Rs" || id == "Rss") {
679 } else if (id == "HI") {
681 } else if (id == "VIN") {
683 } else if (id == "Lrs") {
685 } else if (id == "Rrs") {
687 } else if (id == "DBOX") {
688 return Channel::MOTION_DATA;
689 } else if (id == "FSKSync") {
690 return Channel::SYNC_SIGNAL;
691 } else if (id == "SLVS") {
692 return Channel::SIGN_LANGUAGE;
695 throw UnknownChannelIdError (id);
700 dcp::channel_to_mca_id (Channel c, MCASoundField field)
707 case Channel::CENTRE:
712 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
714 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
723 case Channel::MOTION_DATA:
725 case Channel::SYNC_SIGNAL:
727 case Channel::SIGN_LANGUAGE:
738 dcp::channel_to_mca_name (Channel c, MCASoundField field)
745 case Channel::CENTRE:
750 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
752 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
754 return "Hearing Impaired";
756 return "Visually Impaired-Narrative";
758 return "Left Rear Surround";
760 return "Right Rear Surround";
761 case Channel::MOTION_DATA:
762 return "D-BOX Motion Code Primary Stream";
763 case Channel::SYNC_SIGNAL:
765 case Channel::SIGN_LANGUAGE:
766 return "Sign Language Video Stream";
776 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
778 static byte_t sync_signal[] = {
779 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
782 static byte_t sign_language[] = {
783 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
788 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
790 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
791 case Channel::CENTRE:
792 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
794 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
796 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
798 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
800 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
802 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
804 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
806 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
807 case Channel::MOTION_DATA:
808 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
809 case Channel::SYNC_SIGNAL:
810 return ASDCP::UL(sync_signal);
811 case Channel::SIGN_LANGUAGE:
812 return ASDCP::UL(sign_language);
822 dcp::used_audio_channels ()
825 c.push_back (Channel::LEFT);
826 c.push_back (Channel::RIGHT);
827 c.push_back (Channel::CENTRE);
828 c.push_back (Channel::LFE);
829 c.push_back (Channel::LS);
830 c.push_back (Channel::RS);
831 c.push_back (Channel::HI);
832 c.push_back (Channel::VI);
833 c.push_back (Channel::BSL);
834 c.push_back (Channel::BSR);
835 c.push_back (Channel::MOTION_DATA);
836 c.push_back (Channel::SYNC_SIGNAL);
837 c.push_back (Channel::SIGN_LANGUAGE);