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