Remove now-presumed-wrong 2.0 MCA sound field.
[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.size() != 2) {
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                 throw MainSoundConfigurationError (s);
486         }
487
488         vector<string> channels;
489         boost::split (channels, parts[1], boost::is_any_of(","));
490
491         if (channels.size() > 16) {
492                 throw MainSoundConfigurationError (s);
493         }
494
495         for (auto i: channels) {
496                 if (i == "-") {
497                         _channels.push_back(optional<Channel>());
498                 } else {
499                         _channels.push_back(mca_id_to_channel(i));
500                 }
501         }
502 }
503
504
505 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
506         : _field (field)
507 {
508         _channels.resize (channels);
509 }
510
511
512 string
513 MainSoundConfiguration::to_string () const
514 {
515         string c;
516         switch (_field) {
517         case MCASoundField::FIVE_POINT_ONE:
518                 c = "51/";
519                 break;
520         case MCASoundField::SEVEN_POINT_ONE:
521                 c = "71/";
522                 break;
523         default:
524                 DCP_ASSERT(false);
525         }
526
527         for (auto i: _channels) {
528                 if (!i) {
529                         c += "-,";
530                 } else {
531                         c += channel_to_mca_id(*i, _field) + ",";
532                 }
533         }
534
535         if (c.length() > 0) {
536                 c = c.substr(0, c.length() - 1);
537         }
538
539         return c;
540 }
541
542
543 optional<Channel>
544 MainSoundConfiguration::mapping (int index) const
545 {
546         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
547         return _channels[index];
548 }
549
550
551 void
552 MainSoundConfiguration::set_mapping (int index, Channel c)
553 {
554         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
555         _channels[index] = c;
556 }
557
558
559 string
560 dcp::status_to_string (Status s)
561 {
562         switch (s) {
563         case Status::FINAL:
564                 return "final";
565         case Status::TEMP:
566                 return "temp";
567         case Status::PRE:
568                 return "pre";
569         default:
570                 DCP_ASSERT (false);
571         }
572 }
573
574
575 Status
576 dcp::string_to_status (string s)
577 {
578         if (s == "final") {
579                 return Status::FINAL;
580         } else if (s == "temp") {
581                 return Status::TEMP;
582         } else if (s == "pre") {
583                 return Status::PRE;
584         }
585
586         DCP_ASSERT (false);
587 }
588
589
590 Channel
591 dcp::mca_id_to_channel (string id)
592 {
593         if (id == "L") {
594                 return Channel::LEFT;
595         } else if (id == "R") {
596                 return Channel::RIGHT;
597         } else if (id == "C") {
598                 return Channel::CENTRE;
599         } else if (id == "LFE") {
600                 return Channel::LFE;
601         } else if (id == "Ls" || id == "Lss") {
602                 return Channel::LS;
603         } else if (id == "Rs" || id == "Rss") {
604                 return Channel::RS;
605         } else if (id == "HI") {
606                 return Channel::HI;
607         } else if (id == "VIN") {
608                 return Channel::VI;
609         } else if (id == "Lrs") {
610                 return Channel::BSL;
611         } else if (id == "Rrs") {
612                 return Channel::BSR;
613         } else if (id == "DBOX") {
614                 return Channel::MOTION_DATA;
615         } else if (id == "FSKSync") {
616                 return Channel::SYNC_SIGNAL;
617         } else if (id == "SLVS") {
618                 return Channel::SIGN_LANGUAGE;
619         }
620
621         throw UnknownChannelIdError (id);
622 }
623
624
625 string
626 dcp::channel_to_mca_id (Channel c, MCASoundField field)
627 {
628         switch (c) {
629         case Channel::LEFT:
630                 return "L";
631         case Channel::RIGHT:
632                 return "R";
633         case Channel::CENTRE:
634                 return "C";
635         case Channel::LFE:
636                 return "LFE";
637         case Channel::LS:
638                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
639         case Channel::RS:
640                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
641         case Channel::HI:
642                 return "HI";
643         case Channel::VI:
644                 return "VIN";
645         case Channel::BSL:
646                 return "Lrs";
647         case Channel::BSR:
648                 return "Rrs";
649         case Channel::MOTION_DATA:
650                 return "DBOX";
651         case Channel::SYNC_SIGNAL:
652                 return "FSKSync";
653         case Channel::SIGN_LANGUAGE:
654                 return "SLVS";
655         default:
656                 break;
657         }
658
659         DCP_ASSERT (false);
660 }
661
662
663 string
664 dcp::channel_to_mca_name (Channel c, MCASoundField field)
665 {
666         switch (c) {
667         case Channel::LEFT:
668                 return "Left";
669         case Channel::RIGHT:
670                 return "Right";
671         case Channel::CENTRE:
672                 return "Center";
673         case Channel::LFE:
674                 return "LFE";
675         case Channel::LS:
676                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
677         case Channel::RS:
678                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
679         case Channel::HI:
680                 return "Hearing Impaired";
681         case Channel::VI:
682                 return "Visually Impaired-Narrative";
683         case Channel::BSL:
684                 return "Left Rear Surround";
685         case Channel::BSR:
686                 return "Right Rear Surround";
687         case Channel::MOTION_DATA:
688                 return "D-BOX Motion Code Primary Stream";
689         case Channel::SYNC_SIGNAL:
690                 return "FSK Sync";
691         case Channel::SIGN_LANGUAGE:
692                 return "Sign Language Video Stream";
693         default:
694                 break;
695         }
696
697         DCP_ASSERT (false);
698 }
699
700
701 ASDCP::UL
702 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
703 {
704         static byte_t sync_signal[] = {
705                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
706         };
707
708         static byte_t sign_language[] = {
709                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
710         };
711
712         switch (c) {
713         case Channel::LEFT:
714                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
715         case Channel::RIGHT:
716                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
717         case Channel::CENTRE:
718                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
719         case Channel::LFE:
720                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
721         case Channel::LS:
722                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
723         case Channel::RS:
724                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
725         case Channel::HI:
726                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
727         case Channel::VI:
728                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
729         case Channel::BSL:
730                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
731         case Channel::BSR:
732                 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
733         case Channel::MOTION_DATA:
734                 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
735         case Channel::SYNC_SIGNAL:
736                 return ASDCP::UL(sync_signal);
737         case Channel::SIGN_LANGUAGE:
738                 return ASDCP::UL(sign_language);
739         default:
740                 break;
741         }
742
743         DCP_ASSERT (false);
744 }
745
746
747 vector<dcp::Channel>
748 dcp::used_audio_channels ()
749 {
750         return {
751                 Channel::LEFT,
752                 Channel::RIGHT,
753                 Channel::CENTRE,
754                 Channel::LFE,
755                 Channel::LS,
756                 Channel::RS,
757                 Channel::HI,
758                 Channel::VI,
759                 Channel::BSL,
760                 Channel::BSR,
761                 Channel::MOTION_DATA,
762                 Channel::SYNC_SIGNAL,
763                 Channel::SIGN_LANGUAGE
764         };
765 }
766
767
768 string
769 dcp::formulation_to_string (dcp::Formulation formulation)
770 {
771         switch (formulation) {
772         case Formulation::MODIFIED_TRANSITIONAL_1:
773                 return "modified-transitional-1";
774         case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
775                 return "multiple-modified-transitional-1";
776         case Formulation::DCI_ANY:
777                 return "dci-any";
778         case Formulation::DCI_SPECIFIC:
779                 return "dci-specific";
780         }
781
782         DCP_ASSERT (false);
783 }
784
785
786 dcp::Formulation
787 dcp::string_to_formulation (string formulation)
788 {
789         if (formulation == "modified-transitional-1") {
790                 return Formulation::MODIFIED_TRANSITIONAL_1;
791         } else if (formulation == "multiple-modified-transitional-1") {
792                 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
793         } else if (formulation == "dci-any") {
794                 return Formulation::DCI_ANY;
795         } else if (formulation == "dci-specific") {
796                 return Formulation::DCI_SPECIFIC;
797         }
798
799         DCP_ASSERT (false);
800 }
801