Support CPL metadata.
[libdcp.git] / src / types.cc
1 /*
2     Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 #include "raw_convert.h"
35 #include "types.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>
42 #include <string>
43 #include <vector>
44 #include <cmath>
45 #include <cstdio>
46 #include <iomanip>
47
48 using std::string;
49 using std::ostream;
50 using std::vector;
51 using namespace dcp;
52 using namespace boost;
53
54 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
55 {
56         return (a.width == b.width && a.height == b.height);
57 }
58
59 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
60 {
61         return !(a == b);
62 }
63
64 ostream& dcp::operator<< (ostream& s, dcp::Size const & a)
65 {
66         s << a.width << "x" << a.height;
67         return s;
68 }
69
70 /** Construct a Fraction from a string of the form <numerator> <denominator>
71  *  e.g. "1 3".
72  */
73 Fraction::Fraction (string s)
74 {
75         vector<string> b;
76         split (b, s, is_any_of (" "));
77         if (b.size() != 2) {
78                 boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
79         }
80         numerator = raw_convert<int> (b[0]);
81         denominator = raw_convert<int> (b[1]);
82 }
83
84 string
85 Fraction::as_string () const
86 {
87         return String::compose ("%1 %2", numerator, denominator);
88 }
89
90 bool
91 dcp::operator== (Fraction const & a, Fraction const & b)
92 {
93         return (a.numerator == b.numerator && a.denominator == b.denominator);
94 }
95
96 bool
97 dcp::operator!= (Fraction const & a, Fraction const & b)
98 {
99         return (a.numerator != b.numerator || a.denominator != b.denominator);
100 }
101
102 ostream&
103 dcp::operator<< (ostream& s, Fraction const & f)
104 {
105         s << f.numerator << "/" << f.denominator;
106         return s;
107 }
108
109 /** Construct a Colour, initialising it to black. */
110 Colour::Colour ()
111         : r (0)
112         , g (0)
113         , b (0)
114 {
115
116 }
117
118 /** Construct a Colour from R, G and B.  The values run between
119  *  0 and 255.
120  */
121 Colour::Colour (int r_, int g_, int b_)
122         : r (r_)
123         , g (g_)
124         , b (b_)
125 {
126
127 }
128
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
131  *  hex value.
132  */
133 Colour::Colour (string argb_hex)
134 {
135         int alpha;
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"));
138         }
139 }
140
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).
143  */
144 string
145 Colour::to_argb_string () const
146 {
147         char buffer[9];
148         snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
149         return buffer;
150 }
151
152 /** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
153  *  hex value.
154  */
155 string
156 Colour::to_rgb_string () const
157 {
158         char buffer[7];
159         snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
160         return buffer;
161 }
162
163 /** operator== for Colours.
164  *  @param a First colour to compare.
165  *  @param b Second colour to compare.
166  */
167 bool
168 dcp::operator== (Colour const & a, Colour const & b)
169 {
170         return (a.r == b.r && a.g == b.g && a.b == b.b);
171 }
172
173 /** operator!= for Colours.
174  *  @param a First colour to compare.
175  *  @param b Second colour to compare.
176  */
177 bool
178 dcp::operator!= (Colour const & a, Colour const & b)
179 {
180         return !(a == b);
181 }
182
183 ostream &
184 dcp::operator<< (ostream& s, Colour const & c)
185 {
186         s << "(" << c.r << ", " << c.g << ", " << c.b << ")";
187         return s;
188 }
189
190 string
191 dcp::effect_to_string (Effect e)
192 {
193         switch (e) {
194         case NONE:
195                 return "none";
196         case BORDER:
197                 return "border";
198         case SHADOW:
199                 return "shadow";
200         }
201
202         boost::throw_exception (MiscError ("unknown effect type"));
203 }
204
205 Effect
206 dcp::string_to_effect (string s)
207 {
208         if (s == "none") {
209                 return NONE;
210         } else if (s == "border") {
211                 return BORDER;
212         } else if (s == "shadow") {
213                 return SHADOW;
214         }
215
216         boost::throw_exception (ReadError ("unknown subtitle effect type"));
217 }
218
219 string
220 dcp::halign_to_string (HAlign h)
221 {
222         switch (h) {
223         case HALIGN_LEFT:
224                 return "left";
225         case HALIGN_CENTER:
226                 return "center";
227         case HALIGN_RIGHT:
228                 return "right";
229         }
230
231         boost::throw_exception (MiscError ("unknown subtitle halign type"));
232 }
233
234 HAlign
235 dcp::string_to_halign (string s)
236 {
237         if (s == "left") {
238                 return HALIGN_LEFT;
239         } else if (s == "center") {
240                 return HALIGN_CENTER;
241         } else if (s == "right") {
242                 return HALIGN_RIGHT;
243         }
244
245         boost::throw_exception (ReadError ("unknown subtitle halign type"));
246 }
247
248 string
249 dcp::valign_to_string (VAlign v)
250 {
251         switch (v) {
252         case VALIGN_TOP:
253                 return "top";
254         case VALIGN_CENTER:
255                 return "center";
256         case VALIGN_BOTTOM:
257                 return "bottom";
258         }
259
260         boost::throw_exception (MiscError ("unknown subtitle valign type"));
261 }
262
263 VAlign
264 dcp::string_to_valign (string s)
265 {
266         if (s == "top") {
267                 return VALIGN_TOP;
268         } else if (s == "center") {
269                 return VALIGN_CENTER;
270         } else if (s == "bottom") {
271                 return VALIGN_BOTTOM;
272         }
273
274         boost::throw_exception (ReadError ("unknown subtitle valign type"));
275 }
276
277 string
278 dcp::direction_to_string (Direction v)
279 {
280         switch (v) {
281         case DIRECTION_LTR:
282                 return "ltr";
283         case DIRECTION_RTL:
284                 return "rtl";
285         case DIRECTION_TTB:
286                 return "ttb";
287         case DIRECTION_BTT:
288                 return "btt";
289         }
290
291         boost::throw_exception (MiscError ("unknown subtitle direction type"));
292 }
293
294 Direction
295 dcp::string_to_direction (string s)
296 {
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;
305         }
306
307         boost::throw_exception (ReadError ("unknown subtitle direction type"));
308 }
309
310 /** Convert a content kind to a string which can be used in a
311  *  &lt;ContentKind&gt; node.
312  *  @param kind ContentKind.
313  *  @return string.
314  */
315 string
316 dcp::content_kind_to_string (ContentKind kind)
317 {
318         switch (kind) {
319         case FEATURE:
320                 return "feature";
321         case SHORT:
322                 return "short";
323         case TRAILER:
324                 return "trailer";
325         case TEST:
326                 return "test";
327         case TRANSITIONAL:
328                 return "transitional";
329         case RATING:
330                 return "rating";
331         case TEASER:
332                 return "teaser";
333         case POLICY:
334                 return "policy";
335         case PUBLIC_SERVICE_ANNOUNCEMENT:
336                 return "psa";
337         case ADVERTISEMENT:
338                 return "advertisement";
339         case EPISODE:
340                 return "episode";
341         case PROMO:
342                 return "promo";
343         }
344
345         DCP_ASSERT (false);
346 }
347
348 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
349  *  Reasonably tolerant about varying case.
350  *  @param kind Content kind string.
351  *  @return libdcp ContentKind.
352  */
353 dcp::ContentKind
354 dcp::content_kind_from_string (string kind)
355 {
356         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
357
358         if (kind == "feature") {
359                 return FEATURE;
360         } else if (kind == "short") {
361                 return SHORT;
362         } else if (kind == "trailer") {
363                 return TRAILER;
364         } else if (kind == "test") {
365                 return TEST;
366         } else if (kind == "transitional") {
367                 return TRANSITIONAL;
368         } else if (kind == "rating") {
369                 return RATING;
370         } else if (kind == "teaser") {
371                 return TEASER;
372         } else if (kind == "policy") {
373                 return POLICY;
374         } else if (kind == "psa") {
375                 return PUBLIC_SERVICE_ANNOUNCEMENT;
376         } else if (kind == "advertisement") {
377                 return ADVERTISEMENT;
378         } else if (kind == "episode") {
379                 return EPISODE;
380         } else if (kind == "promo") {
381                 return PROMO;
382         }
383
384         throw BadContentKindError (kind);
385 }
386
387 string
388 dcp::marker_to_string (dcp::Marker m)
389 {
390         switch (m) {
391         case FFOC:
392                 return "FFOC";
393         case LFOC:
394                 return "LFOC";
395         case FFTC:
396                 return "FFTC";
397         case LFTC:
398                 return "LFTC";
399         case FFOI:
400                 return "FFOI";
401         case LFOI:
402                 return "LFOI";
403         case FFEC:
404                 return "FFEC";
405         case LFEC:
406                 return "LFEC";
407         case FFMC:
408                 return "FFMC";
409         case LFMC:
410                 return "LFMC";
411         }
412
413         DCP_ASSERT (false);
414 }
415
416 dcp::Marker
417 dcp::marker_from_string (string s)
418 {
419         if (s == "FFOC") {
420                 return FFOC;
421         } else if (s == "LFOC") {
422                 return LFOC;
423         } else if (s == "FFTC") {
424                 return FFTC;
425         } else if (s == "LFTC") {
426                 return LFTC;
427         } else if (s == "FFOI") {
428                 return FFOI;
429         } else if (s == "LFOI") {
430                 return LFOI;
431         } else if (s == "FFEC") {
432                 return FFEC;
433         } else if (s == "LFEC") {
434                 return LFEC;
435         } else if (s == "FFMC") {
436                 return FFMC;
437         } else if (s == "LFMC") {
438                 return LFMC;
439         }
440
441         DCP_ASSERT (false);
442 }
443
444 Rating::Rating (cxml::ConstNodePtr node)
445 {
446         agency = node->string_child("Agency");
447         label = node->string_child("Label");
448         node->done ();
449 }
450
451 void
452 Rating::as_xml (xmlpp::Element* parent) const
453 {
454         parent->add_child("Agency")->add_child_text(agency);
455         parent->add_child("Label")->add_child_text(label);
456 }
457
458 bool
459 dcp::operator== (Rating const & a, Rating const & b)
460 {
461         return a.agency == b.agency && a.label == b.label;
462 }
463
464 ostream &
465 dcp::operator<< (ostream& s, Rating const & r)
466 {
467         s << r.agency << " " << r.label;
468         return s;
469 }
470
471
472 ContentVersion::ContentVersion ()
473         : id ("urn:uuid:" + make_uuid())
474 {
475
476 }
477
478
479 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
480 {
481         id = node->string_child("Id");
482         label_text = node->string_child("LabelText");
483 }
484
485
486 ContentVersion::ContentVersion (string label_text_)
487         : id ("urn:uuid:" + make_uuid())
488         , label_text (label_text_)
489 {
490
491 }
492
493
494 void
495 ContentVersion::as_xml (xmlpp::Element* parent) const
496 {
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);
500 }
501
502
503 Luminance::Luminance (cxml::ConstNodePtr node)
504 {
505         _unit = string_to_unit (node->string_attribute("units"));
506         _value = raw_convert<float> (node->content());
507 }
508
509
510 Luminance::Luminance (float value, Unit unit)
511         : _unit (unit)
512 {
513         set_value (value);
514 }
515
516
517 void
518 Luminance::set_value (float v)
519 {
520         if (v < 0) {
521                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
522         }
523
524         _value = v;
525 }
526
527
528 void
529 Luminance::as_xml (xmlpp::Element* parent, string ns) const
530 {
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));
534 }
535
536
537 string
538 Luminance::unit_to_string (Unit u)
539 {
540         switch (u) {
541         case CANDELA_PER_SQUARE_METRE:
542                 return "candela-per-square-metre";
543         case FOOT_LAMBERT:
544                 return "foot-lambert";
545         default:
546                 DCP_ASSERT (false);
547         }
548
549         return "";
550 }
551
552
553 Luminance::Unit
554 Luminance::string_to_unit (string u)
555 {
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;
560         }
561
562         throw XMLError (String::compose("Invalid luminance unit %1", u));
563 }
564
565
566 bool
567 dcp::operator== (Luminance const& a, Luminance const& b)
568 {
569         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
570 }
571
572
573 MainSoundConfiguration::MainSoundConfiguration (string s)
574 {
575         vector<string> parts;
576         boost::split (parts, s, boost::is_any_of("/"));
577         if (parts.size() != 2) {
578                 throw MainSoundConfigurationError (s);
579         }
580
581         if (parts[0] == "51") {
582                 _field = FIVE_POINT_ONE;
583         } else if (parts[0] == "71") {
584                 _field = SEVEN_POINT_ONE;
585         } else {
586                 throw MainSoundConfigurationError (s);
587         }
588
589         vector<string> channels;
590         boost::split (channels, parts[1], boost::is_any_of(","));
591
592         if (channels.size() > 16) {
593                 throw MainSoundConfigurationError (s);
594         }
595
596         BOOST_FOREACH (string i, channels) {
597                 if (i == "-") {
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);
625                 } else {
626                         throw MainSoundConfigurationError (s);
627                 }
628         }
629 }
630
631
632 MainSoundConfiguration::MainSoundConfiguration (Field field, int channels)
633         : _field (field)
634 {
635         _channels.resize (channels);
636 }
637
638
639 string
640 MainSoundConfiguration::to_string () const
641 {
642         string c;
643         if (_field == FIVE_POINT_ONE) {
644                 c = "51/";
645         } else {
646                 c = "71/";
647         }
648
649         BOOST_FOREACH (optional<Channel> i, _channels) {
650                 if (!i) {
651                         c += "-,";
652                 } else {
653                         switch (*i) {
654                         case LEFT:
655                                 c += "L,";
656                                 break;
657                         case RIGHT:
658                                 c += "R,";
659                                 break;
660                         case CENTRE:
661                                 c += "C,";
662                                 break;
663                         case LFE:
664                                 c += "LFE,";
665                                 break;
666                         case LS:
667                                 c += (_field == FIVE_POINT_ONE ? "Ls," : "Lss,");
668                                 break;
669                         case RS:
670                                 c += (_field == FIVE_POINT_ONE ? "Rs," : "Rss,");
671                                 break;
672                         case HI:
673                                 c += "HI,";
674                                 break;
675                         case VI:
676                                 c += "VIN,";
677                                 break;
678                         case LC:
679                         case RC:
680                                 c += "-,";
681                                 break;
682                         case BSL:
683                                 c += "Lrs,";
684                                 break;
685                         case BSR:
686                                 c += "Rrs,";
687                                 break;
688                         case MOTION_DATA:
689                                 c += "DBOX,";
690                                 break;
691                         case SYNC_SIGNAL:
692                                 c += "Sync,";
693                                 break;
694                         case SIGN_LANGUAGE:
695                                 /* XXX: not sure what this should be */
696                                 c += "Sign,";
697                                 break;
698                         default:
699                                 c += "-,";
700                                 break;
701                         }
702                 }
703         }
704
705         if (c.length() > 0) {
706                 c = c.substr(0, c.length() - 1);
707         }
708
709         return c;
710 }
711
712
713 optional<Channel>
714 MainSoundConfiguration::mapping (int index) const
715 {
716         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
717         return _channels[index];
718 }
719
720
721 void
722 MainSoundConfiguration::set_mapping (int index, Channel c)
723 {
724         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
725         _channels[index] = c;
726 }
727
728
729 string
730 dcp::status_to_string (Status s)
731 {
732         switch (s) {
733         case FINAL:
734                 return "final";
735         case TEMP:
736                 return "temp";
737         case PRE:
738                 return "pre";
739         default:
740                 DCP_ASSERT (false);
741         }
742
743 }
744
745
746 Status
747 dcp::string_to_status (string s)
748 {
749         if (s == "final") {
750                 return FINAL;
751         } else if (s == "temp") {
752                 return TEMP;
753         } else if (s == "pre") {
754                 return PRE;
755         }
756
757         DCP_ASSERT (false);
758 }
759