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