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::halign_to_string (HAlign h)
205 boost::throw_exception (MiscError("unknown subtitle halign type"));
210 dcp::string_to_halign (string s)
214 } else if (s == "center") {
215 return HAlign::CENTER;
216 } else if (s == "right") {
217 return HAlign::RIGHT;
220 boost::throw_exception (ReadError("unknown subtitle halign type"));
225 dcp::valign_to_string (VAlign v)
236 boost::throw_exception (MiscError("unknown subtitle valign type"));
241 dcp::string_to_valign (string s)
245 } else if (s == "center") {
246 return VAlign::CENTER;
247 } else if (s == "bottom") {
248 return VAlign::BOTTOM;
251 boost::throw_exception (ReadError("unknown subtitle valign type"));
256 dcp::direction_to_string (Direction v)
269 boost::throw_exception (MiscError("unknown subtitle direction type"));
274 dcp::string_to_direction (string s)
276 if (s == "ltr" || s == "horizontal") {
277 return Direction::LTR;
278 } else if (s == "rtl") {
279 return Direction::RTL;
280 } else if (s == "ttb" || s == "vertical") {
281 return Direction::TTB;
282 } else if (s == "btt") {
283 return Direction::BTT;
286 boost::throw_exception (ReadError("unknown subtitle direction type"));
291 dcp::marker_to_string (dcp::Marker m)
321 dcp::marker_from_string (string s)
325 } else if (s == "LFOC") {
327 } else if (s == "FFTC") {
329 } else if (s == "LFTC") {
331 } else if (s == "FFOI") {
333 } else if (s == "LFOI") {
335 } else if (s == "FFEC") {
337 } else if (s == "LFEC") {
339 } else if (s == "FFMC") {
341 } else if (s == "LFMC") {
349 ContentVersion::ContentVersion ()
350 : id ("urn:uuid:" + make_uuid())
356 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
357 : id(node->string_child("Id"))
358 , label_text(node->string_child("LabelText"))
364 ContentVersion::ContentVersion (string label_text_)
365 : id ("urn:uuid:" + make_uuid())
366 , label_text (label_text_)
373 ContentVersion::as_xml (xmlpp::Element* parent) const
375 auto cv = parent->add_child("ContentVersion");
376 cv->add_child("Id")->add_child_text(id);
377 cv->add_child("LabelText")->add_child_text(label_text);
381 Luminance::Luminance (cxml::ConstNodePtr node)
382 : _value(raw_convert<float>(node->content()))
383 , _unit(string_to_unit(node->string_attribute("units")))
389 Luminance::Luminance (float value, Unit unit)
397 Luminance::set_value (float v)
400 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
408 Luminance::as_xml (xmlpp::Element* parent, string ns) const
410 auto lum = parent->add_child("Luminance", ns);
411 lum->set_attribute("units", unit_to_string(_unit));
412 lum->add_child_text(raw_convert<string>(_value, 3));
417 Luminance::unit_to_string (Unit u)
420 case Unit::CANDELA_PER_SQUARE_METRE:
421 return "candela-per-square-metre";
422 case Unit::FOOT_LAMBERT:
423 return "foot-lambert";
433 Luminance::string_to_unit (string u)
435 if (u == "candela-per-square-metre") {
436 return Unit::CANDELA_PER_SQUARE_METRE;
437 } else if (u == "foot-lambert") {
438 return Unit::FOOT_LAMBERT;
441 throw XMLError (String::compose("Invalid luminance unit %1", u));
446 Luminance::value_in_foot_lamberts () const
449 case Unit::CANDELA_PER_SQUARE_METRE:
450 return _value / 3.426;
451 case Unit::FOOT_LAMBERT:
460 dcp::operator== (Luminance const& a, Luminance const& b)
462 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
466 MainSoundConfiguration::MainSoundConfiguration (string s)
468 vector<string> parts;
469 boost::split (parts, s, boost::is_any_of("/"));
471 throw MainSoundConfigurationError(s);
474 if (parts[0] == "51") {
475 _field = MCASoundField::FIVE_POINT_ONE;
476 } else if (parts[0] == "71") {
477 _field = MCASoundField::SEVEN_POINT_ONE;
479 _field = MCASoundField::OTHER;
482 if (parts.size() < 2) {
483 /* I think it's OK to just have the sound field descriptor with no channels, though
484 * to me it's not clear and I might be wrong.
489 vector<string> channels;
490 boost::split (channels, parts[1], boost::is_any_of(","));
492 if (channels.size() > 16) {
493 throw MainSoundConfigurationError (s);
496 for (auto i: channels) {
498 _channels.push_back(optional<Channel>());
500 _channels.push_back(mca_id_to_channel(i));
506 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
509 _channels.resize (channels);
514 MainSoundConfiguration::to_string () const
518 case MCASoundField::FIVE_POINT_ONE:
521 case MCASoundField::SEVEN_POINT_ONE:
528 for (auto i: _channels) {
532 c += channel_to_mca_id(*i, _field) + ",";
536 if (c.length() > 0) {
537 c = c.substr(0, c.length() - 1);
545 MainSoundConfiguration::mapping (int index) const
547 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
548 return _channels[index];
553 MainSoundConfiguration::set_mapping (int index, Channel c)
555 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
556 _channels[index] = c;
561 dcp::status_to_string (Status s)
577 dcp::string_to_status (string s)
580 return Status::FINAL;
581 } else if (s == "temp") {
583 } else if (s == "pre") {
592 dcp::mca_id_to_channel (string id)
594 transform(id.begin(), id.end(), id.begin(), ::tolower);
597 return Channel::LEFT;
598 } else if (id == "r") {
599 return Channel::RIGHT;
600 } else if (id == "c") {
601 return Channel::CENTRE;
602 } else if (id == "lfe") {
604 } else if (id == "ls" || id == "lss") {
606 } else if (id == "rs" || id == "rss") {
608 } else if (id == "hi") {
610 } else if (id == "vin") {
612 } else if (id == "lrs") {
614 } else if (id == "rrs") {
616 } else if (id == "dbox") {
617 return Channel::MOTION_DATA;
618 } else if (id == "sync" || id == "fsksync") {
619 return Channel::SYNC_SIGNAL;
620 } else if (id == "slvs") {
621 return Channel::SIGN_LANGUAGE;
624 throw UnknownChannelIdError (id);
629 dcp::channel_to_mca_id (Channel c, MCASoundField field)
636 case Channel::CENTRE:
641 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
643 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
652 case Channel::MOTION_DATA:
654 case Channel::SYNC_SIGNAL:
656 case Channel::SIGN_LANGUAGE:
667 dcp::channel_to_mca_name (Channel c, MCASoundField field)
674 case Channel::CENTRE:
679 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
681 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
683 return "Hearing Impaired";
685 return "Visually Impaired-Narrative";
687 return "Left Rear Surround";
689 return "Right Rear Surround";
690 case Channel::MOTION_DATA:
691 return "D-BOX Motion Code Primary Stream";
692 case Channel::SYNC_SIGNAL:
694 case Channel::SIGN_LANGUAGE:
695 return "Sign Language Video Stream";
705 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
707 static byte_t sync_signal[] = {
708 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
711 static byte_t sign_language[] = {
712 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
717 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
719 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
720 case Channel::CENTRE:
721 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
723 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
725 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
727 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
729 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
731 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
733 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
735 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
736 case Channel::MOTION_DATA:
737 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
738 case Channel::SYNC_SIGNAL:
739 return ASDCP::UL(sync_signal);
740 case Channel::SIGN_LANGUAGE:
741 return ASDCP::UL(sign_language);
751 dcp::used_audio_channels ()
764 Channel::MOTION_DATA,
765 Channel::SYNC_SIGNAL,
766 Channel::SIGN_LANGUAGE
772 dcp::formulation_to_string (dcp::Formulation formulation)
774 switch (formulation) {
775 case Formulation::MODIFIED_TRANSITIONAL_1:
776 return "modified-transitional-1";
777 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
778 return "multiple-modified-transitional-1";
779 case Formulation::DCI_ANY:
781 case Formulation::DCI_SPECIFIC:
782 return "dci-specific";
790 dcp::string_to_formulation (string formulation)
792 if (formulation == "modified-transitional-1") {
793 return Formulation::MODIFIED_TRANSITIONAL_1;
794 } else if (formulation == "multiple-modified-transitional-1") {
795 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
796 } else if (formulation == "dci-any") {
797 return Formulation::DCI_ANY;
798 } else if (formulation == "dci-specific") {
799 return Formulation::DCI_SPECIFIC;