2 Copyright (C) 2012-2019 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.
34 #include "raw_convert.h"
36 #include "exceptions.h"
37 #include "compose.hpp"
38 #include "dcp_assert.h"
39 #include <libxml++/libxml++.h>
40 #include <boost/algorithm/string.hpp>
41 #include <boost/foreach.hpp>
52 using namespace boost;
54 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
56 return (a.width == b.width && a.height == b.height);
59 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
64 ostream& dcp::operator<< (ostream& s, dcp::Size const & a)
66 s << a.width << "x" << a.height;
70 /** Construct a Fraction from a string of the form <numerator> <denominator>
73 Fraction::Fraction (string s)
76 split (b, s, is_any_of (" "));
78 boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
80 numerator = raw_convert<int> (b[0]);
81 denominator = raw_convert<int> (b[1]);
85 Fraction::as_string () const
87 return String::compose ("%1 %2", numerator, denominator);
91 dcp::operator== (Fraction const & a, Fraction const & b)
93 return (a.numerator == b.numerator && a.denominator == b.denominator);
97 dcp::operator!= (Fraction const & a, Fraction const & b)
99 return (a.numerator != b.numerator || a.denominator != b.denominator);
103 dcp::operator<< (ostream& s, Fraction const & f)
105 s << f.numerator << "/" << f.denominator;
109 /** Construct a Colour, initialising it to black. */
118 /** Construct a Colour from R, G and B. The values run between
121 Colour::Colour (int r_, int g_, int b_)
129 /** Construct a Colour from an ARGB hex string; the alpha value is ignored.
130 * @param argb_hex A string of the form AARRGGBB, where e.g. RR is a two-character
133 Colour::Colour (string argb_hex)
136 if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
137 boost::throw_exception (XMLError ("could not parse colour string"));
141 /** @return An ARGB string of the form AARRGGBB, where e.g. RR is a two-character
142 * hex value. The alpha value will always be FF (ie 255; maximum alpha).
145 Colour::to_argb_string () const
148 snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
152 /** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
156 Colour::to_rgb_string () const
159 snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
163 /** operator== for Colours.
164 * @param a First colour to compare.
165 * @param b Second colour to compare.
168 dcp::operator== (Colour const & a, Colour const & b)
170 return (a.r == b.r && a.g == b.g && a.b == b.b);
173 /** operator!= for Colours.
174 * @param a First colour to compare.
175 * @param b Second colour to compare.
178 dcp::operator!= (Colour const & a, Colour const & b)
184 dcp::operator<< (ostream& s, Colour const & c)
186 s << "(" << c.r << ", " << c.g << ", " << c.b << ")";
191 dcp::effect_to_string (Effect e)
202 boost::throw_exception (MiscError ("unknown effect type"));
206 dcp::string_to_effect (string s)
210 } else if (s == "border") {
212 } else if (s == "shadow") {
216 boost::throw_exception (ReadError ("unknown subtitle effect type"));
220 dcp::halign_to_string (HAlign h)
231 boost::throw_exception (MiscError ("unknown subtitle halign type"));
235 dcp::string_to_halign (string s)
239 } else if (s == "center") {
240 return HALIGN_CENTER;
241 } else if (s == "right") {
245 boost::throw_exception (ReadError ("unknown subtitle halign type"));
249 dcp::valign_to_string (VAlign v)
260 boost::throw_exception (MiscError ("unknown subtitle valign type"));
264 dcp::string_to_valign (string s)
268 } else if (s == "center") {
269 return VALIGN_CENTER;
270 } else if (s == "bottom") {
271 return VALIGN_BOTTOM;
274 boost::throw_exception (ReadError ("unknown subtitle valign type"));
278 dcp::direction_to_string (Direction v)
291 boost::throw_exception (MiscError ("unknown subtitle direction type"));
295 dcp::string_to_direction (string s)
297 if (s == "ltr" || s == "horizontal") {
298 return DIRECTION_LTR;
299 } else if (s == "rtl") {
300 return DIRECTION_RTL;
301 } else if (s == "ttb" || s == "vertical") {
302 return DIRECTION_TTB;
303 } else if (s == "btt") {
304 return DIRECTION_BTT;
307 boost::throw_exception (ReadError ("unknown subtitle direction type"));
310 /** Convert a content kind to a string which can be used in a
311 * <ContentKind> node.
312 * @param kind ContentKind.
316 dcp::content_kind_to_string (ContentKind kind)
328 return "transitional";
335 case PUBLIC_SERVICE_ANNOUNCEMENT:
338 return "advertisement";
348 /** Convert a string from a <ContentKind> node to a libdcp ContentKind.
349 * Reasonably tolerant about varying case.
350 * @param kind Content kind string.
351 * @return libdcp ContentKind.
354 dcp::content_kind_from_string (string kind)
356 transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
358 if (kind == "feature") {
360 } else if (kind == "short") {
362 } else if (kind == "trailer") {
364 } else if (kind == "test") {
366 } else if (kind == "transitional") {
368 } else if (kind == "rating") {
370 } else if (kind == "teaser") {
372 } else if (kind == "policy") {
374 } else if (kind == "psa") {
375 return PUBLIC_SERVICE_ANNOUNCEMENT;
376 } else if (kind == "advertisement") {
377 return ADVERTISEMENT;
378 } else if (kind == "episode") {
380 } else if (kind == "promo") {
384 throw BadContentKindError (kind);
388 dcp::marker_to_string (dcp::Marker m)
417 dcp::marker_from_string (string s)
421 } else if (s == "LFOC") {
423 } else if (s == "FFTC") {
425 } else if (s == "LFTC") {
427 } else if (s == "FFOI") {
429 } else if (s == "LFOI") {
431 } else if (s == "FFEC") {
433 } else if (s == "LFEC") {
435 } else if (s == "FFMC") {
437 } else if (s == "LFMC") {
444 Rating::Rating (cxml::ConstNodePtr node)
446 agency = node->string_child("Agency");
447 label = node->string_child("Label");
452 Rating::as_xml (xmlpp::Element* parent) const
454 parent->add_child("Agency")->add_child_text(agency);
455 parent->add_child("Label")->add_child_text(label);
459 dcp::operator== (Rating const & a, Rating const & b)
461 return a.agency == b.agency && a.label == b.label;
465 dcp::operator<< (ostream& s, Rating const & r)
467 s << r.agency << " " << r.label;
472 ContentVersion::ContentVersion ()
473 : id ("urn:uuid:" + make_uuid())
479 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
481 id = node->string_child("Id");
482 label_text = node->string_child("LabelText");
486 ContentVersion::ContentVersion (string label_text_)
487 : id ("urn:uuid:" + make_uuid())
488 , label_text (label_text_)
495 ContentVersion::as_xml (xmlpp::Element* parent) const
497 xmlpp::Node* cv = parent->add_child("ContentVersion");
498 cv->add_child("Id")->add_child_text(id);
499 cv->add_child("LabelText")->add_child_text(label_text);
503 Luminance::Luminance (cxml::ConstNodePtr node)
505 _unit = string_to_unit (node->string_attribute("units"));
506 _value = raw_convert<float> (node->content());
510 Luminance::Luminance (float value, Unit unit)
518 Luminance::set_value (float v)
521 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
529 Luminance::as_xml (xmlpp::Element* parent, string ns) const
531 xmlpp::Element* lum = parent->add_child("Luminance", ns);
532 lum->set_attribute("units", unit_to_string(_unit));
533 lum->add_child_text(raw_convert<string>(_value, 3));
538 Luminance::unit_to_string (Unit u)
541 case CANDELA_PER_SQUARE_METRE:
542 return "candela-per-square-metre";
544 return "foot-lambert";
554 Luminance::string_to_unit (string u)
556 if (u == "candela-per-square-metre") {
557 return Unit::CANDELA_PER_SQUARE_METRE;
558 } else if (u == "foot-lambert") {
559 return Unit::FOOT_LAMBERT;
562 throw XMLError (String::compose("Invalid luminance unit %1", u));
567 dcp::operator== (Luminance const& a, Luminance const& b)
569 return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
573 MainSoundConfiguration::MainSoundConfiguration (string s)
575 vector<string> parts;
576 boost::split (parts, s, boost::is_any_of("/"));
577 if (parts.size() != 2) {
578 throw MainSoundConfigurationError (s);
581 if (parts[0] == "51") {
582 _field = FIVE_POINT_ONE;
583 } else if (parts[0] == "71") {
584 _field = SEVEN_POINT_ONE;
586 throw MainSoundConfigurationError (s);
589 vector<string> channels;
590 boost::split (channels, parts[1], boost::is_any_of(","));
592 if (channels.size() > 16) {
593 throw MainSoundConfigurationError (s);
596 BOOST_FOREACH (string i, channels) {
598 _channels.push_back(optional<Channel>());
600 _channels.push_back(mca_id_to_channel(i));
606 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
609 _channels.resize (channels);
614 MainSoundConfiguration::to_string () const
617 if (_field == FIVE_POINT_ONE) {
623 BOOST_FOREACH (optional<Channel> i, _channels) {
627 c += channel_to_mca_id(*i, _field) + ",";
631 if (c.length() > 0) {
632 c = c.substr(0, c.length() - 1);
640 MainSoundConfiguration::mapping (int index) const
642 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
643 return _channels[index];
648 MainSoundConfiguration::set_mapping (int index, Channel c)
650 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
651 _channels[index] = c;
656 dcp::status_to_string (Status s)
673 dcp::string_to_status (string s)
677 } else if (s == "temp") {
679 } else if (s == "pre") {
688 dcp::mca_id_to_channel (string id)
692 } else if (id == "R") {
694 } else if (id == "C") {
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") {
712 } else if (id == "FSKSync") {
714 } else if (id == "SLVS") {
715 return SIGN_LANGUAGE;
718 throw UnknownChannelIdError (id);
723 dcp::channel_to_mca_id (Channel c, MCASoundField field)
735 return field == FIVE_POINT_ONE ? "Ls" : "Lss";
737 return field == FIVE_POINT_ONE ? "Rs" : "Rss";
761 dcp::channel_to_mca_name (Channel c, MCASoundField field)
773 return field == FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
775 return field == 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";
785 return "D-BOX Motion Code Primary Stream";
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);
815 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
817 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
819 return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
821 return dict->ul(field == 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);
831 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
833 return ASDCP::UL(sync_signal);
835 return ASDCP::UL(sign_language);
845 dcp::used_audio_channels ()
847 vector<dcp::Channel> c;
850 c.push_back (CENTRE);
858 c.push_back (MOTION_DATA);
859 c.push_back (SYNC_SIGNAL);
860 c.push_back (SIGN_LANGUAGE);