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 "raw_convert.h"
42 #include "exceptions.h"
43 #include "compose.hpp"
44 #include "dcp_assert.h"
45 #include <libxml++/libxml++.h>
46 #include <boost/algorithm/string.hpp>
58 using namespace boost;
61 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
63 return (a.width == b.width && a.height == b.height);
67 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
73 /** Construct a Fraction from a string of the form <numerator> <denominator>
76 Fraction::Fraction (string s)
79 split (b, s, is_any_of (" "));
81 boost::throw_exception (XMLError("malformed fraction " + s + " in XML node"));
83 numerator = raw_convert<int> (b[0]);
84 denominator = raw_convert<int> (b[1]);
89 Fraction::as_string () const
91 return String::compose ("%1 %2", numerator, denominator);
96 dcp::operator== (Fraction const & a, Fraction const & b)
98 return (a.numerator == b.numerator && a.denominator == b.denominator);
103 dcp::operator!= (Fraction const & a, Fraction const & b)
105 return (a.numerator != b.numerator || a.denominator != b.denominator);
115 Colour::Colour (int r_, int g_, int b_)
124 Colour::Colour (string argb_hex)
127 if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
128 boost::throw_exception (XMLError ("could not parse colour string"));
134 Colour::to_argb_string () const
137 snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
143 Colour::to_rgb_string () const
146 snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
152 dcp::operator== (Colour const & a, Colour const & b)
154 return (a.r == b.r && a.g == b.g && a.b == b.b);
159 dcp::operator!= (Colour const & a, Colour const & b)
166 dcp::effect_to_string (Effect e)
177 boost::throw_exception (MiscError("unknown effect type"));
182 dcp::string_to_effect (string s)
186 } else if (s == "border") {
187 return Effect::BORDER;
188 } else if (s == "shadow") {
189 return Effect::SHADOW;
192 boost::throw_exception (ReadError("unknown subtitle effect type"));
197 dcp::halign_to_string (HAlign h)
208 boost::throw_exception (MiscError("unknown subtitle halign type"));
213 dcp::string_to_halign (string s)
217 } else if (s == "center") {
218 return HAlign::CENTER;
219 } else if (s == "right") {
220 return HAlign::RIGHT;
223 boost::throw_exception (ReadError("unknown subtitle halign type"));
228 dcp::valign_to_string (VAlign v)
239 boost::throw_exception (MiscError("unknown subtitle valign type"));
244 dcp::string_to_valign (string s)
248 } else if (s == "center") {
249 return VAlign::CENTER;
250 } else if (s == "bottom") {
251 return VAlign::BOTTOM;
254 boost::throw_exception (ReadError("unknown subtitle valign type"));
259 dcp::direction_to_string (Direction v)
272 boost::throw_exception (MiscError("unknown subtitle direction type"));
277 dcp::string_to_direction (string s)
279 if (s == "ltr" || s == "horizontal") {
280 return Direction::LTR;
281 } else if (s == "rtl") {
282 return Direction::RTL;
283 } else if (s == "ttb" || s == "vertical") {
284 return Direction::TTB;
285 } else if (s == "btt") {
286 return Direction::BTT;
289 boost::throw_exception (ReadError("unknown subtitle direction type"));
293 /** Convert a content kind to a string which can be used in a
294 * <ContentKind> node
295 * @param kind ContentKind
299 dcp::content_kind_to_string (ContentKind kind)
302 case ContentKind::FEATURE:
304 case ContentKind::SHORT:
306 case ContentKind::TRAILER:
308 case ContentKind::TEST:
310 case ContentKind::TRANSITIONAL:
311 return "transitional";
312 case ContentKind::RATING:
314 case ContentKind::TEASER:
316 case ContentKind::POLICY:
318 case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
320 case ContentKind::ADVERTISEMENT:
321 return "advertisement";
322 case ContentKind::EPISODE:
324 case ContentKind::PROMO:
332 /** Convert a string from a <ContentKind> node to a libdcp ContentKind.
333 * Reasonably tolerant about varying case
334 * @param kind Content kind string
335 * @return libdcp ContentKind
338 dcp::content_kind_from_string (string kind)
340 transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
342 if (kind == "feature") {
343 return ContentKind::FEATURE;
344 } else if (kind == "short") {
345 return ContentKind::SHORT;
346 } else if (kind == "trailer") {
347 return ContentKind::TRAILER;
348 } else if (kind == "test") {
349 return ContentKind::TEST;
350 } else if (kind == "transitional") {
351 return ContentKind::TRANSITIONAL;
352 } else if (kind == "rating") {
353 return ContentKind::RATING;
354 } else if (kind == "teaser") {
355 return ContentKind::TEASER;
356 } else if (kind == "policy") {
357 return ContentKind::POLICY;
358 } else if (kind == "psa") {
359 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
360 } else if (kind == "advertisement") {
361 return ContentKind::ADVERTISEMENT;
362 } else if (kind == "episode") {
363 return ContentKind::EPISODE;
364 } else if (kind == "promo") {
365 return ContentKind::PROMO;
368 throw BadContentKindError (kind);
373 dcp::marker_to_string (dcp::Marker m)
403 dcp::marker_from_string (string s)
407 } else if (s == "LFOC") {
409 } else if (s == "FFTC") {
411 } else if (s == "LFTC") {
413 } else if (s == "FFOI") {
415 } else if (s == "LFOI") {
417 } else if (s == "FFEC") {
419 } else if (s == "LFEC") {
421 } else if (s == "FFMC") {
423 } else if (s == "LFMC") {
431 Rating::Rating (cxml::ConstNodePtr node)
432 : agency(node->string_child("Agency"))
433 , label(node->string_child("Label"))
440 Rating::as_xml (xmlpp::Element* parent) const
442 parent->add_child("Agency")->add_child_text(agency);
443 parent->add_child("Label")->add_child_text(label);
448 dcp::operator== (Rating const & a, Rating const & b)
450 return a.agency == b.agency && a.label == b.label;
454 ContentVersion::ContentVersion ()
455 : id ("urn:uuid:" + make_uuid())
461 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
462 : id(node->string_child("Id"))
463 , label_text(node->string_child("LabelText"))
469 ContentVersion::ContentVersion (string label_text_)
470 : id ("urn:uuid:" + make_uuid())
471 , label_text (label_text_)
478 ContentVersion::as_xml (xmlpp::Element* parent) const
480 auto cv = parent->add_child("ContentVersion");
481 cv->add_child("Id")->add_child_text(id);
482 cv->add_child("LabelText")->add_child_text(label_text);
486 Luminance::Luminance (cxml::ConstNodePtr node)
487 : _unit(string_to_unit(node->string_attribute("units")))
488 , _value(raw_convert<float>(node->content()))
494 Luminance::Luminance (float value, Unit unit)
502 Luminance::set_value (float v)
505 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
513 Luminance::as_xml (xmlpp::Element* parent, string ns) const
515 auto lum = parent->add_child("Luminance", ns);
516 lum->set_attribute("units", unit_to_string(_unit));
517 lum->add_child_text(raw_convert<string>(_value, 3));
522 Luminance::unit_to_string (Unit u)
525 case Unit::CANDELA_PER_SQUARE_METRE:
526 return "candela-per-square-metre";
527 case Unit::FOOT_LAMBERT:
528 return "foot-lambert";
538 Luminance::string_to_unit (string u)
540 if (u == "candela-per-square-metre") {
541 return Unit::CANDELA_PER_SQUARE_METRE;
542 } else if (u == "foot-lambert") {
543 return Unit::FOOT_LAMBERT;
546 throw XMLError (String::compose("Invalid luminance unit %1", u));
551 dcp::operator== (Luminance const& a, Luminance const& b)
553 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
557 MainSoundConfiguration::MainSoundConfiguration (string s)
559 vector<string> parts;
560 boost::split (parts, s, boost::is_any_of("/"));
561 if (parts.size() != 2) {
562 throw MainSoundConfigurationError (s);
565 if (parts[0] == "51") {
566 _field = MCASoundField::FIVE_POINT_ONE;
567 } else if (parts[0] == "71") {
568 _field = MCASoundField::SEVEN_POINT_ONE;
570 throw MainSoundConfigurationError (s);
573 vector<string> channels;
574 boost::split (channels, parts[1], boost::is_any_of(","));
576 if (channels.size() > 16) {
577 throw MainSoundConfigurationError (s);
580 for (auto i: channels) {
582 _channels.push_back(optional<Channel>());
584 _channels.push_back(mca_id_to_channel(i));
590 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
593 _channels.resize (channels);
598 MainSoundConfiguration::to_string () const
601 if (_field == MCASoundField::FIVE_POINT_ONE) {
607 for (auto i: _channels) {
611 c += channel_to_mca_id(*i, _field) + ",";
615 if (c.length() > 0) {
616 c = c.substr(0, c.length() - 1);
624 MainSoundConfiguration::mapping (int index) const
626 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
627 return _channels[index];
632 MainSoundConfiguration::set_mapping (int index, Channel c)
634 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
635 _channels[index] = c;
640 dcp::status_to_string (Status s)
656 dcp::string_to_status (string s)
659 return Status::FINAL;
660 } else if (s == "temp") {
662 } else if (s == "pre") {
671 dcp::mca_id_to_channel (string id)
674 return Channel::LEFT;
675 } else if (id == "R") {
676 return Channel::RIGHT;
677 } else if (id == "C") {
678 return Channel::CENTRE;
679 } else if (id == "LFE") {
681 } else if (id == "Ls" || id == "Lss") {
683 } else if (id == "Rs" || id == "Rss") {
685 } else if (id == "HI") {
687 } else if (id == "VIN") {
689 } else if (id == "Lrs") {
691 } else if (id == "Rrs") {
693 } else if (id == "DBOX") {
694 return Channel::MOTION_DATA;
695 } else if (id == "FSKSync") {
696 return Channel::SYNC_SIGNAL;
697 } else if (id == "SLVS") {
698 return Channel::SIGN_LANGUAGE;
701 throw UnknownChannelIdError (id);
706 dcp::channel_to_mca_id (Channel c, MCASoundField field)
713 case Channel::CENTRE:
718 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
720 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
729 case Channel::MOTION_DATA:
731 case Channel::SYNC_SIGNAL:
733 case Channel::SIGN_LANGUAGE:
744 dcp::channel_to_mca_name (Channel c, MCASoundField field)
751 case Channel::CENTRE:
756 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
758 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
760 return "Hearing Impaired";
762 return "Visually Impaired-Narrative";
764 return "Left Rear Surround";
766 return "Right Rear Surround";
767 case Channel::MOTION_DATA:
768 return "D-BOX Motion Code Primary Stream";
769 case Channel::SYNC_SIGNAL:
771 case Channel::SIGN_LANGUAGE:
772 return "Sign Language Video Stream";
782 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
784 static byte_t sync_signal[] = {
785 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
788 static byte_t sign_language[] = {
789 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
794 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
796 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
797 case Channel::CENTRE:
798 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
800 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
802 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
804 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
806 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
808 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
810 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
812 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
813 case Channel::MOTION_DATA:
814 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
815 case Channel::SYNC_SIGNAL:
816 return ASDCP::UL(sync_signal);
817 case Channel::SIGN_LANGUAGE:
818 return ASDCP::UL(sign_language);
828 dcp::used_audio_channels ()
831 c.push_back (Channel::LEFT);
832 c.push_back (Channel::RIGHT);
833 c.push_back (Channel::CENTRE);
834 c.push_back (Channel::LFE);
835 c.push_back (Channel::LS);
836 c.push_back (Channel::RS);
837 c.push_back (Channel::HI);
838 c.push_back (Channel::VI);
839 c.push_back (Channel::BSL);
840 c.push_back (Channel::BSR);
841 c.push_back (Channel::MOTION_DATA);
842 c.push_back (Channel::SYNC_SIGNAL);
843 c.push_back (Channel::SIGN_LANGUAGE);