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
517 case MCASoundField::FIVE_POINT_ONE:
520 case MCASoundField::SEVEN_POINT_ONE:
527 for (auto i: _channels) {
531 c += channel_to_mca_id(*i, _field) + ",";
535 if (c.length() > 0) {
536 c = c.substr(0, c.length() - 1);
544 MainSoundConfiguration::mapping (int index) const
546 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
547 return _channels[index];
552 MainSoundConfiguration::set_mapping (int index, Channel c)
554 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
555 _channels[index] = c;
560 dcp::status_to_string (Status s)
576 dcp::string_to_status (string s)
579 return Status::FINAL;
580 } else if (s == "temp") {
582 } else if (s == "pre") {
591 dcp::mca_id_to_channel (string id)
594 return Channel::LEFT;
595 } else if (id == "R") {
596 return Channel::RIGHT;
597 } else if (id == "C") {
598 return Channel::CENTRE;
599 } else if (id == "LFE") {
601 } else if (id == "Ls" || id == "Lss") {
603 } else if (id == "Rs" || id == "Rss") {
605 } else if (id == "HI") {
607 } else if (id == "VIN") {
609 } else if (id == "Lrs") {
611 } else if (id == "Rrs") {
613 } else if (id == "DBOX") {
614 return Channel::MOTION_DATA;
615 } else if (id == "FSKSync") {
616 return Channel::SYNC_SIGNAL;
617 } else if (id == "SLVS") {
618 return Channel::SIGN_LANGUAGE;
621 throw UnknownChannelIdError (id);
626 dcp::channel_to_mca_id (Channel c, MCASoundField field)
633 case Channel::CENTRE:
638 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
640 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
649 case Channel::MOTION_DATA:
651 case Channel::SYNC_SIGNAL:
653 case Channel::SIGN_LANGUAGE:
664 dcp::channel_to_mca_name (Channel c, MCASoundField field)
671 case Channel::CENTRE:
676 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
678 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
680 return "Hearing Impaired";
682 return "Visually Impaired-Narrative";
684 return "Left Rear Surround";
686 return "Right Rear Surround";
687 case Channel::MOTION_DATA:
688 return "D-BOX Motion Code Primary Stream";
689 case Channel::SYNC_SIGNAL:
691 case Channel::SIGN_LANGUAGE:
692 return "Sign Language Video Stream";
702 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
704 static byte_t sync_signal[] = {
705 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
708 static byte_t sign_language[] = {
709 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
714 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
716 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
717 case Channel::CENTRE:
718 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
720 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
722 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
724 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
726 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
728 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
730 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
732 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
733 case Channel::MOTION_DATA:
734 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
735 case Channel::SYNC_SIGNAL:
736 return ASDCP::UL(sync_signal);
737 case Channel::SIGN_LANGUAGE:
738 return ASDCP::UL(sign_language);
748 dcp::used_audio_channels ()
761 Channel::MOTION_DATA,
762 Channel::SYNC_SIGNAL,
763 Channel::SIGN_LANGUAGE
769 dcp::formulation_to_string (dcp::Formulation formulation)
771 switch (formulation) {
772 case Formulation::MODIFIED_TRANSITIONAL_1:
773 return "modified-transitional-1";
774 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
775 return "multiple-modified-transitional-1";
776 case Formulation::DCI_ANY:
778 case Formulation::DCI_SPECIFIC:
779 return "dci-specific";
787 dcp::string_to_formulation (string formulation)
789 if (formulation == "modified-transitional-1") {
790 return Formulation::MODIFIED_TRANSITIONAL_1;
791 } else if (formulation == "multiple-modified-transitional-1") {
792 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
793 } else if (formulation == "dci-any") {
794 return Formulation::DCI_ANY;
795 } else if (formulation == "dci-specific") {
796 return Formulation::DCI_SPECIFIC;