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 : _value(raw_convert<float>(node->content()))
488 , _unit(string_to_unit(node->string_attribute("units")))
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 Luminance::value_in_foot_lamberts () const
554 case Unit::CANDELA_PER_SQUARE_METRE:
555 return _value / 3.426;
556 case Unit::FOOT_LAMBERT:
565 dcp::operator== (Luminance const& a, Luminance const& b)
567 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
571 MainSoundConfiguration::MainSoundConfiguration (string s)
573 vector<string> parts;
574 boost::split (parts, s, boost::is_any_of("/"));
575 if (parts.size() != 2) {
576 throw MainSoundConfigurationError (s);
579 if (parts[0] == "51") {
580 _field = MCASoundField::FIVE_POINT_ONE;
581 } else if (parts[0] == "71") {
582 _field = MCASoundField::SEVEN_POINT_ONE;
584 throw MainSoundConfigurationError (s);
587 vector<string> channels;
588 boost::split (channels, parts[1], boost::is_any_of(","));
590 if (channels.size() > 16) {
591 throw MainSoundConfigurationError (s);
594 for (auto i: channels) {
596 _channels.push_back(optional<Channel>());
598 _channels.push_back(mca_id_to_channel(i));
604 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
607 _channels.resize (channels);
612 MainSoundConfiguration::to_string () const
615 if (_field == MCASoundField::FIVE_POINT_ONE) {
621 for (auto i: _channels) {
625 c += channel_to_mca_id(*i, _field) + ",";
629 if (c.length() > 0) {
630 c = c.substr(0, c.length() - 1);
638 MainSoundConfiguration::mapping (int index) const
640 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
641 return _channels[index];
646 MainSoundConfiguration::set_mapping (int index, Channel c)
648 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
649 _channels[index] = c;
654 dcp::status_to_string (Status s)
670 dcp::string_to_status (string s)
673 return Status::FINAL;
674 } else if (s == "temp") {
676 } else if (s == "pre") {
685 dcp::mca_id_to_channel (string id)
688 return Channel::LEFT;
689 } else if (id == "R") {
690 return Channel::RIGHT;
691 } else if (id == "C") {
692 return Channel::CENTRE;
693 } else if (id == "LFE") {
695 } else if (id == "Ls" || id == "Lss") {
697 } else if (id == "Rs" || id == "Rss") {
699 } else if (id == "HI") {
701 } else if (id == "VIN") {
703 } else if (id == "Lrs") {
705 } else if (id == "Rrs") {
707 } else if (id == "DBOX") {
708 return Channel::MOTION_DATA;
709 } else if (id == "FSKSync") {
710 return Channel::SYNC_SIGNAL;
711 } else if (id == "SLVS") {
712 return Channel::SIGN_LANGUAGE;
715 throw UnknownChannelIdError (id);
720 dcp::channel_to_mca_id (Channel c, MCASoundField field)
727 case Channel::CENTRE:
732 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
734 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
743 case Channel::MOTION_DATA:
745 case Channel::SYNC_SIGNAL:
747 case Channel::SIGN_LANGUAGE:
758 dcp::channel_to_mca_name (Channel c, MCASoundField field)
765 case Channel::CENTRE:
770 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
772 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
774 return "Hearing Impaired";
776 return "Visually Impaired-Narrative";
778 return "Left Rear Surround";
780 return "Right Rear Surround";
781 case Channel::MOTION_DATA:
782 return "D-BOX Motion Code Primary Stream";
783 case Channel::SYNC_SIGNAL:
785 case Channel::SIGN_LANGUAGE:
786 return "Sign Language Video Stream";
796 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
798 static byte_t sync_signal[] = {
799 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
802 static byte_t sign_language[] = {
803 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
808 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
810 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
811 case Channel::CENTRE:
812 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
814 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
816 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
818 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
820 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
822 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
824 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
826 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
827 case Channel::MOTION_DATA:
828 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
829 case Channel::SYNC_SIGNAL:
830 return ASDCP::UL(sync_signal);
831 case Channel::SIGN_LANGUAGE:
832 return ASDCP::UL(sign_language);
842 dcp::used_audio_channels ()
855 Channel::MOTION_DATA,
856 Channel::SYNC_SIGNAL,
857 Channel::SIGN_LANGUAGE