Fix/hide some warnings.
[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 /** Convert a content kind to a string which can be used in a
297  *  &lt;ContentKind&gt; node
298  *  @param kind ContentKind
299  *  @return string
300  */
301 string
302 dcp::content_kind_to_string (ContentKind kind)
303 {
304         switch (kind) {
305         case ContentKind::FEATURE:
306                 return "feature";
307         case ContentKind::SHORT:
308                 return "short";
309         case ContentKind::TRAILER:
310                 return "trailer";
311         case ContentKind::TEST:
312                 return "test";
313         case ContentKind::TRANSITIONAL:
314                 return "transitional";
315         case ContentKind::RATING:
316                 return "rating";
317         case ContentKind::TEASER:
318                 return "teaser";
319         case ContentKind::POLICY:
320                 return "policy";
321         case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
322                 return "psa";
323         case ContentKind::ADVERTISEMENT:
324                 return "advertisement";
325         case ContentKind::EPISODE:
326                 return "episode";
327         case ContentKind::PROMO:
328                 return "promo";
329         }
330
331         DCP_ASSERT (false);
332 }
333
334
335 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
336  *  Reasonably tolerant about varying case
337  *  @param kind Content kind string
338  *  @return libdcp ContentKind
339  */
340 dcp::ContentKind
341 dcp::content_kind_from_string (string kind)
342 {
343         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
344
345         if (kind == "feature") {
346                 return ContentKind::FEATURE;
347         } else if (kind == "short") {
348                 return ContentKind::SHORT;
349         } else if (kind == "trailer") {
350                 return ContentKind::TRAILER;
351         } else if (kind == "test") {
352                 return ContentKind::TEST;
353         } else if (kind == "transitional") {
354                 return ContentKind::TRANSITIONAL;
355         } else if (kind == "rating") {
356                 return ContentKind::RATING;
357         } else if (kind == "teaser") {
358                 return ContentKind::TEASER;
359         } else if (kind == "policy") {
360                 return ContentKind::POLICY;
361         } else if (kind == "psa") {
362                 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
363         } else if (kind == "advertisement") {
364                 return ContentKind::ADVERTISEMENT;
365         } else if (kind == "episode") {
366                 return ContentKind::EPISODE;
367         } else if (kind == "promo") {
368                 return ContentKind::PROMO;
369         }
370
371         throw BadContentKindError (kind);
372 }
373
374
375 string
376 dcp::marker_to_string (dcp::Marker m)
377 {
378         switch (m) {
379         case Marker::FFOC:
380                 return "FFOC";
381         case Marker::LFOC:
382                 return "LFOC";
383         case Marker::FFTC:
384                 return "FFTC";
385         case Marker::LFTC:
386                 return "LFTC";
387         case Marker::FFOI:
388                 return "FFOI";
389         case Marker::LFOI:
390                 return "LFOI";
391         case Marker::FFEC:
392                 return "FFEC";
393         case Marker::LFEC:
394                 return "LFEC";
395         case Marker::FFMC:
396                 return "FFMC";
397         case Marker::LFMC:
398                 return "LFMC";
399         }
400
401         DCP_ASSERT (false);
402 }
403
404
405 dcp::Marker
406 dcp::marker_from_string (string s)
407 {
408         if (s == "FFOC") {
409                 return Marker::FFOC;
410         } else if (s == "LFOC") {
411                 return Marker::LFOC;
412         } else if (s == "FFTC") {
413                 return Marker::FFTC;
414         } else if (s == "LFTC") {
415                 return Marker::LFTC;
416         } else if (s == "FFOI") {
417                 return Marker::FFOI;
418         } else if (s == "LFOI") {
419                 return Marker::LFOI;
420         } else if (s == "FFEC") {
421                 return Marker::FFEC;
422         } else if (s == "LFEC") {
423                 return Marker::LFEC;
424         } else if (s == "FFMC") {
425                 return Marker::FFMC;
426         } else if (s == "LFMC") {
427                 return Marker::LFMC;
428         }
429
430         DCP_ASSERT (false);
431 }
432
433
434 Rating::Rating (cxml::ConstNodePtr node)
435         : agency(node->string_child("Agency"))
436         , label(node->string_child("Label"))
437 {
438         node->done ();
439 }
440
441
442 void
443 Rating::as_xml (xmlpp::Element* parent) const
444 {
445         parent->add_child("Agency")->add_child_text(agency);
446         parent->add_child("Label")->add_child_text(label);
447 }
448
449
450 bool
451 dcp::operator== (Rating const & a, Rating const & b)
452 {
453         return a.agency == b.agency && a.label == b.label;
454 }
455
456
457 ContentVersion::ContentVersion ()
458         : id ("urn:uuid:" + make_uuid())
459 {
460
461 }
462
463
464 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
465         : id(node->string_child("Id"))
466         , label_text(node->string_child("LabelText"))
467 {
468
469 }
470
471
472 ContentVersion::ContentVersion (string label_text_)
473         : id ("urn:uuid:" + make_uuid())
474         , label_text (label_text_)
475 {
476
477 }
478
479
480 void
481 ContentVersion::as_xml (xmlpp::Element* parent) const
482 {
483         auto cv = parent->add_child("ContentVersion");
484         cv->add_child("Id")->add_child_text(id);
485         cv->add_child("LabelText")->add_child_text(label_text);
486 }
487
488
489 Luminance::Luminance (cxml::ConstNodePtr node)
490         : _value(raw_convert<float>(node->content()))
491         , _unit(string_to_unit(node->string_attribute("units")))
492 {
493
494 }
495
496
497 Luminance::Luminance (float value, Unit unit)
498         : _unit (unit)
499 {
500         set_value (value);
501 }
502
503
504 void
505 Luminance::set_value (float v)
506 {
507         if (v < 0) {
508                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
509         }
510
511         _value = v;
512 }
513
514
515 void
516 Luminance::as_xml (xmlpp::Element* parent, string ns) const
517 {
518         auto lum = parent->add_child("Luminance", ns);
519         lum->set_attribute("units", unit_to_string(_unit));
520         lum->add_child_text(raw_convert<string>(_value, 3));
521 }
522
523
524 string
525 Luminance::unit_to_string (Unit u)
526 {
527         switch (u) {
528         case Unit::CANDELA_PER_SQUARE_METRE:
529                 return "candela-per-square-metre";
530         case Unit::FOOT_LAMBERT:
531                 return "foot-lambert";
532         default:
533                 DCP_ASSERT (false);
534         }
535
536         return {};
537 }
538
539
540 Luminance::Unit
541 Luminance::string_to_unit (string u)
542 {
543         if (u == "candela-per-square-metre") {
544                 return Unit::CANDELA_PER_SQUARE_METRE;
545         } else if (u == "foot-lambert") {
546                 return Unit::FOOT_LAMBERT;
547         }
548
549         throw XMLError (String::compose("Invalid luminance unit %1", u));
550 }
551
552
553 float
554 Luminance::value_in_foot_lamberts () const
555 {
556         switch (_unit) {
557         case Unit::CANDELA_PER_SQUARE_METRE:
558                 return _value / 3.426;
559         case Unit::FOOT_LAMBERT:
560                 return _value;
561         default:
562                 DCP_ASSERT (false);
563         }
564 }
565
566
567 bool
568 dcp::operator== (Luminance const& a, Luminance const& b)
569 {
570         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
571 }
572
573
574 MainSoundConfiguration::MainSoundConfiguration (string s)
575 {
576         vector<string> parts;
577         boost::split (parts, s, boost::is_any_of("/"));
578         if (parts.size() != 2) {
579                 throw MainSoundConfigurationError (s);
580         }
581
582         if (parts[0] == "51") {
583                 _field = MCASoundField::FIVE_POINT_ONE;
584         } else if (parts[0] == "71") {
585                 _field = MCASoundField::SEVEN_POINT_ONE;
586         } else {
587                 throw MainSoundConfigurationError (s);
588         }
589
590         vector<string> channels;
591         boost::split (channels, parts[1], boost::is_any_of(","));
592
593         if (channels.size() > 16) {
594                 throw MainSoundConfigurationError (s);
595         }
596
597         for (auto i: channels) {
598                 if (i == "-") {
599                         _channels.push_back(optional<Channel>());
600                 } else {
601                         _channels.push_back(mca_id_to_channel(i));
602                 }
603         }
604 }
605
606
607 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
608         : _field (field)
609 {
610         _channels.resize (channels);
611 }
612
613
614 string
615 MainSoundConfiguration::to_string () const
616 {
617         string c;
618         if (_field == MCASoundField::FIVE_POINT_ONE) {
619                 c = "51/";
620         } else {
621                 c = "71/";
622         }
623
624         for (auto i: _channels) {
625                 if (!i) {
626                         c += "-,";
627                 } else {
628                         c += channel_to_mca_id(*i, _field) + ",";
629                 }
630         }
631
632         if (c.length() > 0) {
633                 c = c.substr(0, c.length() - 1);
634         }
635
636         return c;
637 }
638
639
640 optional<Channel>
641 MainSoundConfiguration::mapping (int index) const
642 {
643         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
644         return _channels[index];
645 }
646
647
648 void
649 MainSoundConfiguration::set_mapping (int index, Channel c)
650 {
651         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
652         _channels[index] = c;
653 }
654
655
656 string
657 dcp::status_to_string (Status s)
658 {
659         switch (s) {
660         case Status::FINAL:
661                 return "final";
662         case Status::TEMP:
663                 return "temp";
664         case Status::PRE:
665                 return "pre";
666         default:
667                 DCP_ASSERT (false);
668         }
669 }
670
671
672 Status
673 dcp::string_to_status (string s)
674 {
675         if (s == "final") {
676                 return Status::FINAL;
677         } else if (s == "temp") {
678                 return Status::TEMP;
679         } else if (s == "pre") {
680                 return Status::PRE;
681         }
682
683         DCP_ASSERT (false);
684 }
685
686
687 Channel
688 dcp::mca_id_to_channel (string id)
689 {
690         if (id == "L") {
691                 return Channel::LEFT;
692         } else if (id == "R") {
693                 return Channel::RIGHT;
694         } else if (id == "C") {
695                 return Channel::CENTRE;
696         } else if (id == "LFE") {
697                 return Channel::LFE;
698         } else if (id == "Ls" || id == "Lss") {
699                 return Channel::LS;
700         } else if (id == "Rs" || id == "Rss") {
701                 return Channel::RS;
702         } else if (id == "HI") {
703                 return Channel::HI;
704         } else if (id == "VIN") {
705                 return Channel::VI;
706         } else if (id == "Lrs") {
707                 return Channel::BSL;
708         } else if (id == "Rrs") {
709                 return Channel::BSR;
710         } else if (id == "DBOX") {
711                 return Channel::MOTION_DATA;
712         } else if (id == "FSKSync") {
713                 return Channel::SYNC_SIGNAL;
714         } else if (id == "SLVS") {
715                 return Channel::SIGN_LANGUAGE;
716         }
717
718         throw UnknownChannelIdError (id);
719 }
720
721
722 string
723 dcp::channel_to_mca_id (Channel c, MCASoundField field)
724 {
725         switch (c) {
726         case Channel::LEFT:
727                 return "L";
728         case Channel::RIGHT:
729                 return "R";
730         case Channel::CENTRE:
731                 return "C";
732         case Channel::LFE:
733                 return "LFE";
734         case Channel::LS:
735                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
736         case Channel::RS:
737                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
738         case Channel::HI:
739                 return "HI";
740         case Channel::VI:
741                 return "VIN";
742         case Channel::BSL:
743                 return "Lrs";
744         case Channel::BSR:
745                 return "Rrs";
746         case Channel::MOTION_DATA:
747                 return "DBOX";
748         case Channel::SYNC_SIGNAL:
749                 return "FSKSync";
750         case Channel::SIGN_LANGUAGE:
751                 return "SLVS";
752         default:
753                 break;
754         }
755
756         DCP_ASSERT (false);
757 }
758
759
760 string
761 dcp::channel_to_mca_name (Channel c, MCASoundField field)
762 {
763         switch (c) {
764         case Channel::LEFT:
765                 return "Left";
766         case Channel::RIGHT:
767                 return "Right";
768         case Channel::CENTRE:
769                 return "Center";
770         case Channel::LFE:
771                 return "LFE";
772         case Channel::LS:
773                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
774         case Channel::RS:
775                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
776         case Channel::HI:
777                 return "Hearing Impaired";
778         case Channel::VI:
779                 return "Visually Impaired-Narrative";
780         case Channel::BSL:
781                 return "Left Rear Surround";
782         case Channel::BSR:
783                 return "Right Rear Surround";
784         case Channel::MOTION_DATA:
785                 return "D-BOX Motion Code Primary Stream";
786         case Channel::SYNC_SIGNAL:
787                 return "FSK Sync";
788         case Channel::SIGN_LANGUAGE:
789                 return "Sign Language Video Stream";
790         default:
791                 break;
792         }
793
794         DCP_ASSERT (false);
795 }
796
797
798 ASDCP::UL
799 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
800 {
801         static byte_t sync_signal[] = {
802                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
803         };
804
805         static byte_t sign_language[] = {
806                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
807         };
808
809         switch (c) {
810         case Channel::LEFT:
811                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
812         case Channel::RIGHT:
813                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
814         case Channel::CENTRE:
815                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
816         case Channel::LFE:
817                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
818         case Channel::LS:
819                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
820         case Channel::RS:
821                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
822         case Channel::HI:
823                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
824         case Channel::VI:
825                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
826         case Channel::BSL:
827                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
828         case Channel::BSR:
829                 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
830         case Channel::MOTION_DATA:
831                 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
832         case Channel::SYNC_SIGNAL:
833                 return ASDCP::UL(sync_signal);
834         case Channel::SIGN_LANGUAGE:
835                 return ASDCP::UL(sign_language);
836         default:
837                 break;
838         }
839
840         DCP_ASSERT (false);
841 }
842
843
844 vector<dcp::Channel>
845 dcp::used_audio_channels ()
846 {
847         return {
848                 Channel::LEFT,
849                 Channel::RIGHT,
850                 Channel::CENTRE,
851                 Channel::LFE,
852                 Channel::LS,
853                 Channel::RS,
854                 Channel::HI,
855                 Channel::VI,
856                 Channel::BSL,
857                 Channel::BSR,
858                 Channel::MOTION_DATA,
859                 Channel::SYNC_SIGNAL,
860                 Channel::SIGN_LANGUAGE
861         };
862 }
863