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("/"));
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 _field = MCASoundField::OTHER;
488 if (parts.size() < 2) {
489 /* I think it's OK to just have the sound field descriptor with no channels, though
490 * to me it's not clear and I might be wrong.
495 vector<string> channels;
496 boost::split (channels, parts[1], boost::is_any_of(","));
498 if (channels.size() > 16) {
499 throw MainSoundConfigurationError (s);
502 for (auto i: channels) {
504 _channels.push_back(optional<Channel>());
506 _channels.push_back(mca_id_to_channel(i));
512 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
515 _channels.resize (channels);
520 MainSoundConfiguration::to_string () const
524 case MCASoundField::FIVE_POINT_ONE:
527 case MCASoundField::SEVEN_POINT_ONE:
534 for (auto i: _channels) {
538 c += channel_to_mca_id(*i, _field) + ",";
542 if (c.length() > 0) {
543 c = c.substr(0, c.length() - 1);
551 MainSoundConfiguration::mapping (int index) const
553 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
554 return _channels[index];
559 MainSoundConfiguration::set_mapping (int index, Channel c)
561 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
562 _channels[index] = c;
567 dcp::status_to_string (Status s)
583 dcp::string_to_status (string s)
586 return Status::FINAL;
587 } else if (s == "temp") {
589 } else if (s == "pre") {
598 dcp::mca_id_to_channel (string id)
600 transform(id.begin(), id.end(), id.begin(), ::tolower);
603 return Channel::LEFT;
604 } else if (id == "r") {
605 return Channel::RIGHT;
606 } else if (id == "c") {
607 return Channel::CENTRE;
608 } else if (id == "lfe") {
610 } else if (id == "ls" || id == "lss") {
612 } else if (id == "rs" || id == "rss") {
614 } else if (id == "hi") {
616 } else if (id == "vin") {
618 } else if (id == "lrs") {
620 } else if (id == "rrs") {
622 } else if (id == "dbox") {
623 return Channel::MOTION_DATA;
624 } else if (id == "sync" || id == "fsksync") {
625 return Channel::SYNC_SIGNAL;
626 } else if (id == "slvs") {
627 return Channel::SIGN_LANGUAGE;
630 throw UnknownChannelIdError (id);
635 dcp::channel_to_mca_id (Channel c, MCASoundField field)
642 case Channel::CENTRE:
647 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
649 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
658 case Channel::MOTION_DATA:
660 case Channel::SYNC_SIGNAL:
662 case Channel::SIGN_LANGUAGE:
673 dcp::channel_to_mca_name (Channel c, MCASoundField field)
680 case Channel::CENTRE:
685 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
687 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
689 return "Hearing Impaired";
691 return "Visually Impaired-Narrative";
693 return "Left Rear Surround";
695 return "Right Rear Surround";
696 case Channel::MOTION_DATA:
697 return "D-BOX Motion Code Primary Stream";
698 case Channel::SYNC_SIGNAL:
700 case Channel::SIGN_LANGUAGE:
701 return "Sign Language Video Stream";
711 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
713 static byte_t sync_signal[] = {
714 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
717 static byte_t sign_language[] = {
718 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
723 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
725 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
726 case Channel::CENTRE:
727 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
729 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
731 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
733 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
735 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
737 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
739 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
741 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
742 case Channel::MOTION_DATA:
743 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
744 case Channel::SYNC_SIGNAL:
745 return ASDCP::UL(sync_signal);
746 case Channel::SIGN_LANGUAGE:
747 return ASDCP::UL(sign_language);
757 dcp::used_audio_channels ()
770 Channel::MOTION_DATA,
771 Channel::SYNC_SIGNAL,
772 Channel::SIGN_LANGUAGE
778 dcp::formulation_to_string (dcp::Formulation formulation)
780 switch (formulation) {
781 case Formulation::MODIFIED_TRANSITIONAL_1:
782 return "modified-transitional-1";
783 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
784 return "multiple-modified-transitional-1";
785 case Formulation::DCI_ANY:
787 case Formulation::DCI_SPECIFIC:
788 return "dci-specific";
796 dcp::string_to_formulation (string formulation)
798 if (formulation == "modified-transitional-1") {
799 return Formulation::MODIFIED_TRANSITIONAL_1;
800 } else if (formulation == "multiple-modified-transitional-1") {
801 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
802 } else if (formulation == "dci-any") {
803 return Formulation::DCI_ANY;
804 } else if (formulation == "dci-specific") {
805 return Formulation::DCI_SPECIFIC;