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::direction_to_string (Direction v)
238 boost::throw_exception (MiscError("unknown subtitle direction type"));
243 dcp::string_to_direction (string s)
245 if (s == "ltr" || s == "horizontal") {
246 return Direction::LTR;
247 } else if (s == "rtl") {
248 return Direction::RTL;
249 } else if (s == "ttb" || s == "vertical") {
250 return Direction::TTB;
251 } else if (s == "btt") {
252 return Direction::BTT;
255 boost::throw_exception (ReadError("unknown subtitle direction type"));
260 dcp::marker_to_string (dcp::Marker m)
290 dcp::marker_from_string (string s)
294 } else if (s == "LFOC") {
296 } else if (s == "FFTC") {
298 } else if (s == "LFTC") {
300 } else if (s == "FFOI") {
302 } else if (s == "LFOI") {
304 } else if (s == "FFEC") {
306 } else if (s == "LFEC") {
308 } else if (s == "FFMC") {
310 } else if (s == "LFMC") {
318 ContentVersion::ContentVersion ()
319 : id ("urn:uuid:" + make_uuid())
325 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
326 : id(node->string_child("Id"))
327 , label_text(node->string_child("LabelText"))
333 ContentVersion::ContentVersion (string label_text_)
334 : id ("urn:uuid:" + make_uuid())
335 , label_text (label_text_)
342 ContentVersion::as_xml (xmlpp::Element* parent) const
344 auto cv = parent->add_child("ContentVersion");
345 cv->add_child("Id")->add_child_text(id);
346 cv->add_child("LabelText")->add_child_text(label_text);
350 Luminance::Luminance (cxml::ConstNodePtr node)
351 : _value(raw_convert<float>(node->content()))
352 , _unit(string_to_unit(node->string_attribute("units")))
358 Luminance::Luminance (float value, Unit unit)
366 Luminance::set_value (float v)
369 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
377 Luminance::as_xml (xmlpp::Element* parent, string ns) const
379 auto lum = parent->add_child("Luminance", ns);
380 lum->set_attribute("units", unit_to_string(_unit));
381 lum->add_child_text(raw_convert<string>(_value, 3));
386 Luminance::unit_to_string (Unit u)
389 case Unit::CANDELA_PER_SQUARE_METRE:
390 return "candela-per-square-metre";
391 case Unit::FOOT_LAMBERT:
392 return "foot-lambert";
402 Luminance::string_to_unit (string u)
404 if (u == "candela-per-square-metre") {
405 return Unit::CANDELA_PER_SQUARE_METRE;
406 } else if (u == "foot-lambert") {
407 return Unit::FOOT_LAMBERT;
410 throw XMLError (String::compose("Invalid luminance unit %1", u));
415 Luminance::value_in_foot_lamberts () const
418 case Unit::CANDELA_PER_SQUARE_METRE:
419 return _value / 3.426;
420 case Unit::FOOT_LAMBERT:
429 dcp::operator== (Luminance const& a, Luminance const& b)
431 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
435 MainSoundConfiguration::MainSoundConfiguration (string s)
437 vector<string> parts;
438 boost::split (parts, s, boost::is_any_of("/"));
440 throw MainSoundConfigurationError(s);
443 if (parts[0] == "51") {
444 _field = MCASoundField::FIVE_POINT_ONE;
445 } else if (parts[0] == "71") {
446 _field = MCASoundField::SEVEN_POINT_ONE;
448 _field = MCASoundField::OTHER;
451 if (parts.size() < 2) {
452 /* I think it's OK to just have the sound field descriptor with no channels, though
453 * to me it's not clear and I might be wrong.
458 vector<string> channels;
459 boost::split (channels, parts[1], boost::is_any_of(","));
461 if (channels.size() > 16) {
462 throw MainSoundConfigurationError (s);
465 for (auto i: channels) {
467 _channels.push_back(optional<Channel>());
469 _channels.push_back(mca_id_to_channel(i));
475 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
478 _channels.resize (channels);
483 MainSoundConfiguration::to_string () const
487 case MCASoundField::FIVE_POINT_ONE:
490 case MCASoundField::SEVEN_POINT_ONE:
497 for (auto i: _channels) {
501 c += channel_to_mca_id(*i, _field) + ",";
505 if (c.length() > 0) {
506 c = c.substr(0, c.length() - 1);
514 MainSoundConfiguration::mapping (int index) const
516 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
517 return _channels[index];
522 MainSoundConfiguration::set_mapping (int index, Channel c)
524 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
525 _channels[index] = c;
530 dcp::status_to_string (Status s)
546 dcp::string_to_status (string s)
549 return Status::FINAL;
550 } else if (s == "temp") {
552 } else if (s == "pre") {
561 dcp::mca_id_to_channel (string id)
563 transform(id.begin(), id.end(), id.begin(), ::tolower);
566 return Channel::LEFT;
567 } else if (id == "r") {
568 return Channel::RIGHT;
569 } else if (id == "c") {
570 return Channel::CENTRE;
571 } else if (id == "lfe") {
573 } else if (id == "ls" || id == "lss") {
575 } else if (id == "rs" || id == "rss") {
577 } else if (id == "hi") {
579 } else if (id == "vin") {
581 } else if (id == "lrs") {
583 } else if (id == "rrs") {
585 } else if (id == "dbox") {
586 return Channel::MOTION_DATA;
587 } else if (id == "sync" || id == "fsksync") {
588 return Channel::SYNC_SIGNAL;
589 } else if (id == "slvs") {
590 return Channel::SIGN_LANGUAGE;
593 throw UnknownChannelIdError (id);
598 dcp::channel_to_mca_id (Channel c, MCASoundField field)
605 case Channel::CENTRE:
610 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
612 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
621 case Channel::MOTION_DATA:
623 case Channel::SYNC_SIGNAL:
625 case Channel::SIGN_LANGUAGE:
636 dcp::channel_to_mca_name (Channel c, MCASoundField field)
643 case Channel::CENTRE:
648 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
650 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
652 return "Hearing Impaired";
654 return "Visually Impaired-Narrative";
656 return "Left Rear Surround";
658 return "Right Rear Surround";
659 case Channel::MOTION_DATA:
660 return "D-BOX Motion Code Primary Stream";
661 case Channel::SYNC_SIGNAL:
663 case Channel::SIGN_LANGUAGE:
664 return "Sign Language Video Stream";
674 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
676 static byte_t sync_signal[] = {
677 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
680 static byte_t sign_language[] = {
681 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
686 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
688 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
689 case Channel::CENTRE:
690 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
692 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
694 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
696 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
698 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
700 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
702 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
704 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
705 case Channel::MOTION_DATA:
706 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
707 case Channel::SYNC_SIGNAL:
708 return ASDCP::UL(sync_signal);
709 case Channel::SIGN_LANGUAGE:
710 return ASDCP::UL(sign_language);
720 dcp::used_audio_channels ()
733 Channel::MOTION_DATA,
734 Channel::SYNC_SIGNAL,
735 Channel::SIGN_LANGUAGE
741 dcp::formulation_to_string (dcp::Formulation formulation)
743 switch (formulation) {
744 case Formulation::MODIFIED_TRANSITIONAL_1:
745 return "modified-transitional-1";
746 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
747 return "multiple-modified-transitional-1";
748 case Formulation::DCI_ANY:
750 case Formulation::DCI_SPECIFIC:
751 return "dci-specific";
759 dcp::string_to_formulation (string formulation)
761 if (formulation == "modified-transitional-1") {
762 return Formulation::MODIFIED_TRANSITIONAL_1;
763 } else if (formulation == "multiple-modified-transitional-1") {
764 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
765 } else if (formulation == "dci-any") {
766 return Formulation::DCI_ANY;
767 } else if (formulation == "dci-specific") {
768 return Formulation::DCI_SPECIFIC;