Support MCA sound channel tags in MXF/CPL.
[libdcp.git] / src / types.cc
1 /*
2     Copyright (C) 2012-2019 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 #include "raw_convert.h"
35 #include "types.h"
36 #include "exceptions.h"
37 #include "compose.hpp"
38 #include "dcp_assert.h"
39 #include <libxml++/libxml++.h>
40 #include <boost/algorithm/string.hpp>
41 #include <boost/foreach.hpp>
42 #include <string>
43 #include <vector>
44 #include <cmath>
45 #include <cstdio>
46 #include <iomanip>
47
48 using std::string;
49 using std::ostream;
50 using std::vector;
51 using namespace dcp;
52 using namespace boost;
53
54 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
55 {
56         return (a.width == b.width && a.height == b.height);
57 }
58
59 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
60 {
61         return !(a == b);
62 }
63
64 ostream& dcp::operator<< (ostream& s, dcp::Size const & a)
65 {
66         s << a.width << "x" << a.height;
67         return s;
68 }
69
70 /** Construct a Fraction from a string of the form <numerator> <denominator>
71  *  e.g. "1 3".
72  */
73 Fraction::Fraction (string s)
74 {
75         vector<string> b;
76         split (b, s, is_any_of (" "));
77         if (b.size() != 2) {
78                 boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
79         }
80         numerator = raw_convert<int> (b[0]);
81         denominator = raw_convert<int> (b[1]);
82 }
83
84 string
85 Fraction::as_string () const
86 {
87         return String::compose ("%1 %2", numerator, denominator);
88 }
89
90 bool
91 dcp::operator== (Fraction const & a, Fraction const & b)
92 {
93         return (a.numerator == b.numerator && a.denominator == b.denominator);
94 }
95
96 bool
97 dcp::operator!= (Fraction const & a, Fraction const & b)
98 {
99         return (a.numerator != b.numerator || a.denominator != b.denominator);
100 }
101
102 ostream&
103 dcp::operator<< (ostream& s, Fraction const & f)
104 {
105         s << f.numerator << "/" << f.denominator;
106         return s;
107 }
108
109 /** Construct a Colour, initialising it to black. */
110 Colour::Colour ()
111         : r (0)
112         , g (0)
113         , b (0)
114 {
115
116 }
117
118 /** Construct a Colour from R, G and B.  The values run between
119  *  0 and 255.
120  */
121 Colour::Colour (int r_, int g_, int b_)
122         : r (r_)
123         , g (g_)
124         , b (b_)
125 {
126
127 }
128
129 /** Construct a Colour from an ARGB hex string; the alpha value is ignored.
130  *  @param argb_hex A string of the form AARRGGBB, where e.g. RR is a two-character
131  *  hex value.
132  */
133 Colour::Colour (string argb_hex)
134 {
135         int alpha;
136         if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
137                 boost::throw_exception (XMLError ("could not parse colour string"));
138         }
139 }
140
141 /** @return An ARGB string of the form AARRGGBB, where e.g. RR is a two-character
142  *  hex value.  The alpha value will always be FF (ie 255; maximum alpha).
143  */
144 string
145 Colour::to_argb_string () const
146 {
147         char buffer[9];
148         snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
149         return buffer;
150 }
151
152 /** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
153  *  hex value.
154  */
155 string
156 Colour::to_rgb_string () const
157 {
158         char buffer[7];
159         snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
160         return buffer;
161 }
162
163 /** operator== for Colours.
164  *  @param a First colour to compare.
165  *  @param b Second colour to compare.
166  */
167 bool
168 dcp::operator== (Colour const & a, Colour const & b)
169 {
170         return (a.r == b.r && a.g == b.g && a.b == b.b);
171 }
172
173 /** operator!= for Colours.
174  *  @param a First colour to compare.
175  *  @param b Second colour to compare.
176  */
177 bool
178 dcp::operator!= (Colour const & a, Colour const & b)
179 {
180         return !(a == b);
181 }
182
183 ostream &
184 dcp::operator<< (ostream& s, Colour const & c)
185 {
186         s << "(" << c.r << ", " << c.g << ", " << c.b << ")";
187         return s;
188 }
189
190 string
191 dcp::effect_to_string (Effect e)
192 {
193         switch (e) {
194         case NONE:
195                 return "none";
196         case BORDER:
197                 return "border";
198         case SHADOW:
199                 return "shadow";
200         }
201
202         boost::throw_exception (MiscError ("unknown effect type"));
203 }
204
205 Effect
206 dcp::string_to_effect (string s)
207 {
208         if (s == "none") {
209                 return NONE;
210         } else if (s == "border") {
211                 return BORDER;
212         } else if (s == "shadow") {
213                 return SHADOW;
214         }
215
216         boost::throw_exception (ReadError ("unknown subtitle effect type"));
217 }
218
219 string
220 dcp::halign_to_string (HAlign h)
221 {
222         switch (h) {
223         case HALIGN_LEFT:
224                 return "left";
225         case HALIGN_CENTER:
226                 return "center";
227         case HALIGN_RIGHT:
228                 return "right";
229         }
230
231         boost::throw_exception (MiscError ("unknown subtitle halign type"));
232 }
233
234 HAlign
235 dcp::string_to_halign (string s)
236 {
237         if (s == "left") {
238                 return HALIGN_LEFT;
239         } else if (s == "center") {
240                 return HALIGN_CENTER;
241         } else if (s == "right") {
242                 return HALIGN_RIGHT;
243         }
244
245         boost::throw_exception (ReadError ("unknown subtitle halign type"));
246 }
247
248 string
249 dcp::valign_to_string (VAlign v)
250 {
251         switch (v) {
252         case VALIGN_TOP:
253                 return "top";
254         case VALIGN_CENTER:
255                 return "center";
256         case VALIGN_BOTTOM:
257                 return "bottom";
258         }
259
260         boost::throw_exception (MiscError ("unknown subtitle valign type"));
261 }
262
263 VAlign
264 dcp::string_to_valign (string s)
265 {
266         if (s == "top") {
267                 return VALIGN_TOP;
268         } else if (s == "center") {
269                 return VALIGN_CENTER;
270         } else if (s == "bottom") {
271                 return VALIGN_BOTTOM;
272         }
273
274         boost::throw_exception (ReadError ("unknown subtitle valign type"));
275 }
276
277 string
278 dcp::direction_to_string (Direction v)
279 {
280         switch (v) {
281         case DIRECTION_LTR:
282                 return "ltr";
283         case DIRECTION_RTL:
284                 return "rtl";
285         case DIRECTION_TTB:
286                 return "ttb";
287         case DIRECTION_BTT:
288                 return "btt";
289         }
290
291         boost::throw_exception (MiscError ("unknown subtitle direction type"));
292 }
293
294 Direction
295 dcp::string_to_direction (string s)
296 {
297         if (s == "ltr" || s == "horizontal") {
298                 return DIRECTION_LTR;
299         } else if (s == "rtl") {
300                 return DIRECTION_RTL;
301         } else if (s == "ttb" || s == "vertical") {
302                 return DIRECTION_TTB;
303         } else if (s == "btt") {
304                 return DIRECTION_BTT;
305         }
306
307         boost::throw_exception (ReadError ("unknown subtitle direction type"));
308 }
309
310 /** Convert a content kind to a string which can be used in a
311  *  &lt;ContentKind&gt; node.
312  *  @param kind ContentKind.
313  *  @return string.
314  */
315 string
316 dcp::content_kind_to_string (ContentKind kind)
317 {
318         switch (kind) {
319         case FEATURE:
320                 return "feature";
321         case SHORT:
322                 return "short";
323         case TRAILER:
324                 return "trailer";
325         case TEST:
326                 return "test";
327         case TRANSITIONAL:
328                 return "transitional";
329         case RATING:
330                 return "rating";
331         case TEASER:
332                 return "teaser";
333         case POLICY:
334                 return "policy";
335         case PUBLIC_SERVICE_ANNOUNCEMENT:
336                 return "psa";
337         case ADVERTISEMENT:
338                 return "advertisement";
339         case EPISODE:
340                 return "episode";
341         case PROMO:
342                 return "promo";
343         }
344
345         DCP_ASSERT (false);
346 }
347
348 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
349  *  Reasonably tolerant about varying case.
350  *  @param kind Content kind string.
351  *  @return libdcp ContentKind.
352  */
353 dcp::ContentKind
354 dcp::content_kind_from_string (string kind)
355 {
356         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
357
358         if (kind == "feature") {
359                 return FEATURE;
360         } else if (kind == "short") {
361                 return SHORT;
362         } else if (kind == "trailer") {
363                 return TRAILER;
364         } else if (kind == "test") {
365                 return TEST;
366         } else if (kind == "transitional") {
367                 return TRANSITIONAL;
368         } else if (kind == "rating") {
369                 return RATING;
370         } else if (kind == "teaser") {
371                 return TEASER;
372         } else if (kind == "policy") {
373                 return POLICY;
374         } else if (kind == "psa") {
375                 return PUBLIC_SERVICE_ANNOUNCEMENT;
376         } else if (kind == "advertisement") {
377                 return ADVERTISEMENT;
378         } else if (kind == "episode") {
379                 return EPISODE;
380         } else if (kind == "promo") {
381                 return PROMO;
382         }
383
384         throw BadContentKindError (kind);
385 }
386
387 string
388 dcp::marker_to_string (dcp::Marker m)
389 {
390         switch (m) {
391         case FFOC:
392                 return "FFOC";
393         case LFOC:
394                 return "LFOC";
395         case FFTC:
396                 return "FFTC";
397         case LFTC:
398                 return "LFTC";
399         case FFOI:
400                 return "FFOI";
401         case LFOI:
402                 return "LFOI";
403         case FFEC:
404                 return "FFEC";
405         case LFEC:
406                 return "LFEC";
407         case FFMC:
408                 return "FFMC";
409         case LFMC:
410                 return "LFMC";
411         }
412
413         DCP_ASSERT (false);
414 }
415
416 dcp::Marker
417 dcp::marker_from_string (string s)
418 {
419         if (s == "FFOC") {
420                 return FFOC;
421         } else if (s == "LFOC") {
422                 return LFOC;
423         } else if (s == "FFTC") {
424                 return FFTC;
425         } else if (s == "LFTC") {
426                 return LFTC;
427         } else if (s == "FFOI") {
428                 return FFOI;
429         } else if (s == "LFOI") {
430                 return LFOI;
431         } else if (s == "FFEC") {
432                 return FFEC;
433         } else if (s == "LFEC") {
434                 return LFEC;
435         } else if (s == "FFMC") {
436                 return FFMC;
437         } else if (s == "LFMC") {
438                 return LFMC;
439         }
440
441         DCP_ASSERT (false);
442 }
443
444 Rating::Rating (cxml::ConstNodePtr node)
445 {
446         agency = node->string_child("Agency");
447         label = node->string_child("Label");
448         node->done ();
449 }
450
451 void
452 Rating::as_xml (xmlpp::Element* parent) const
453 {
454         parent->add_child("Agency")->add_child_text(agency);
455         parent->add_child("Label")->add_child_text(label);
456 }
457
458 bool
459 dcp::operator== (Rating const & a, Rating const & b)
460 {
461         return a.agency == b.agency && a.label == b.label;
462 }
463
464 ostream &
465 dcp::operator<< (ostream& s, Rating const & r)
466 {
467         s << r.agency << " " << r.label;
468         return s;
469 }
470
471
472 ContentVersion::ContentVersion ()
473         : id ("urn:uuid:" + make_uuid())
474 {
475
476 }
477
478
479 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
480 {
481         id = node->string_child("Id");
482         label_text = node->string_child("LabelText");
483 }
484
485
486 ContentVersion::ContentVersion (string label_text_)
487         : id ("urn:uuid:" + make_uuid())
488         , label_text (label_text_)
489 {
490
491 }
492
493
494 void
495 ContentVersion::as_xml (xmlpp::Element* parent) const
496 {
497         xmlpp::Node* cv = parent->add_child("ContentVersion");
498         cv->add_child("Id")->add_child_text(id);
499         cv->add_child("LabelText")->add_child_text(label_text);
500 }
501
502
503 Luminance::Luminance (cxml::ConstNodePtr node)
504 {
505         _unit = string_to_unit (node->string_attribute("units"));
506         _value = raw_convert<float> (node->content());
507 }
508
509
510 Luminance::Luminance (float value, Unit unit)
511         : _unit (unit)
512 {
513         set_value (value);
514 }
515
516
517 void
518 Luminance::set_value (float v)
519 {
520         if (v < 0) {
521                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
522         }
523
524         _value = v;
525 }
526
527
528 void
529 Luminance::as_xml (xmlpp::Element* parent, string ns) const
530 {
531         xmlpp::Element* lum = parent->add_child("Luminance", ns);
532         lum->set_attribute("units", unit_to_string(_unit));
533         lum->add_child_text(raw_convert<string>(_value, 3));
534 }
535
536
537 string
538 Luminance::unit_to_string (Unit u)
539 {
540         switch (u) {
541         case CANDELA_PER_SQUARE_METRE:
542                 return "candela-per-square-metre";
543         case FOOT_LAMBERT:
544                 return "foot-lambert";
545         default:
546                 DCP_ASSERT (false);
547         }
548
549         return "";
550 }
551
552
553 Luminance::Unit
554 Luminance::string_to_unit (string u)
555 {
556         if (u == "candela-per-square-metre") {
557                 return Unit::CANDELA_PER_SQUARE_METRE;
558         } else if (u == "foot-lambert") {
559                 return Unit::FOOT_LAMBERT;
560         }
561
562         throw XMLError (String::compose("Invalid luminance unit %1", u));
563 }
564
565
566 bool
567 dcp::operator== (Luminance const& a, Luminance const& b)
568 {
569         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
570 }
571
572
573 MainSoundConfiguration::MainSoundConfiguration (string s)
574 {
575         vector<string> parts;
576         boost::split (parts, s, boost::is_any_of("/"));
577         if (parts.size() != 2) {
578                 throw MainSoundConfigurationError (s);
579         }
580
581         if (parts[0] == "51") {
582                 _field = FIVE_POINT_ONE;
583         } else if (parts[0] == "71") {
584                 _field = SEVEN_POINT_ONE;
585         } else {
586                 throw MainSoundConfigurationError (s);
587         }
588
589         vector<string> channels;
590         boost::split (channels, parts[1], boost::is_any_of(","));
591
592         if (channels.size() > 16) {
593                 throw MainSoundConfigurationError (s);
594         }
595
596         BOOST_FOREACH (string i, channels) {
597                 if (i == "-") {
598                         _channels.push_back(optional<Channel>());
599                 } else {
600                         _channels.push_back(mca_id_to_channel(i));
601                 }
602         }
603 }
604
605
606 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
607         : _field (field)
608 {
609         _channels.resize (channels);
610 }
611
612
613 string
614 MainSoundConfiguration::to_string () const
615 {
616         string c;
617         if (_field == FIVE_POINT_ONE) {
618                 c = "51/";
619         } else {
620                 c = "71/";
621         }
622
623         BOOST_FOREACH (optional<Channel> i, _channels) {
624                 if (!i) {
625                         c += "-,";
626                 } else {
627                         c += channel_to_mca_id(*i, _field) + ",";
628                 }
629         }
630
631         if (c.length() > 0) {
632                 c = c.substr(0, c.length() - 1);
633         }
634
635         return c;
636 }
637
638
639 optional<Channel>
640 MainSoundConfiguration::mapping (int index) const
641 {
642         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
643         return _channels[index];
644 }
645
646
647 void
648 MainSoundConfiguration::set_mapping (int index, Channel c)
649 {
650         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
651         _channels[index] = c;
652 }
653
654
655 string
656 dcp::status_to_string (Status s)
657 {
658         switch (s) {
659         case FINAL:
660                 return "final";
661         case TEMP:
662                 return "temp";
663         case PRE:
664                 return "pre";
665         default:
666                 DCP_ASSERT (false);
667         }
668
669 }
670
671
672 Status
673 dcp::string_to_status (string s)
674 {
675         if (s == "final") {
676                 return FINAL;
677         } else if (s == "temp") {
678                 return TEMP;
679         } else if (s == "pre") {
680                 return 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 LEFT;
692         } else if (id == "R") {
693                 return RIGHT;
694         } else if (id == "C") {
695                 return CENTRE;
696         } else if (id == "LFE") {
697                 return LFE;
698         } else if (id == "Ls" || id == "Lss") {
699                 return LS;
700         } else if (id == "Rs" || id == "Rss") {
701                 return RS;
702         } else if (id == "HI") {
703                 return HI;
704         } else if (id == "VIN") {
705                 return VI;
706         } else if (id == "Lrs") {
707                 return BSL;
708         } else if (id == "Rrs") {
709                 return BSR;
710         } else if (id == "DBOX") {
711                 return MOTION_DATA;
712         } else if (id == "FSKSync") {
713                 return SYNC_SIGNAL;
714         } else if (id == "SLVS") {
715                 return 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 LEFT:
727                 return "L";
728         case RIGHT:
729                 return "R";
730         case CENTRE:
731                 return "C";
732         case LFE:
733                 return "LFE";
734         case LS:
735                 return field == FIVE_POINT_ONE ? "Ls" : "Lss";
736         case RS:
737                 return field == FIVE_POINT_ONE ? "Rs" : "Rss";
738         case HI:
739                 return "HI";
740         case VI:
741                 return "VIN";
742         case BSL:
743                 return "Lrs";
744         case BSR:
745                 return "Rrs";
746         case MOTION_DATA:
747                 return "DBOX";
748         case SYNC_SIGNAL:
749                 return "FSKSync";
750         case 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 LEFT:
765                 return "Left";
766         case RIGHT:
767                 return "Right";
768         case CENTRE:
769                 return "Center";
770         case LFE:
771                 return "LFE";
772         case LS:
773                 return field == FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
774         case RS:
775                 return field == FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
776         case HI:
777                 return "Hearing Impaired";
778         case VI:
779                 return "Visually Impaired-Narrative";
780         case BSL:
781                 return "Left Rear Surround";
782         case BSR:
783                 return "Right Rear Surround";
784         case MOTION_DATA:
785                 return "D-BOX Motion Code Primary Stream";
786         case SYNC_SIGNAL:
787                 return "FSK Sync";
788         case 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 LEFT:
811                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
812         case RIGHT:
813                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
814         case CENTRE:
815                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
816         case LFE:
817                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
818         case LS:
819                 return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
820         case RS:
821                 return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
822         case HI:
823                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
824         case VI:
825                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
826         case BSL:
827                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
828         case BSR:
829                 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
830         case MOTION_DATA:
831                 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
832         case SYNC_SIGNAL:
833                 return ASDCP::UL(sync_signal);
834         case SIGN_LANGUAGE:
835                 return ASDCP::UL(sign_language);
836         default:
837                 break;
838         }
839
840         DCP_ASSERT (false);
841 }
842
843