Hide dump_notes() again.
[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         : _value(raw_convert<float>(node->content()))
488         , _unit(string_to_unit(node->string_attribute("units")))
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 float
551 Luminance::value_in_foot_lamberts () const
552 {
553         switch (_unit) {
554         case Unit::CANDELA_PER_SQUARE_METRE:
555                 return _value / 3.426;
556         case Unit::FOOT_LAMBERT:
557                 return _value;
558         default:
559                 DCP_ASSERT (false);
560         }
561 }
562
563
564 bool
565 dcp::operator== (Luminance const& a, Luminance const& b)
566 {
567         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
568 }
569
570
571 MainSoundConfiguration::MainSoundConfiguration (string s)
572 {
573         vector<string> parts;
574         boost::split (parts, s, boost::is_any_of("/"));
575         if (parts.size() != 2) {
576                 throw MainSoundConfigurationError (s);
577         }
578
579         if (parts[0] == "51") {
580                 _field = MCASoundField::FIVE_POINT_ONE;
581         } else if (parts[0] == "71") {
582                 _field = MCASoundField::SEVEN_POINT_ONE;
583         } else {
584                 throw MainSoundConfigurationError (s);
585         }
586
587         vector<string> channels;
588         boost::split (channels, parts[1], boost::is_any_of(","));
589
590         if (channels.size() > 16) {
591                 throw MainSoundConfigurationError (s);
592         }
593
594         for (auto i: channels) {
595                 if (i == "-") {
596                         _channels.push_back(optional<Channel>());
597                 } else {
598                         _channels.push_back(mca_id_to_channel(i));
599                 }
600         }
601 }
602
603
604 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
605         : _field (field)
606 {
607         _channels.resize (channels);
608 }
609
610
611 string
612 MainSoundConfiguration::to_string () const
613 {
614         string c;
615         if (_field == MCASoundField::FIVE_POINT_ONE) {
616                 c = "51/";
617         } else {
618                 c = "71/";
619         }
620
621         for (auto i: _channels) {
622                 if (!i) {
623                         c += "-,";
624                 } else {
625                         c += channel_to_mca_id(*i, _field) + ",";
626                 }
627         }
628
629         if (c.length() > 0) {
630                 c = c.substr(0, c.length() - 1);
631         }
632
633         return c;
634 }
635
636
637 optional<Channel>
638 MainSoundConfiguration::mapping (int index) const
639 {
640         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
641         return _channels[index];
642 }
643
644
645 void
646 MainSoundConfiguration::set_mapping (int index, Channel c)
647 {
648         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
649         _channels[index] = c;
650 }
651
652
653 string
654 dcp::status_to_string (Status s)
655 {
656         switch (s) {
657         case Status::FINAL:
658                 return "final";
659         case Status::TEMP:
660                 return "temp";
661         case Status::PRE:
662                 return "pre";
663         default:
664                 DCP_ASSERT (false);
665         }
666 }
667
668
669 Status
670 dcp::string_to_status (string s)
671 {
672         if (s == "final") {
673                 return Status::FINAL;
674         } else if (s == "temp") {
675                 return Status::TEMP;
676         } else if (s == "pre") {
677                 return Status::PRE;
678         }
679
680         DCP_ASSERT (false);
681 }
682
683
684 Channel
685 dcp::mca_id_to_channel (string id)
686 {
687         if (id == "L") {
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") {
694                 return Channel::LFE;
695         } else if (id == "Ls" || id == "Lss") {
696                 return Channel::LS;
697         } else if (id == "Rs" || id == "Rss") {
698                 return Channel::RS;
699         } else if (id == "HI") {
700                 return Channel::HI;
701         } else if (id == "VIN") {
702                 return Channel::VI;
703         } else if (id == "Lrs") {
704                 return Channel::BSL;
705         } else if (id == "Rrs") {
706                 return Channel::BSR;
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;
713         }
714
715         throw UnknownChannelIdError (id);
716 }
717
718
719 string
720 dcp::channel_to_mca_id (Channel c, MCASoundField field)
721 {
722         switch (c) {
723         case Channel::LEFT:
724                 return "L";
725         case Channel::RIGHT:
726                 return "R";
727         case Channel::CENTRE:
728                 return "C";
729         case Channel::LFE:
730                 return "LFE";
731         case Channel::LS:
732                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
733         case Channel::RS:
734                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
735         case Channel::HI:
736                 return "HI";
737         case Channel::VI:
738                 return "VIN";
739         case Channel::BSL:
740                 return "Lrs";
741         case Channel::BSR:
742                 return "Rrs";
743         case Channel::MOTION_DATA:
744                 return "DBOX";
745         case Channel::SYNC_SIGNAL:
746                 return "FSKSync";
747         case Channel::SIGN_LANGUAGE:
748                 return "SLVS";
749         default:
750                 break;
751         }
752
753         DCP_ASSERT (false);
754 }
755
756
757 string
758 dcp::channel_to_mca_name (Channel c, MCASoundField field)
759 {
760         switch (c) {
761         case Channel::LEFT:
762                 return "Left";
763         case Channel::RIGHT:
764                 return "Right";
765         case Channel::CENTRE:
766                 return "Center";
767         case Channel::LFE:
768                 return "LFE";
769         case Channel::LS:
770                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
771         case Channel::RS:
772                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
773         case Channel::HI:
774                 return "Hearing Impaired";
775         case Channel::VI:
776                 return "Visually Impaired-Narrative";
777         case Channel::BSL:
778                 return "Left Rear Surround";
779         case Channel::BSR:
780                 return "Right Rear Surround";
781         case Channel::MOTION_DATA:
782                 return "D-BOX Motion Code Primary Stream";
783         case Channel::SYNC_SIGNAL:
784                 return "FSK Sync";
785         case Channel::SIGN_LANGUAGE:
786                 return "Sign Language Video Stream";
787         default:
788                 break;
789         }
790
791         DCP_ASSERT (false);
792 }
793
794
795 ASDCP::UL
796 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
797 {
798         static byte_t sync_signal[] = {
799                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
800         };
801
802         static byte_t sign_language[] = {
803                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
804         };
805
806         switch (c) {
807         case Channel::LEFT:
808                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
809         case Channel::RIGHT:
810                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
811         case Channel::CENTRE:
812                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
813         case Channel::LFE:
814                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
815         case Channel::LS:
816                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
817         case Channel::RS:
818                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
819         case Channel::HI:
820                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
821         case Channel::VI:
822                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
823         case Channel::BSL:
824                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
825         case Channel::BSR:
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);
833         default:
834                 break;
835         }
836
837         DCP_ASSERT (false);
838 }
839
840
841 vector<dcp::Channel>
842 dcp::used_audio_channels ()
843 {
844         return {
845                 Channel::LEFT,
846                 Channel::RIGHT,
847                 Channel::CENTRE,
848                 Channel::LFE,
849                 Channel::LS,
850                 Channel::RS,
851                 Channel::HI,
852                 Channel::VI,
853                 Channel::BSL,
854                 Channel::BSR,
855                 Channel::MOTION_DATA,
856                 Channel::SYNC_SIGNAL,
857                 Channel::SIGN_LANGUAGE
858         };
859 }
860