2 Copyright (C) 2012-2021 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.
35 /** @file src/types.cc
36 * @brief Miscellaneous types
40 #include "compose.hpp"
41 #include "dcp_assert.h"
42 #include "exceptions.h"
43 #include "raw_convert.h"
46 LIBDCP_DISABLE_WARNINGS
47 #include <libxml++/libxml++.h>
48 LIBDCP_ENABLE_WARNINGS
49 #include <boost/algorithm/string.hpp>
61 using namespace boost;
64 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
66 return (a.width == b.width && a.height == b.height);
70 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
76 /** Construct a Fraction from a string of the form "numerator denominator"
79 Fraction::Fraction (string s)
82 split (b, s, is_any_of (" "));
84 boost::throw_exception (XMLError("malformed fraction " + s + " in XML node"));
86 numerator = raw_convert<int> (b[0]);
87 denominator = raw_convert<int> (b[1]);
92 Fraction::as_string () const
94 return String::compose ("%1 %2", numerator, denominator);
99 dcp::operator== (Fraction const & a, Fraction const & b)
101 return (a.numerator == b.numerator && a.denominator == b.denominator);
106 dcp::operator!= (Fraction const & a, Fraction const & b)
108 return (a.numerator != b.numerator || a.denominator != b.denominator);
118 Colour::Colour (int r_, int g_, int b_)
127 Colour::Colour (string argb_hex)
130 if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
131 boost::throw_exception (XMLError ("could not parse colour string"));
137 Colour::to_argb_string () const
140 snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
146 Colour::to_rgb_string () const
149 snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
155 dcp::operator== (Colour const & a, Colour const & b)
157 return (a.r == b.r && a.g == b.g && a.b == b.b);
162 dcp::operator!= (Colour const & a, Colour const & b)
169 dcp::effect_to_string (Effect e)
180 boost::throw_exception (MiscError("unknown effect type"));
185 dcp::string_to_effect (string s)
189 } else if (s == "border") {
190 return Effect::BORDER;
191 } else if (s == "shadow") {
192 return Effect::SHADOW;
195 boost::throw_exception (ReadError("unknown subtitle effect type"));
200 dcp::halign_to_string (HAlign h)
211 boost::throw_exception (MiscError("unknown subtitle halign type"));
216 dcp::string_to_halign (string s)
220 } else if (s == "center") {
221 return HAlign::CENTER;
222 } else if (s == "right") {
223 return HAlign::RIGHT;
226 boost::throw_exception (ReadError("unknown subtitle halign type"));
231 dcp::valign_to_string (VAlign v)
242 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"));
262 dcp::direction_to_string (Direction v)
275 boost::throw_exception (MiscError("unknown subtitle direction type"));
280 dcp::string_to_direction (string s)
282 if (s == "ltr" || s == "horizontal") {
283 return Direction::LTR;
284 } else if (s == "rtl") {
285 return Direction::RTL;
286 } else if (s == "ttb" || s == "vertical") {
287 return Direction::TTB;
288 } else if (s == "btt") {
289 return Direction::BTT;
292 boost::throw_exception (ReadError("unknown subtitle direction type"));
297 dcp::marker_to_string (dcp::Marker m)
327 dcp::marker_from_string (string s)
331 } else if (s == "LFOC") {
333 } else if (s == "FFTC") {
335 } else if (s == "LFTC") {
337 } else if (s == "FFOI") {
339 } else if (s == "LFOI") {
341 } else if (s == "FFEC") {
343 } else if (s == "LFEC") {
345 } else if (s == "FFMC") {
347 } else if (s == "LFMC") {
355 ContentVersion::ContentVersion ()
356 : id ("urn:uuid:" + make_uuid())
362 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
363 : id(node->string_child("Id"))
364 , label_text(node->string_child("LabelText"))
370 ContentVersion::ContentVersion (string label_text_)
371 : id ("urn:uuid:" + make_uuid())
372 , label_text (label_text_)
379 ContentVersion::as_xml (xmlpp::Element* parent) const
381 auto cv = parent->add_child("ContentVersion");
382 cv->add_child("Id")->add_child_text(id);
383 cv->add_child("LabelText")->add_child_text(label_text);
387 Luminance::Luminance (cxml::ConstNodePtr node)
388 : _value(raw_convert<float>(node->content()))
389 , _unit(string_to_unit(node->string_attribute("units")))
395 Luminance::Luminance (float value, Unit unit)
403 Luminance::set_value (float v)
406 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
414 Luminance::as_xml (xmlpp::Element* parent, string ns) const
416 auto lum = parent->add_child("Luminance", ns);
417 lum->set_attribute("units", unit_to_string(_unit));
418 lum->add_child_text(raw_convert<string>(_value, 3));
423 Luminance::unit_to_string (Unit u)
426 case Unit::CANDELA_PER_SQUARE_METRE:
427 return "candela-per-square-metre";
428 case Unit::FOOT_LAMBERT:
429 return "foot-lambert";
439 Luminance::string_to_unit (string u)
441 if (u == "candela-per-square-metre") {
442 return Unit::CANDELA_PER_SQUARE_METRE;
443 } else if (u == "foot-lambert") {
444 return Unit::FOOT_LAMBERT;
447 throw XMLError (String::compose("Invalid luminance unit %1", u));
452 Luminance::value_in_foot_lamberts () const
455 case Unit::CANDELA_PER_SQUARE_METRE:
456 return _value / 3.426;
457 case Unit::FOOT_LAMBERT:
466 dcp::operator== (Luminance const& a, Luminance const& b)
468 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
472 MainSoundConfiguration::MainSoundConfiguration (string s)
474 vector<string> parts;
475 boost::split (parts, s, boost::is_any_of("/"));
476 if (parts.size() != 2) {
477 throw MainSoundConfigurationError (s);
480 if (parts[0] == "51") {
481 _field = MCASoundField::FIVE_POINT_ONE;
482 } else if (parts[0] == "71") {
483 _field = MCASoundField::SEVEN_POINT_ONE;
485 throw MainSoundConfigurationError (s);
488 vector<string> channels;
489 boost::split (channels, parts[1], boost::is_any_of(","));
491 if (channels.size() > 16) {
492 throw MainSoundConfigurationError (s);
495 for (auto i: channels) {
497 _channels.push_back(optional<Channel>());
499 _channels.push_back(mca_id_to_channel(i));
505 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
508 _channels.resize (channels);
513 MainSoundConfiguration::to_string () const
516 if (_field == MCASoundField::FIVE_POINT_ONE) {
522 for (auto i: _channels) {
526 c += channel_to_mca_id(*i, _field) + ",";
530 if (c.length() > 0) {
531 c = c.substr(0, c.length() - 1);
539 MainSoundConfiguration::mapping (int index) const
541 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
542 return _channels[index];
547 MainSoundConfiguration::set_mapping (int index, Channel c)
549 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
550 _channels[index] = c;
555 dcp::status_to_string (Status s)
571 dcp::string_to_status (string s)
574 return Status::FINAL;
575 } else if (s == "temp") {
577 } else if (s == "pre") {
586 dcp::mca_id_to_channel (string id)
589 return Channel::LEFT;
590 } else if (id == "R") {
591 return Channel::RIGHT;
592 } else if (id == "C") {
593 return Channel::CENTRE;
594 } else if (id == "LFE") {
596 } else if (id == "Ls" || id == "Lss") {
598 } else if (id == "Rs" || id == "Rss") {
600 } else if (id == "HI") {
602 } else if (id == "VIN") {
604 } else if (id == "Lrs") {
606 } else if (id == "Rrs") {
608 } else if (id == "DBOX") {
609 return Channel::MOTION_DATA;
610 } else if (id == "FSKSync") {
611 return Channel::SYNC_SIGNAL;
612 } else if (id == "SLVS") {
613 return Channel::SIGN_LANGUAGE;
616 throw UnknownChannelIdError (id);
621 dcp::channel_to_mca_id (Channel c, MCASoundField field)
628 case Channel::CENTRE:
633 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
635 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
644 case Channel::MOTION_DATA:
646 case Channel::SYNC_SIGNAL:
648 case Channel::SIGN_LANGUAGE:
659 dcp::channel_to_mca_name (Channel c, MCASoundField field)
666 case Channel::CENTRE:
671 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
673 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
675 return "Hearing Impaired";
677 return "Visually Impaired-Narrative";
679 return "Left Rear Surround";
681 return "Right Rear Surround";
682 case Channel::MOTION_DATA:
683 return "D-BOX Motion Code Primary Stream";
684 case Channel::SYNC_SIGNAL:
686 case Channel::SIGN_LANGUAGE:
687 return "Sign Language Video Stream";
697 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
699 static byte_t sync_signal[] = {
700 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
703 static byte_t sign_language[] = {
704 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
709 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
711 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
712 case Channel::CENTRE:
713 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
715 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
717 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
719 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
721 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
723 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
725 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
727 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
728 case Channel::MOTION_DATA:
729 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
730 case Channel::SYNC_SIGNAL:
731 return ASDCP::UL(sync_signal);
732 case Channel::SIGN_LANGUAGE:
733 return ASDCP::UL(sign_language);
743 dcp::used_audio_channels ()
756 Channel::MOTION_DATA,
757 Channel::SYNC_SIGNAL,
758 Channel::SIGN_LANGUAGE
764 dcp::formulation_to_string (dcp::Formulation formulation)
766 switch (formulation) {
767 case Formulation::MODIFIED_TRANSITIONAL_1:
768 return "modified-transitional-1";
769 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
770 return "multiple-modified-transitional-1";
771 case Formulation::DCI_ANY:
773 case Formulation::DCI_SPECIFIC:
774 return "dci-specific";
782 dcp::string_to_formulation (string formulation)
784 if (formulation == "modified-transitional-1") {
785 return Formulation::MODIFIED_TRANSITIONAL_1;
786 } else if (formulation == "multiple-modified-transitional-1") {
787 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
788 } else if (formulation == "dci-any") {
789 return Formulation::DCI_ANY;
790 } else if (formulation == "dci-specific") {
791 return Formulation::DCI_SPECIFIC;