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>());
599 } else if (i == "L") {
600 _channels.push_back(LEFT);
601 } else if (i == "R") {
602 _channels.push_back(RIGHT);
603 } else if (i == "C") {
604 _channels.push_back(CENTRE);
605 } else if (i == "LFE") {
606 _channels.push_back(LFE);
607 } else if (i == "Ls" || i == "Lss") {
608 _channels.push_back(LS);
609 } else if (i == "Rs" || i == "Rss") {
610 _channels.push_back(RS);
611 } else if (i == "HI") {
612 _channels.push_back(HI);
613 } else if (i == "VIN") {
614 _channels.push_back(VI);
615 } else if (i == "Lrs") {
616 _channels.push_back(BSL);
617 } else if (i == "Rrs") {
618 _channels.push_back(BSR);
619 } else if (i == "DBOX") {
620 _channels.push_back(MOTION_DATA);
621 } else if (i == "Sync") {
622 _channels.push_back(SYNC_SIGNAL);
623 } else if (i == "Sign") {
624 _channels.push_back(SIGN_LANGUAGE);
626 throw MainSoundConfigurationError (s);
632 MainSoundConfiguration::MainSoundConfiguration (Field field, int channels)
635 _channels.resize (channels);
640 MainSoundConfiguration::to_string () const
643 if (_field == FIVE_POINT_ONE) {
649 BOOST_FOREACH (optional<Channel> i, _channels) {
667 c += (_field == FIVE_POINT_ONE ? "Ls," : "Lss,");
670 c += (_field == FIVE_POINT_ONE ? "Rs," : "Rss,");
695 /* XXX: not sure what this should be */
705 if (c.length() > 0) {
706 c = c.substr(0, c.length() - 1);
714 MainSoundConfiguration::mapping (int index) const
716 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
717 return _channels[index];
722 MainSoundConfiguration::set_mapping (int index, Channel c)
724 DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
725 _channels[index] = c;
730 dcp::status_to_string (Status s)
747 dcp::string_to_status (string s)
751 } else if (s == "temp") {
753 } else if (s == "pre") {