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);
112 Colour::Colour (int r_, int g_, int b_)
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"));
131 Colour::to_argb_string () const
134 snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
140 Colour::to_rgb_string () const
143 snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
149 dcp::operator== (Colour const & a, Colour const & b)
151 return (a.r == b.r && a.g == b.g && a.b == b.b);
156 dcp::operator!= (Colour const & a, Colour const & b)
163 dcp::effect_to_string (Effect e)
174 boost::throw_exception (MiscError("unknown effect type"));
179 dcp::string_to_effect (string s)
183 } else if (s == "border") {
184 return Effect::BORDER;
185 } else if (s == "shadow") {
186 return Effect::SHADOW;
189 boost::throw_exception (ReadError("unknown subtitle effect type"));
194 dcp::direction_to_string (Direction v)
207 boost::throw_exception (MiscError("unknown subtitle direction type"));
212 dcp::string_to_direction (string s)
214 if (s == "ltr" || s == "horizontal") {
215 return Direction::LTR;
216 } else if (s == "rtl") {
217 return Direction::RTL;
218 } else if (s == "ttb" || s == "vertical") {
219 return Direction::TTB;
220 } else if (s == "btt") {
221 return Direction::BTT;
224 boost::throw_exception (ReadError("unknown subtitle direction type"));
229 dcp::marker_to_string (dcp::Marker m)
259 dcp::marker_from_string (string s)
263 } else if (s == "LFOC") {
265 } else if (s == "FFTC") {
267 } else if (s == "LFTC") {
269 } else if (s == "FFOI") {
271 } else if (s == "LFOI") {
273 } else if (s == "FFEC") {
275 } else if (s == "LFEC") {
277 } else if (s == "FFMC") {
279 } else if (s == "LFMC") {
287 ContentVersion::ContentVersion ()
288 : id ("urn:uuid:" + make_uuid())
294 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
295 : id(node->string_child("Id"))
296 , label_text(node->string_child("LabelText"))
302 ContentVersion::ContentVersion (string label_text_)
303 : id ("urn:uuid:" + make_uuid())
304 , label_text (label_text_)
311 ContentVersion::as_xml (xmlpp::Element* parent) const
313 auto cv = parent->add_child("ContentVersion");
314 cv->add_child("Id")->add_child_text(id);
315 cv->add_child("LabelText")->add_child_text(label_text);
319 Luminance::Luminance (cxml::ConstNodePtr node)
320 : _value(raw_convert<float>(node->content()))
321 , _unit(string_to_unit(node->string_attribute("units")))
327 Luminance::Luminance (float value, Unit unit)
335 Luminance::set_value (float v)
338 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
346 Luminance::as_xml (xmlpp::Element* parent, string ns) const
348 auto lum = parent->add_child("Luminance", ns);
349 lum->set_attribute("units", unit_to_string(_unit));
350 lum->add_child_text(raw_convert<string>(_value, 3));
355 Luminance::unit_to_string (Unit u)
358 case Unit::CANDELA_PER_SQUARE_METRE:
359 return "candela-per-square-metre";
360 case Unit::FOOT_LAMBERT:
361 return "foot-lambert";
371 Luminance::string_to_unit (string u)
373 if (u == "candela-per-square-metre") {
374 return Unit::CANDELA_PER_SQUARE_METRE;
375 } else if (u == "foot-lambert") {
376 return Unit::FOOT_LAMBERT;
379 throw XMLError (String::compose("Invalid luminance unit %1", u));
384 Luminance::value_in_foot_lamberts () const
387 case Unit::CANDELA_PER_SQUARE_METRE:
388 return _value / 3.426;
389 case Unit::FOOT_LAMBERT:
398 dcp::operator== (Luminance const& a, Luminance const& b)
400 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
404 MainSoundConfiguration::MainSoundConfiguration (string s)
406 vector<string> parts;
407 boost::split (parts, s, boost::is_any_of("/"));
409 throw MainSoundConfigurationError(s);
412 if (parts[0] == "51") {
413 _field = MCASoundField::FIVE_POINT_ONE;
414 } else if (parts[0] == "71") {
415 _field = MCASoundField::SEVEN_POINT_ONE;
417 _field = MCASoundField::OTHER;
420 if (parts.size() < 2) {
421 /* I think it's OK to just have the sound field descriptor with no channels, though
422 * to me it's not clear and I might be wrong.
427 vector<string> channels;
428 boost::split (channels, parts[1], boost::is_any_of(","));
430 if (channels.size() > 16) {
431 throw MainSoundConfigurationError (s);
434 for (auto i: channels) {
436 _channels.push_back(optional<Channel>());
438 _channels.push_back(mca_id_to_channel(i));
444 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
447 _channels.resize (channels);
452 MainSoundConfiguration::to_string () const
456 case MCASoundField::FIVE_POINT_ONE:
459 case MCASoundField::SEVEN_POINT_ONE:
466 for (auto i: _channels) {
470 c += channel_to_mca_id(*i, _field) + ",";
474 if (c.length() > 0) {
475 c = c.substr(0, c.length() - 1);
483 MainSoundConfiguration::mapping (int index) const
485 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
486 return _channels[index];
491 MainSoundConfiguration::set_mapping (int index, Channel c)
493 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
494 _channels[index] = c;
499 dcp::status_to_string (Status s)
515 dcp::string_to_status (string s)
518 return Status::FINAL;
519 } else if (s == "temp") {
521 } else if (s == "pre") {
530 dcp::mca_id_to_channel (string id)
532 transform(id.begin(), id.end(), id.begin(), ::tolower);
535 return Channel::LEFT;
536 } else if (id == "r") {
537 return Channel::RIGHT;
538 } else if (id == "c") {
539 return Channel::CENTRE;
540 } else if (id == "lfe") {
542 } else if (id == "ls" || id == "lss") {
544 } else if (id == "rs" || id == "rss") {
546 } else if (id == "hi") {
548 } else if (id == "vin") {
550 } else if (id == "lrs") {
552 } else if (id == "rrs") {
554 } else if (id == "dbox") {
555 return Channel::MOTION_DATA;
556 } else if (id == "sync" || id == "fsksync") {
557 return Channel::SYNC_SIGNAL;
558 } else if (id == "slvs") {
559 return Channel::SIGN_LANGUAGE;
562 throw UnknownChannelIdError (id);
567 dcp::channel_to_mca_id (Channel c, MCASoundField field)
574 case Channel::CENTRE:
579 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
581 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
590 case Channel::MOTION_DATA:
592 case Channel::SYNC_SIGNAL:
594 case Channel::SIGN_LANGUAGE:
605 dcp::channel_to_mca_name (Channel c, MCASoundField field)
612 case Channel::CENTRE:
617 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
619 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
621 return "Hearing Impaired";
623 return "Visually Impaired-Narrative";
625 return "Left Rear Surround";
627 return "Right Rear Surround";
628 case Channel::MOTION_DATA:
629 return "D-BOX Motion Code Primary Stream";
630 case Channel::SYNC_SIGNAL:
632 case Channel::SIGN_LANGUAGE:
633 return "Sign Language Video Stream";
643 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
645 static byte_t sync_signal[] = {
646 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
649 static byte_t sign_language[] = {
650 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
655 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
657 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
658 case Channel::CENTRE:
659 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
661 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
663 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
665 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
667 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
669 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
671 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
673 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
674 case Channel::MOTION_DATA:
675 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
676 case Channel::SYNC_SIGNAL:
677 return ASDCP::UL(sync_signal);
678 case Channel::SIGN_LANGUAGE:
679 return ASDCP::UL(sign_language);
689 dcp::used_audio_channels ()
702 Channel::MOTION_DATA,
703 Channel::SYNC_SIGNAL,
704 Channel::SIGN_LANGUAGE
710 dcp::formulation_to_string (dcp::Formulation formulation)
712 switch (formulation) {
713 case Formulation::MODIFIED_TRANSITIONAL_1:
714 return "modified-transitional-1";
715 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
716 return "multiple-modified-transitional-1";
717 case Formulation::DCI_ANY:
719 case Formulation::DCI_SPECIFIC:
720 return "dci-specific";
728 dcp::string_to_formulation (string formulation)
730 if (formulation == "modified-transitional-1") {
731 return Formulation::MODIFIED_TRANSITIONAL_1;
732 } else if (formulation == "multiple-modified-transitional-1") {
733 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
734 } else if (formulation == "dci-any") {
735 return Formulation::DCI_ANY;
736 } else if (formulation == "dci-specific") {
737 return Formulation::DCI_SPECIFIC;