f22bfa911a1a9786e6ac260180c7722690f8f024
[libdcp.git] / src / types.cc
1 /*
2     Copyright (C) 2012-2021 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
35 /** @file  src/types.cc
36  *  @brief Miscellaneous types
37  */
38
39
40 #include "raw_convert.h"
41 #include "types.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>
47 #include <string>
48 #include <vector>
49 #include <cmath>
50 #include <cstdio>
51 #include <iomanip>
52
53
54 using std::string;
55 using std::ostream;
56 using std::vector;
57 using namespace dcp;
58 using namespace boost;
59
60
61 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
62 {
63         return (a.width == b.width && a.height == b.height);
64 }
65
66
67 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
68 {
69         return !(a == b);
70 }
71
72
73 /** Construct a Fraction from a string of the form <numerator> <denominator>
74  *  e.g. "1 3".
75  */
76 Fraction::Fraction (string s)
77 {
78         vector<string> b;
79         split (b, s, is_any_of (" "));
80         if (b.size() != 2) {
81                 boost::throw_exception (XMLError("malformed fraction " + s + " in XML node"));
82         }
83         numerator = raw_convert<int> (b[0]);
84         denominator = raw_convert<int> (b[1]);
85 }
86
87
88 string
89 Fraction::as_string () const
90 {
91         return String::compose ("%1 %2", numerator, denominator);
92 }
93
94
95 bool
96 dcp::operator== (Fraction const & a, Fraction const & b)
97 {
98         return (a.numerator == b.numerator && a.denominator == b.denominator);
99 }
100
101
102 bool
103 dcp::operator!= (Fraction const & a, Fraction const & b)
104 {
105         return (a.numerator != b.numerator || a.denominator != b.denominator);
106 }
107
108
109 Colour::Colour ()
110 {
111
112 }
113
114
115 Colour::Colour (int r_, int g_, int b_)
116         : r (r_)
117         , g (g_)
118         , b (b_)
119 {
120
121 }
122
123
124 Colour::Colour (string argb_hex)
125 {
126         int alpha;
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"));
129         }
130 }
131
132
133 string
134 Colour::to_argb_string () const
135 {
136         char buffer[9];
137         snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
138         return buffer;
139 }
140
141
142 string
143 Colour::to_rgb_string () const
144 {
145         char buffer[7];
146         snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
147         return buffer;
148 }
149
150
151 bool
152 dcp::operator== (Colour const & a, Colour const & b)
153 {
154         return (a.r == b.r && a.g == b.g && a.b == b.b);
155 }
156
157
158 bool
159 dcp::operator!= (Colour const & a, Colour const & b)
160 {
161         return !(a == b);
162 }
163
164
165 string
166 dcp::effect_to_string (Effect e)
167 {
168         switch (e) {
169         case Effect::NONE:
170                 return "none";
171         case Effect::BORDER:
172                 return "border";
173         case Effect::SHADOW:
174                 return "shadow";
175         }
176
177         boost::throw_exception (MiscError("unknown effect type"));
178 }
179
180
181 Effect
182 dcp::string_to_effect (string s)
183 {
184         if (s == "none") {
185                 return Effect::NONE;
186         } else if (s == "border") {
187                 return Effect::BORDER;
188         } else if (s == "shadow") {
189                 return Effect::SHADOW;
190         }
191
192         boost::throw_exception (ReadError("unknown subtitle effect type"));
193 }
194
195
196 string
197 dcp::halign_to_string (HAlign h)
198 {
199         switch (h) {
200         case HAlign::LEFT:
201                 return "left";
202         case HAlign::CENTER:
203                 return "center";
204         case HAlign::RIGHT:
205                 return "right";
206         }
207
208         boost::throw_exception (MiscError("unknown subtitle halign type"));
209 }
210
211
212 HAlign
213 dcp::string_to_halign (string s)
214 {
215         if (s == "left") {
216                 return HAlign::LEFT;
217         } else if (s == "center") {
218                 return HAlign::CENTER;
219         } else if (s == "right") {
220                 return HAlign::RIGHT;
221         }
222
223         boost::throw_exception (ReadError("unknown subtitle halign type"));
224 }
225
226
227 string
228 dcp::valign_to_string (VAlign v)
229 {
230         switch (v) {
231         case VAlign::TOP:
232                 return "top";
233         case VAlign::CENTER:
234                 return "center";
235         case VAlign::BOTTOM:
236                 return "bottom";
237         }
238
239         boost::throw_exception (MiscError("unknown subtitle valign type"));
240 }
241
242
243 VAlign
244 dcp::string_to_valign (string s)
245 {
246         if (s == "top") {
247                 return VAlign::TOP;
248         } else if (s == "center") {
249                 return VAlign::CENTER;
250         } else if (s == "bottom") {
251                 return VAlign::BOTTOM;
252         }
253
254         boost::throw_exception (ReadError("unknown subtitle valign type"));
255 }
256
257
258 string
259 dcp::direction_to_string (Direction v)
260 {
261         switch (v) {
262         case Direction::LTR:
263                 return "ltr";
264         case Direction::RTL:
265                 return "rtl";
266         case Direction::TTB:
267                 return "ttb";
268         case Direction::BTT:
269                 return "btt";
270         }
271
272         boost::throw_exception (MiscError("unknown subtitle direction type"));
273 }
274
275
276 Direction
277 dcp::string_to_direction (string s)
278 {
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;
287         }
288
289         boost::throw_exception (ReadError("unknown subtitle direction type"));
290 }
291
292
293 /** Convert a content kind to a string which can be used in a
294  *  &lt;ContentKind&gt; node
295  *  @param kind ContentKind
296  *  @return string
297  */
298 string
299 dcp::content_kind_to_string (ContentKind kind)
300 {
301         switch (kind) {
302         case ContentKind::FEATURE:
303                 return "feature";
304         case ContentKind::SHORT:
305                 return "short";
306         case ContentKind::TRAILER:
307                 return "trailer";
308         case ContentKind::TEST:
309                 return "test";
310         case ContentKind::TRANSITIONAL:
311                 return "transitional";
312         case ContentKind::RATING:
313                 return "rating";
314         case ContentKind::TEASER:
315                 return "teaser";
316         case ContentKind::POLICY:
317                 return "policy";
318         case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
319                 return "psa";
320         case ContentKind::ADVERTISEMENT:
321                 return "advertisement";
322         case ContentKind::EPISODE:
323                 return "episode";
324         case ContentKind::PROMO:
325                 return "promo";
326         }
327
328         DCP_ASSERT (false);
329 }
330
331
332 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
333  *  Reasonably tolerant about varying case
334  *  @param kind Content kind string
335  *  @return libdcp ContentKind
336  */
337 dcp::ContentKind
338 dcp::content_kind_from_string (string kind)
339 {
340         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
341
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;
366         }
367
368         throw BadContentKindError (kind);
369 }
370
371
372 string
373 dcp::marker_to_string (dcp::Marker m)
374 {
375         switch (m) {
376         case Marker::FFOC:
377                 return "FFOC";
378         case Marker::LFOC:
379                 return "LFOC";
380         case Marker::FFTC:
381                 return "FFTC";
382         case Marker::LFTC:
383                 return "LFTC";
384         case Marker::FFOI:
385                 return "FFOI";
386         case Marker::LFOI:
387                 return "LFOI";
388         case Marker::FFEC:
389                 return "FFEC";
390         case Marker::LFEC:
391                 return "LFEC";
392         case Marker::FFMC:
393                 return "FFMC";
394         case Marker::LFMC:
395                 return "LFMC";
396         }
397
398         DCP_ASSERT (false);
399 }
400
401
402 dcp::Marker
403 dcp::marker_from_string (string s)
404 {
405         if (s == "FFOC") {
406                 return Marker::FFOC;
407         } else if (s == "LFOC") {
408                 return Marker::LFOC;
409         } else if (s == "FFTC") {
410                 return Marker::FFTC;
411         } else if (s == "LFTC") {
412                 return Marker::LFTC;
413         } else if (s == "FFOI") {
414                 return Marker::FFOI;
415         } else if (s == "LFOI") {
416                 return Marker::LFOI;
417         } else if (s == "FFEC") {
418                 return Marker::FFEC;
419         } else if (s == "LFEC") {
420                 return Marker::LFEC;
421         } else if (s == "FFMC") {
422                 return Marker::FFMC;
423         } else if (s == "LFMC") {
424                 return Marker::LFMC;
425         }
426
427         DCP_ASSERT (false);
428 }
429
430
431 Rating::Rating (cxml::ConstNodePtr node)
432         : agency(node->string_child("Agency"))
433         , label(node->string_child("Label"))
434 {
435         node->done ();
436 }
437
438
439 void
440 Rating::as_xml (xmlpp::Element* parent) const
441 {
442         parent->add_child("Agency")->add_child_text(agency);
443         parent->add_child("Label")->add_child_text(label);
444 }
445
446
447 bool
448 dcp::operator== (Rating const & a, Rating const & b)
449 {
450         return a.agency == b.agency && a.label == b.label;
451 }
452
453
454 ContentVersion::ContentVersion ()
455         : id ("urn:uuid:" + make_uuid())
456 {
457
458 }
459
460
461 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
462         : id(node->string_child("Id"))
463         , label_text(node->string_child("LabelText"))
464 {
465
466 }
467
468
469 ContentVersion::ContentVersion (string label_text_)
470         : id ("urn:uuid:" + make_uuid())
471         , label_text (label_text_)
472 {
473
474 }
475
476
477 void
478 ContentVersion::as_xml (xmlpp::Element* parent) const
479 {
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);
483 }
484
485
486 Luminance::Luminance (cxml::ConstNodePtr node)
487         : _unit(string_to_unit(node->string_attribute("units")))
488         , _value(raw_convert<float>(node->content()))
489 {
490
491 }
492
493
494 Luminance::Luminance (float value, Unit unit)
495         : _unit (unit)
496 {
497         set_value (value);
498 }
499
500
501 void
502 Luminance::set_value (float v)
503 {
504         if (v < 0) {
505                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
506         }
507
508         _value = v;
509 }
510
511
512 void
513 Luminance::as_xml (xmlpp::Element* parent, string ns) const
514 {
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));
518 }
519
520
521 string
522 Luminance::unit_to_string (Unit u)
523 {
524         switch (u) {
525         case Unit::CANDELA_PER_SQUARE_METRE:
526                 return "candela-per-square-metre";
527         case Unit::FOOT_LAMBERT:
528                 return "foot-lambert";
529         default:
530                 DCP_ASSERT (false);
531         }
532
533         return {};
534 }
535
536
537 Luminance::Unit
538 Luminance::string_to_unit (string u)
539 {
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;
544         }
545
546         throw XMLError (String::compose("Invalid luminance unit %1", u));
547 }
548
549
550 bool
551 dcp::operator== (Luminance const& a, Luminance const& b)
552 {
553         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
554 }
555
556
557 MainSoundConfiguration::MainSoundConfiguration (string s)
558 {
559         vector<string> parts;
560         boost::split (parts, s, boost::is_any_of("/"));
561         if (parts.size() != 2) {
562                 throw MainSoundConfigurationError (s);
563         }
564
565         if (parts[0] == "51") {
566                 _field = MCASoundField::FIVE_POINT_ONE;
567         } else if (parts[0] == "71") {
568                 _field = MCASoundField::SEVEN_POINT_ONE;
569         } else {
570                 throw MainSoundConfigurationError (s);
571         }
572
573         vector<string> channels;
574         boost::split (channels, parts[1], boost::is_any_of(","));
575
576         if (channels.size() > 16) {
577                 throw MainSoundConfigurationError (s);
578         }
579
580         for (auto i: channels) {
581                 if (i == "-") {
582                         _channels.push_back(optional<Channel>());
583                 } else {
584                         _channels.push_back(mca_id_to_channel(i));
585                 }
586         }
587 }
588
589
590 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
591         : _field (field)
592 {
593         _channels.resize (channels);
594 }
595
596
597 string
598 MainSoundConfiguration::to_string () const
599 {
600         string c;
601         if (_field == MCASoundField::FIVE_POINT_ONE) {
602                 c = "51/";
603         } else {
604                 c = "71/";
605         }
606
607         for (auto i: _channels) {
608                 if (!i) {
609                         c += "-,";
610                 } else {
611                         c += channel_to_mca_id(*i, _field) + ",";
612                 }
613         }
614
615         if (c.length() > 0) {
616                 c = c.substr(0, c.length() - 1);
617         }
618
619         return c;
620 }
621
622
623 optional<Channel>
624 MainSoundConfiguration::mapping (int index) const
625 {
626         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
627         return _channels[index];
628 }
629
630
631 void
632 MainSoundConfiguration::set_mapping (int index, Channel c)
633 {
634         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
635         _channels[index] = c;
636 }
637
638
639 string
640 dcp::status_to_string (Status s)
641 {
642         switch (s) {
643         case Status::FINAL:
644                 return "final";
645         case Status::TEMP:
646                 return "temp";
647         case Status::PRE:
648                 return "pre";
649         default:
650                 DCP_ASSERT (false);
651         }
652 }
653
654
655 Status
656 dcp::string_to_status (string s)
657 {
658         if (s == "final") {
659                 return Status::FINAL;
660         } else if (s == "temp") {
661                 return Status::TEMP;
662         } else if (s == "pre") {
663                 return Status::PRE;
664         }
665
666         DCP_ASSERT (false);
667 }
668
669
670 Channel
671 dcp::mca_id_to_channel (string id)
672 {
673         if (id == "L") {
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") {
680                 return Channel::LFE;
681         } else if (id == "Ls" || id == "Lss") {
682                 return Channel::LS;
683         } else if (id == "Rs" || id == "Rss") {
684                 return Channel::RS;
685         } else if (id == "HI") {
686                 return Channel::HI;
687         } else if (id == "VIN") {
688                 return Channel::VI;
689         } else if (id == "Lrs") {
690                 return Channel::BSL;
691         } else if (id == "Rrs") {
692                 return Channel::BSR;
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;
699         }
700
701         throw UnknownChannelIdError (id);
702 }
703
704
705 string
706 dcp::channel_to_mca_id (Channel c, MCASoundField field)
707 {
708         switch (c) {
709         case Channel::LEFT:
710                 return "L";
711         case Channel::RIGHT:
712                 return "R";
713         case Channel::CENTRE:
714                 return "C";
715         case Channel::LFE:
716                 return "LFE";
717         case Channel::LS:
718                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
719         case Channel::RS:
720                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
721         case Channel::HI:
722                 return "HI";
723         case Channel::VI:
724                 return "VIN";
725         case Channel::BSL:
726                 return "Lrs";
727         case Channel::BSR:
728                 return "Rrs";
729         case Channel::MOTION_DATA:
730                 return "DBOX";
731         case Channel::SYNC_SIGNAL:
732                 return "FSKSync";
733         case Channel::SIGN_LANGUAGE:
734                 return "SLVS";
735         default:
736                 break;
737         }
738
739         DCP_ASSERT (false);
740 }
741
742
743 string
744 dcp::channel_to_mca_name (Channel c, MCASoundField field)
745 {
746         switch (c) {
747         case Channel::LEFT:
748                 return "Left";
749         case Channel::RIGHT:
750                 return "Right";
751         case Channel::CENTRE:
752                 return "Center";
753         case Channel::LFE:
754                 return "LFE";
755         case Channel::LS:
756                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
757         case Channel::RS:
758                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
759         case Channel::HI:
760                 return "Hearing Impaired";
761         case Channel::VI:
762                 return "Visually Impaired-Narrative";
763         case Channel::BSL:
764                 return "Left Rear Surround";
765         case Channel::BSR:
766                 return "Right Rear Surround";
767         case Channel::MOTION_DATA:
768                 return "D-BOX Motion Code Primary Stream";
769         case Channel::SYNC_SIGNAL:
770                 return "FSK Sync";
771         case Channel::SIGN_LANGUAGE:
772                 return "Sign Language Video Stream";
773         default:
774                 break;
775         }
776
777         DCP_ASSERT (false);
778 }
779
780
781 ASDCP::UL
782 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
783 {
784         static byte_t sync_signal[] = {
785                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
786         };
787
788         static byte_t sign_language[] = {
789                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
790         };
791
792         switch (c) {
793         case Channel::LEFT:
794                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
795         case Channel::RIGHT:
796                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
797         case Channel::CENTRE:
798                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
799         case Channel::LFE:
800                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
801         case Channel::LS:
802                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
803         case Channel::RS:
804                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
805         case Channel::HI:
806                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
807         case Channel::VI:
808                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
809         case Channel::BSL:
810                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
811         case Channel::BSR:
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);
819         default:
820                 break;
821         }
822
823         DCP_ASSERT (false);
824 }
825
826
827 vector<dcp::Channel>
828 dcp::used_audio_channels ()
829 {
830         vector<Channel> c;
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);
844         return c;
845 }
846