Cleanup: extract HAlign to its own files.
[libdcp.git] / src / types.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 /** @file  src/types.cc
36  *  @brief Miscellaneous types
37  */
38
39
40 #include "compose.hpp"
41 #include "dcp_assert.h"
42 #include "exceptions.h"
43 #include "raw_convert.h"
44 #include "types.h"
45 #include "warnings.h"
46 LIBDCP_DISABLE_WARNINGS
47 #include <libxml++/libxml++.h>
48 LIBDCP_ENABLE_WARNINGS
49 #include <boost/algorithm/string.hpp>
50 #include <string>
51 #include <vector>
52 #include <cmath>
53 #include <cstdio>
54 #include <iomanip>
55
56
57 using std::string;
58 using std::ostream;
59 using std::vector;
60 using namespace dcp;
61 using namespace boost;
62
63
64 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
65 {
66         return (a.width == b.width && a.height == b.height);
67 }
68
69
70 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
71 {
72         return !(a == b);
73 }
74
75
76 /** Construct a Fraction from a string of the form "numerator denominator"
77  *  e.g. "1 3".
78  */
79 Fraction::Fraction (string s)
80 {
81         vector<string> b;
82         split (b, s, is_any_of (" "));
83         if (b.size() != 2) {
84                 boost::throw_exception (XMLError("malformed fraction " + s + " in XML node"));
85         }
86         numerator = raw_convert<int> (b[0]);
87         denominator = raw_convert<int> (b[1]);
88 }
89
90
91 string
92 Fraction::as_string () const
93 {
94         return String::compose ("%1 %2", numerator, denominator);
95 }
96
97
98 bool
99 dcp::operator== (Fraction const & a, Fraction const & b)
100 {
101         return (a.numerator == b.numerator && a.denominator == b.denominator);
102 }
103
104
105 bool
106 dcp::operator!= (Fraction const & a, Fraction const & b)
107 {
108         return (a.numerator != b.numerator || a.denominator != b.denominator);
109 }
110
111
112 Colour::Colour (int r_, int g_, int b_)
113         : r (r_)
114         , g (g_)
115         , b (b_)
116 {
117
118 }
119
120
121 Colour::Colour (string argb_hex)
122 {
123         int alpha;
124         if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
125                 boost::throw_exception (XMLError ("could not parse colour string"));
126         }
127 }
128
129
130 string
131 Colour::to_argb_string () const
132 {
133         char buffer[9];
134         snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
135         return buffer;
136 }
137
138
139 string
140 Colour::to_rgb_string () const
141 {
142         char buffer[7];
143         snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
144         return buffer;
145 }
146
147
148 bool
149 dcp::operator== (Colour const & a, Colour const & b)
150 {
151         return (a.r == b.r && a.g == b.g && a.b == b.b);
152 }
153
154
155 bool
156 dcp::operator!= (Colour const & a, Colour const & b)
157 {
158         return !(a == b);
159 }
160
161
162 string
163 dcp::effect_to_string (Effect e)
164 {
165         switch (e) {
166         case Effect::NONE:
167                 return "none";
168         case Effect::BORDER:
169                 return "border";
170         case Effect::SHADOW:
171                 return "shadow";
172         }
173
174         boost::throw_exception (MiscError("unknown effect type"));
175 }
176
177
178 Effect
179 dcp::string_to_effect (string s)
180 {
181         if (s == "none") {
182                 return Effect::NONE;
183         } else if (s == "border") {
184                 return Effect::BORDER;
185         } else if (s == "shadow") {
186                 return Effect::SHADOW;
187         }
188
189         boost::throw_exception (ReadError("unknown subtitle effect type"));
190 }
191
192
193 string
194 dcp::direction_to_string (Direction v)
195 {
196         switch (v) {
197         case Direction::LTR:
198                 return "ltr";
199         case Direction::RTL:
200                 return "rtl";
201         case Direction::TTB:
202                 return "ttb";
203         case Direction::BTT:
204                 return "btt";
205         }
206
207         boost::throw_exception (MiscError("unknown subtitle direction type"));
208 }
209
210
211 Direction
212 dcp::string_to_direction (string s)
213 {
214         if (s == "ltr" || s == "horizontal") {
215                 return Direction::LTR;
216         } else if (s == "rtl") {
217                 return Direction::RTL;
218         } else if (s == "ttb" || s == "vertical") {
219                 return Direction::TTB;
220         } else if (s == "btt") {
221                 return Direction::BTT;
222         }
223
224         boost::throw_exception (ReadError("unknown subtitle direction type"));
225 }
226
227
228 string
229 dcp::marker_to_string (dcp::Marker m)
230 {
231         switch (m) {
232         case Marker::FFOC:
233                 return "FFOC";
234         case Marker::LFOC:
235                 return "LFOC";
236         case Marker::FFTC:
237                 return "FFTC";
238         case Marker::LFTC:
239                 return "LFTC";
240         case Marker::FFOI:
241                 return "FFOI";
242         case Marker::LFOI:
243                 return "LFOI";
244         case Marker::FFEC:
245                 return "FFEC";
246         case Marker::LFEC:
247                 return "LFEC";
248         case Marker::FFMC:
249                 return "FFMC";
250         case Marker::LFMC:
251                 return "LFMC";
252         }
253
254         DCP_ASSERT (false);
255 }
256
257
258 dcp::Marker
259 dcp::marker_from_string (string s)
260 {
261         if (s == "FFOC") {
262                 return Marker::FFOC;
263         } else if (s == "LFOC") {
264                 return Marker::LFOC;
265         } else if (s == "FFTC") {
266                 return Marker::FFTC;
267         } else if (s == "LFTC") {
268                 return Marker::LFTC;
269         } else if (s == "FFOI") {
270                 return Marker::FFOI;
271         } else if (s == "LFOI") {
272                 return Marker::LFOI;
273         } else if (s == "FFEC") {
274                 return Marker::FFEC;
275         } else if (s == "LFEC") {
276                 return Marker::LFEC;
277         } else if (s == "FFMC") {
278                 return Marker::FFMC;
279         } else if (s == "LFMC") {
280                 return Marker::LFMC;
281         }
282
283         DCP_ASSERT (false);
284 }
285
286
287 ContentVersion::ContentVersion ()
288         : id ("urn:uuid:" + make_uuid())
289 {
290
291 }
292
293
294 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
295         : id(node->string_child("Id"))
296         , label_text(node->string_child("LabelText"))
297 {
298
299 }
300
301
302 ContentVersion::ContentVersion (string label_text_)
303         : id ("urn:uuid:" + make_uuid())
304         , label_text (label_text_)
305 {
306
307 }
308
309
310 void
311 ContentVersion::as_xml (xmlpp::Element* parent) const
312 {
313         auto cv = parent->add_child("ContentVersion");
314         cv->add_child("Id")->add_child_text(id);
315         cv->add_child("LabelText")->add_child_text(label_text);
316 }
317
318
319 Luminance::Luminance (cxml::ConstNodePtr node)
320         : _value(raw_convert<float>(node->content()))
321         , _unit(string_to_unit(node->string_attribute("units")))
322 {
323
324 }
325
326
327 Luminance::Luminance (float value, Unit unit)
328         : _unit (unit)
329 {
330         set_value (value);
331 }
332
333
334 void
335 Luminance::set_value (float v)
336 {
337         if (v < 0) {
338                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
339         }
340
341         _value = v;
342 }
343
344
345 void
346 Luminance::as_xml (xmlpp::Element* parent, string ns) const
347 {
348         auto lum = parent->add_child("Luminance", ns);
349         lum->set_attribute("units", unit_to_string(_unit));
350         lum->add_child_text(raw_convert<string>(_value, 3));
351 }
352
353
354 string
355 Luminance::unit_to_string (Unit u)
356 {
357         switch (u) {
358         case Unit::CANDELA_PER_SQUARE_METRE:
359                 return "candela-per-square-metre";
360         case Unit::FOOT_LAMBERT:
361                 return "foot-lambert";
362         default:
363                 DCP_ASSERT (false);
364         }
365
366         return {};
367 }
368
369
370 Luminance::Unit
371 Luminance::string_to_unit (string u)
372 {
373         if (u == "candela-per-square-metre") {
374                 return Unit::CANDELA_PER_SQUARE_METRE;
375         } else if (u == "foot-lambert") {
376                 return Unit::FOOT_LAMBERT;
377         }
378
379         throw XMLError (String::compose("Invalid luminance unit %1", u));
380 }
381
382
383 float
384 Luminance::value_in_foot_lamberts () const
385 {
386         switch (_unit) {
387         case Unit::CANDELA_PER_SQUARE_METRE:
388                 return _value / 3.426;
389         case Unit::FOOT_LAMBERT:
390                 return _value;
391         default:
392                 DCP_ASSERT (false);
393         }
394 }
395
396
397 bool
398 dcp::operator== (Luminance const& a, Luminance const& b)
399 {
400         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
401 }
402
403
404 MainSoundConfiguration::MainSoundConfiguration (string s)
405 {
406         vector<string> parts;
407         boost::split (parts, s, boost::is_any_of("/"));
408         if (parts.empty()) {
409                 throw MainSoundConfigurationError(s);
410         }
411
412         if (parts[0] == "51") {
413                 _field = MCASoundField::FIVE_POINT_ONE;
414         } else if (parts[0] == "71") {
415                 _field = MCASoundField::SEVEN_POINT_ONE;
416         } else {
417                 _field = MCASoundField::OTHER;
418         }
419
420         if (parts.size() < 2) {
421                 /* I think it's OK to just have the sound field descriptor with no channels, though
422                  * to me it's not clear and I might be wrong.
423                  */
424                 return;
425         }
426
427         vector<string> channels;
428         boost::split (channels, parts[1], boost::is_any_of(","));
429
430         if (channels.size() > 16) {
431                 throw MainSoundConfigurationError (s);
432         }
433
434         for (auto i: channels) {
435                 if (i == "-") {
436                         _channels.push_back(optional<Channel>());
437                 } else {
438                         _channels.push_back(mca_id_to_channel(i));
439                 }
440         }
441 }
442
443
444 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
445         : _field (field)
446 {
447         _channels.resize (channels);
448 }
449
450
451 string
452 MainSoundConfiguration::to_string () const
453 {
454         string c;
455         switch (_field) {
456         case MCASoundField::FIVE_POINT_ONE:
457                 c = "51/";
458                 break;
459         case MCASoundField::SEVEN_POINT_ONE:
460                 c = "71/";
461                 break;
462         default:
463                 DCP_ASSERT(false);
464         }
465
466         for (auto i: _channels) {
467                 if (!i) {
468                         c += "-,";
469                 } else {
470                         c += channel_to_mca_id(*i, _field) + ",";
471                 }
472         }
473
474         if (c.length() > 0) {
475                 c = c.substr(0, c.length() - 1);
476         }
477
478         return c;
479 }
480
481
482 optional<Channel>
483 MainSoundConfiguration::mapping (int index) const
484 {
485         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
486         return _channels[index];
487 }
488
489
490 void
491 MainSoundConfiguration::set_mapping (int index, Channel c)
492 {
493         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
494         _channels[index] = c;
495 }
496
497
498 string
499 dcp::status_to_string (Status s)
500 {
501         switch (s) {
502         case Status::FINAL:
503                 return "final";
504         case Status::TEMP:
505                 return "temp";
506         case Status::PRE:
507                 return "pre";
508         default:
509                 DCP_ASSERT (false);
510         }
511 }
512
513
514 Status
515 dcp::string_to_status (string s)
516 {
517         if (s == "final") {
518                 return Status::FINAL;
519         } else if (s == "temp") {
520                 return Status::TEMP;
521         } else if (s == "pre") {
522                 return Status::PRE;
523         }
524
525         DCP_ASSERT (false);
526 }
527
528
529 Channel
530 dcp::mca_id_to_channel (string id)
531 {
532         transform(id.begin(), id.end(), id.begin(), ::tolower);
533
534         if (id == "l") {
535                 return Channel::LEFT;
536         } else if (id == "r") {
537                 return Channel::RIGHT;
538         } else if (id == "c") {
539                 return Channel::CENTRE;
540         } else if (id == "lfe") {
541                 return Channel::LFE;
542         } else if (id == "ls" || id == "lss") {
543                 return Channel::LS;
544         } else if (id == "rs" || id == "rss") {
545                 return Channel::RS;
546         } else if (id == "hi") {
547                 return Channel::HI;
548         } else if (id == "vin") {
549                 return Channel::VI;
550         } else if (id == "lrs") {
551                 return Channel::BSL;
552         } else if (id == "rrs") {
553                 return Channel::BSR;
554         } else if (id == "dbox") {
555                 return Channel::MOTION_DATA;
556         } else if (id == "sync" || id == "fsksync") {
557                 return Channel::SYNC_SIGNAL;
558         } else if (id == "slvs") {
559                 return Channel::SIGN_LANGUAGE;
560         }
561
562         throw UnknownChannelIdError (id);
563 }
564
565
566 string
567 dcp::channel_to_mca_id (Channel c, MCASoundField field)
568 {
569         switch (c) {
570         case Channel::LEFT:
571                 return "L";
572         case Channel::RIGHT:
573                 return "R";
574         case Channel::CENTRE:
575                 return "C";
576         case Channel::LFE:
577                 return "LFE";
578         case Channel::LS:
579                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
580         case Channel::RS:
581                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
582         case Channel::HI:
583                 return "HI";
584         case Channel::VI:
585                 return "VIN";
586         case Channel::BSL:
587                 return "Lrs";
588         case Channel::BSR:
589                 return "Rrs";
590         case Channel::MOTION_DATA:
591                 return "DBOX";
592         case Channel::SYNC_SIGNAL:
593                 return "FSKSync";
594         case Channel::SIGN_LANGUAGE:
595                 return "SLVS";
596         default:
597                 break;
598         }
599
600         DCP_ASSERT (false);
601 }
602
603
604 string
605 dcp::channel_to_mca_name (Channel c, MCASoundField field)
606 {
607         switch (c) {
608         case Channel::LEFT:
609                 return "Left";
610         case Channel::RIGHT:
611                 return "Right";
612         case Channel::CENTRE:
613                 return "Center";
614         case Channel::LFE:
615                 return "LFE";
616         case Channel::LS:
617                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
618         case Channel::RS:
619                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
620         case Channel::HI:
621                 return "Hearing Impaired";
622         case Channel::VI:
623                 return "Visually Impaired-Narrative";
624         case Channel::BSL:
625                 return "Left Rear Surround";
626         case Channel::BSR:
627                 return "Right Rear Surround";
628         case Channel::MOTION_DATA:
629                 return "D-BOX Motion Code Primary Stream";
630         case Channel::SYNC_SIGNAL:
631                 return "FSK Sync";
632         case Channel::SIGN_LANGUAGE:
633                 return "Sign Language Video Stream";
634         default:
635                 break;
636         }
637
638         DCP_ASSERT (false);
639 }
640
641
642 ASDCP::UL
643 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
644 {
645         static byte_t sync_signal[] = {
646                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
647         };
648
649         static byte_t sign_language[] = {
650                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
651         };
652
653         switch (c) {
654         case Channel::LEFT:
655                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
656         case Channel::RIGHT:
657                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
658         case Channel::CENTRE:
659                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
660         case Channel::LFE:
661                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
662         case Channel::LS:
663                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
664         case Channel::RS:
665                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
666         case Channel::HI:
667                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
668         case Channel::VI:
669                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
670         case Channel::BSL:
671                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
672         case Channel::BSR:
673                 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
674         case Channel::MOTION_DATA:
675                 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
676         case Channel::SYNC_SIGNAL:
677                 return ASDCP::UL(sync_signal);
678         case Channel::SIGN_LANGUAGE:
679                 return ASDCP::UL(sign_language);
680         default:
681                 break;
682         }
683
684         DCP_ASSERT (false);
685 }
686
687
688 vector<dcp::Channel>
689 dcp::used_audio_channels ()
690 {
691         return {
692                 Channel::LEFT,
693                 Channel::RIGHT,
694                 Channel::CENTRE,
695                 Channel::LFE,
696                 Channel::LS,
697                 Channel::RS,
698                 Channel::HI,
699                 Channel::VI,
700                 Channel::BSL,
701                 Channel::BSR,
702                 Channel::MOTION_DATA,
703                 Channel::SYNC_SIGNAL,
704                 Channel::SIGN_LANGUAGE
705         };
706 }
707
708
709 string
710 dcp::formulation_to_string (dcp::Formulation formulation)
711 {
712         switch (formulation) {
713         case Formulation::MODIFIED_TRANSITIONAL_1:
714                 return "modified-transitional-1";
715         case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
716                 return "multiple-modified-transitional-1";
717         case Formulation::DCI_ANY:
718                 return "dci-any";
719         case Formulation::DCI_SPECIFIC:
720                 return "dci-specific";
721         }
722
723         DCP_ASSERT (false);
724 }
725
726
727 dcp::Formulation
728 dcp::string_to_formulation (string formulation)
729 {
730         if (formulation == "modified-transitional-1") {
731                 return Formulation::MODIFIED_TRANSITIONAL_1;
732         } else if (formulation == "multiple-modified-transitional-1") {
733                 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
734         } else if (formulation == "dci-any") {
735                 return Formulation::DCI_ANY;
736         } else if (formulation == "dci-specific") {
737                 return Formulation::DCI_SPECIFIC;
738         }
739
740         DCP_ASSERT (false);
741 }
742