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"));
296 /** Convert a content kind to a string which can be used in a
297 * <ContentKind> node
298 * @param kind ContentKind
302 dcp::content_kind_to_string (ContentKind kind)
305 case ContentKind::FEATURE:
307 case ContentKind::SHORT:
309 case ContentKind::TRAILER:
311 case ContentKind::TEST:
313 case ContentKind::TRANSITIONAL:
314 return "transitional";
315 case ContentKind::RATING:
317 case ContentKind::TEASER:
319 case ContentKind::POLICY:
321 case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
323 case ContentKind::ADVERTISEMENT:
324 return "advertisement";
325 case ContentKind::EPISODE:
327 case ContentKind::PROMO:
335 /** Convert a string from a <ContentKind> node to a libdcp ContentKind.
336 * Reasonably tolerant about varying case
337 * @param kind Content kind string
338 * @return libdcp ContentKind
341 dcp::content_kind_from_string (string kind)
343 transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
345 if (kind == "feature") {
346 return ContentKind::FEATURE;
347 } else if (kind == "short") {
348 return ContentKind::SHORT;
349 } else if (kind == "trailer") {
350 return ContentKind::TRAILER;
351 } else if (kind == "test") {
352 return ContentKind::TEST;
353 } else if (kind == "transitional") {
354 return ContentKind::TRANSITIONAL;
355 } else if (kind == "rating") {
356 return ContentKind::RATING;
357 } else if (kind == "teaser") {
358 return ContentKind::TEASER;
359 } else if (kind == "policy") {
360 return ContentKind::POLICY;
361 } else if (kind == "psa") {
362 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
363 } else if (kind == "advertisement") {
364 return ContentKind::ADVERTISEMENT;
365 } else if (kind == "episode") {
366 return ContentKind::EPISODE;
367 } else if (kind == "promo") {
368 return ContentKind::PROMO;
371 throw BadContentKindError (kind);
376 dcp::marker_to_string (dcp::Marker m)
406 dcp::marker_from_string (string s)
410 } else if (s == "LFOC") {
412 } else if (s == "FFTC") {
414 } else if (s == "LFTC") {
416 } else if (s == "FFOI") {
418 } else if (s == "LFOI") {
420 } else if (s == "FFEC") {
422 } else if (s == "LFEC") {
424 } else if (s == "FFMC") {
426 } else if (s == "LFMC") {
434 ContentVersion::ContentVersion ()
435 : id ("urn:uuid:" + make_uuid())
441 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
442 : id(node->string_child("Id"))
443 , label_text(node->string_child("LabelText"))
449 ContentVersion::ContentVersion (string label_text_)
450 : id ("urn:uuid:" + make_uuid())
451 , label_text (label_text_)
458 ContentVersion::as_xml (xmlpp::Element* parent) const
460 auto cv = parent->add_child("ContentVersion");
461 cv->add_child("Id")->add_child_text(id);
462 cv->add_child("LabelText")->add_child_text(label_text);
466 Luminance::Luminance (cxml::ConstNodePtr node)
467 : _value(raw_convert<float>(node->content()))
468 , _unit(string_to_unit(node->string_attribute("units")))
474 Luminance::Luminance (float value, Unit unit)
482 Luminance::set_value (float v)
485 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
493 Luminance::as_xml (xmlpp::Element* parent, string ns) const
495 auto lum = parent->add_child("Luminance", ns);
496 lum->set_attribute("units", unit_to_string(_unit));
497 lum->add_child_text(raw_convert<string>(_value, 3));
502 Luminance::unit_to_string (Unit u)
505 case Unit::CANDELA_PER_SQUARE_METRE:
506 return "candela-per-square-metre";
507 case Unit::FOOT_LAMBERT:
508 return "foot-lambert";
518 Luminance::string_to_unit (string u)
520 if (u == "candela-per-square-metre") {
521 return Unit::CANDELA_PER_SQUARE_METRE;
522 } else if (u == "foot-lambert") {
523 return Unit::FOOT_LAMBERT;
526 throw XMLError (String::compose("Invalid luminance unit %1", u));
531 Luminance::value_in_foot_lamberts () const
534 case Unit::CANDELA_PER_SQUARE_METRE:
535 return _value / 3.426;
536 case Unit::FOOT_LAMBERT:
545 dcp::operator== (Luminance const& a, Luminance const& b)
547 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
551 MainSoundConfiguration::MainSoundConfiguration (string s)
553 vector<string> parts;
554 boost::split (parts, s, boost::is_any_of("/"));
555 if (parts.size() != 2) {
556 throw MainSoundConfigurationError (s);
559 if (parts[0] == "51") {
560 _field = MCASoundField::FIVE_POINT_ONE;
561 } else if (parts[0] == "71") {
562 _field = MCASoundField::SEVEN_POINT_ONE;
564 throw MainSoundConfigurationError (s);
567 vector<string> channels;
568 boost::split (channels, parts[1], boost::is_any_of(","));
570 if (channels.size() > 16) {
571 throw MainSoundConfigurationError (s);
574 for (auto i: channels) {
576 _channels.push_back(optional<Channel>());
578 _channels.push_back(mca_id_to_channel(i));
584 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
587 _channels.resize (channels);
592 MainSoundConfiguration::to_string () const
595 if (_field == MCASoundField::FIVE_POINT_ONE) {
601 for (auto i: _channels) {
605 c += channel_to_mca_id(*i, _field) + ",";
609 if (c.length() > 0) {
610 c = c.substr(0, c.length() - 1);
618 MainSoundConfiguration::mapping (int index) const
620 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
621 return _channels[index];
626 MainSoundConfiguration::set_mapping (int index, Channel c)
628 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
629 _channels[index] = c;
634 dcp::status_to_string (Status s)
650 dcp::string_to_status (string s)
653 return Status::FINAL;
654 } else if (s == "temp") {
656 } else if (s == "pre") {
665 dcp::mca_id_to_channel (string id)
668 return Channel::LEFT;
669 } else if (id == "R") {
670 return Channel::RIGHT;
671 } else if (id == "C") {
672 return Channel::CENTRE;
673 } else if (id == "LFE") {
675 } else if (id == "Ls" || id == "Lss") {
677 } else if (id == "Rs" || id == "Rss") {
679 } else if (id == "HI") {
681 } else if (id == "VIN") {
683 } else if (id == "Lrs") {
685 } else if (id == "Rrs") {
687 } else if (id == "DBOX") {
688 return Channel::MOTION_DATA;
689 } else if (id == "FSKSync") {
690 return Channel::SYNC_SIGNAL;
691 } else if (id == "SLVS") {
692 return Channel::SIGN_LANGUAGE;
695 throw UnknownChannelIdError (id);
700 dcp::channel_to_mca_id (Channel c, MCASoundField field)
707 case Channel::CENTRE:
712 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
714 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
723 case Channel::MOTION_DATA:
725 case Channel::SYNC_SIGNAL:
727 case Channel::SIGN_LANGUAGE:
738 dcp::channel_to_mca_name (Channel c, MCASoundField field)
745 case Channel::CENTRE:
750 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
752 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
754 return "Hearing Impaired";
756 return "Visually Impaired-Narrative";
758 return "Left Rear Surround";
760 return "Right Rear Surround";
761 case Channel::MOTION_DATA:
762 return "D-BOX Motion Code Primary Stream";
763 case Channel::SYNC_SIGNAL:
765 case Channel::SIGN_LANGUAGE:
766 return "Sign Language Video Stream";
776 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
778 static byte_t sync_signal[] = {
779 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
782 static byte_t sign_language[] = {
783 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
788 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
790 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
791 case Channel::CENTRE:
792 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
794 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
796 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
798 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
800 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
802 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
804 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
806 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
807 case Channel::MOTION_DATA:
808 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
809 case Channel::SYNC_SIGNAL:
810 return ASDCP::UL(sync_signal);
811 case Channel::SIGN_LANGUAGE:
812 return ASDCP::UL(sign_language);
822 dcp::used_audio_channels ()
835 Channel::MOTION_DATA,
836 Channel::SYNC_SIGNAL,
837 Channel::SIGN_LANGUAGE
843 dcp::formulation_to_string (dcp::Formulation formulation)
845 switch (formulation) {
846 case Formulation::MODIFIED_TRANSITIONAL_1:
847 return "modified-transitional-1";
848 case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
849 return "multiple-modified-transitional-1";
850 case Formulation::DCI_ANY:
852 case Formulation::DCI_SPECIFIC:
853 return "dci-specific";
861 dcp::string_to_formulation (string formulation)
863 if (formulation == "modified-transitional-1") {
864 return Formulation::MODIFIED_TRANSITIONAL_1;
865 } else if (formulation == "multiple-modified-transitional-1") {
866 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
867 } else if (formulation == "dci-any") {
868 return Formulation::DCI_ANY;
869 } else if (formulation == "dci-specific") {
870 return Formulation::DCI_SPECIFIC;