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