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 Rating::Rating (cxml::ConstNodePtr node)
435 : agency(node->string_child("Agency"))
436 , label(node->string_child("Label"))
443 Rating::as_xml (xmlpp::Element* parent) const
445 parent->add_child("Agency")->add_child_text(agency);
446 parent->add_child("Label")->add_child_text(label);
451 dcp::operator== (Rating const & a, Rating const & b)
453 return a.agency == b.agency && a.label == b.label;
457 ContentVersion::ContentVersion ()
458 : id ("urn:uuid:" + make_uuid())
464 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
465 : id(node->string_child("Id"))
466 , label_text(node->string_child("LabelText"))
472 ContentVersion::ContentVersion (string label_text_)
473 : id ("urn:uuid:" + make_uuid())
474 , label_text (label_text_)
481 ContentVersion::as_xml (xmlpp::Element* parent) const
483 auto cv = parent->add_child("ContentVersion");
484 cv->add_child("Id")->add_child_text(id);
485 cv->add_child("LabelText")->add_child_text(label_text);
489 Luminance::Luminance (cxml::ConstNodePtr node)
490 : _value(raw_convert<float>(node->content()))
491 , _unit(string_to_unit(node->string_attribute("units")))
497 Luminance::Luminance (float value, Unit unit)
505 Luminance::set_value (float v)
508 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
516 Luminance::as_xml (xmlpp::Element* parent, string ns) const
518 auto lum = parent->add_child("Luminance", ns);
519 lum->set_attribute("units", unit_to_string(_unit));
520 lum->add_child_text(raw_convert<string>(_value, 3));
525 Luminance::unit_to_string (Unit u)
528 case Unit::CANDELA_PER_SQUARE_METRE:
529 return "candela-per-square-metre";
530 case Unit::FOOT_LAMBERT:
531 return "foot-lambert";
541 Luminance::string_to_unit (string u)
543 if (u == "candela-per-square-metre") {
544 return Unit::CANDELA_PER_SQUARE_METRE;
545 } else if (u == "foot-lambert") {
546 return Unit::FOOT_LAMBERT;
549 throw XMLError (String::compose("Invalid luminance unit %1", u));
554 Luminance::value_in_foot_lamberts () const
557 case Unit::CANDELA_PER_SQUARE_METRE:
558 return _value / 3.426;
559 case Unit::FOOT_LAMBERT:
568 dcp::operator== (Luminance const& a, Luminance const& b)
570 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
574 MainSoundConfiguration::MainSoundConfiguration (string s)
576 vector<string> parts;
577 boost::split (parts, s, boost::is_any_of("/"));
578 if (parts.size() != 2) {
579 throw MainSoundConfigurationError (s);
582 if (parts[0] == "51") {
583 _field = MCASoundField::FIVE_POINT_ONE;
584 } else if (parts[0] == "71") {
585 _field = MCASoundField::SEVEN_POINT_ONE;
587 throw MainSoundConfigurationError (s);
590 vector<string> channels;
591 boost::split (channels, parts[1], boost::is_any_of(","));
593 if (channels.size() > 16) {
594 throw MainSoundConfigurationError (s);
597 for (auto i: channels) {
599 _channels.push_back(optional<Channel>());
601 _channels.push_back(mca_id_to_channel(i));
607 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
610 _channels.resize (channels);
615 MainSoundConfiguration::to_string () const
618 if (_field == MCASoundField::FIVE_POINT_ONE) {
624 for (auto i: _channels) {
628 c += channel_to_mca_id(*i, _field) + ",";
632 if (c.length() > 0) {
633 c = c.substr(0, c.length() - 1);
641 MainSoundConfiguration::mapping (int index) const
643 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
644 return _channels[index];
649 MainSoundConfiguration::set_mapping (int index, Channel c)
651 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
652 _channels[index] = c;
657 dcp::status_to_string (Status s)
673 dcp::string_to_status (string s)
676 return Status::FINAL;
677 } else if (s == "temp") {
679 } else if (s == "pre") {
688 dcp::mca_id_to_channel (string id)
691 return Channel::LEFT;
692 } else if (id == "R") {
693 return Channel::RIGHT;
694 } else if (id == "C") {
695 return Channel::CENTRE;
696 } else if (id == "LFE") {
698 } else if (id == "Ls" || id == "Lss") {
700 } else if (id == "Rs" || id == "Rss") {
702 } else if (id == "HI") {
704 } else if (id == "VIN") {
706 } else if (id == "Lrs") {
708 } else if (id == "Rrs") {
710 } else if (id == "DBOX") {
711 return Channel::MOTION_DATA;
712 } else if (id == "FSKSync") {
713 return Channel::SYNC_SIGNAL;
714 } else if (id == "SLVS") {
715 return Channel::SIGN_LANGUAGE;
718 throw UnknownChannelIdError (id);
723 dcp::channel_to_mca_id (Channel c, MCASoundField field)
730 case Channel::CENTRE:
735 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
737 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
746 case Channel::MOTION_DATA:
748 case Channel::SYNC_SIGNAL:
750 case Channel::SIGN_LANGUAGE:
761 dcp::channel_to_mca_name (Channel c, MCASoundField field)
768 case Channel::CENTRE:
773 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
775 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
777 return "Hearing Impaired";
779 return "Visually Impaired-Narrative";
781 return "Left Rear Surround";
783 return "Right Rear Surround";
784 case Channel::MOTION_DATA:
785 return "D-BOX Motion Code Primary Stream";
786 case Channel::SYNC_SIGNAL:
788 case Channel::SIGN_LANGUAGE:
789 return "Sign Language Video Stream";
799 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
801 static byte_t sync_signal[] = {
802 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
805 static byte_t sign_language[] = {
806 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
811 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
813 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
814 case Channel::CENTRE:
815 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
817 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
819 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
821 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
823 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
825 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
827 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
829 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
830 case Channel::MOTION_DATA:
831 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
832 case Channel::SYNC_SIGNAL:
833 return ASDCP::UL(sync_signal);
834 case Channel::SIGN_LANGUAGE:
835 return ASDCP::UL(sign_language);
845 dcp::used_audio_channels ()
858 Channel::MOTION_DATA,
859 Channel::SYNC_SIGNAL,
860 Channel::SIGN_LANGUAGE