libdcp
types.cc
Go to the documentation of this file.
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 
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 
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
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
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  case Marker::FFOB:
253  return "FFOB";
254  case Marker::LFOB:
255  return "LFOB";
256  }
257 
258  DCP_ASSERT (false);
259 }
260 
261 
263 dcp::marker_from_string (string s)
264 {
265  if (s == "FFOC") {
266  return Marker::FFOC;
267  } else if (s == "LFOC") {
268  return Marker::LFOC;
269  } else if (s == "FFTC") {
270  return Marker::FFTC;
271  } else if (s == "LFTC") {
272  return Marker::LFTC;
273  } else if (s == "FFOI") {
274  return Marker::FFOI;
275  } else if (s == "LFOI") {
276  return Marker::LFOI;
277  } else if (s == "FFEC") {
278  return Marker::FFEC;
279  } else if (s == "LFEC") {
280  return Marker::LFEC;
281  } else if (s == "FFMC") {
282  return Marker::FFMC;
283  } else if (s == "LFMC") {
284  return Marker::LFMC;
285  } else if (s == "FFOB") {
286  return Marker::FFOB;
287  } else if (s == "LFOB") {
288  return Marker::LFOB;
289  }
290 
291  DCP_ASSERT (false);
292 }
293 
294 
295 ContentVersion::ContentVersion ()
296  : id ("urn:uuid:" + make_uuid())
297 {
298 
299 }
300 
301 
302 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
303  : id(node->string_child("Id"))
304  , label_text(node->string_child("LabelText"))
305 {
306 
307 }
308 
309 
310 ContentVersion::ContentVersion (string label_text_)
311  : id ("urn:uuid:" + make_uuid())
312  , label_text (label_text_)
313 {
314 
315 }
316 
317 
318 void
319 ContentVersion::as_xml (xmlpp::Element* parent) const
320 {
321  auto cv = cxml::add_child(parent, "ContentVersion");
322  cxml::add_text_child(cv, "Id", id);
323  cxml::add_text_child(cv, "LabelText", label_text);
324 }
325 
326 
327 Luminance::Luminance (cxml::ConstNodePtr node)
328  : _value(raw_convert<float>(node->content()))
329  , _unit(string_to_unit(node->string_attribute("units")))
330 {
331 
332 }
333 
334 
335 Luminance::Luminance (float value, Unit unit)
336  : _unit (unit)
337 {
338  set_value (value);
339 }
340 
341 
342 void
343 Luminance::set_value (float v)
344 {
345  if (v < 0) {
346  throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
347  }
348 
349  _value = v;
350 }
351 
352 
353 void
354 Luminance::as_xml (xmlpp::Element* parent, string ns) const
355 {
356  auto lum = cxml::add_child(parent, "Luminance", ns);
357  lum->set_attribute("units", unit_to_string(_unit));
358  lum->add_child_text(raw_convert<string>(_value, 3));
359 }
360 
361 
362 string
363 Luminance::unit_to_string (Unit u)
364 {
365  switch (u) {
366  case Unit::CANDELA_PER_SQUARE_METRE:
367  return "candela-per-square-metre";
368  case Unit::FOOT_LAMBERT:
369  return "foot-lambert";
370  default:
371  DCP_ASSERT (false);
372  }
373 
374  return {};
375 }
376 
377 
378 Luminance::Unit
379 Luminance::string_to_unit (string u)
380 {
381  if (u == "candela-per-square-metre") {
382  return Unit::CANDELA_PER_SQUARE_METRE;
383  } else if (u == "foot-lambert") {
384  return Unit::FOOT_LAMBERT;
385  }
386 
387  throw XMLError (String::compose("Invalid luminance unit %1", u));
388 }
389 
390 
391 float
392 Luminance::value_in_foot_lamberts () const
393 {
394  switch (_unit) {
395  case Unit::CANDELA_PER_SQUARE_METRE:
396  return _value / 3.426;
397  case Unit::FOOT_LAMBERT:
398  return _value;
399  default:
400  DCP_ASSERT (false);
401  }
402 }
403 
404 
405 bool
406 dcp::operator== (Luminance const& a, Luminance const& b)
407 {
408  return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
409 }
410 
411 
412 MainSoundConfiguration::MainSoundConfiguration (string s)
413 {
414  vector<string> parts;
415  boost::split (parts, s, boost::is_any_of("/"));
416  if (parts.empty()) {
418  }
419 
420  if (parts[0] == "51") {
421  _field = MCASoundField::FIVE_POINT_ONE;
422  } else if (parts[0] == "71") {
423  _field = MCASoundField::SEVEN_POINT_ONE;
424  } else {
425  _field = MCASoundField::OTHER;
426  }
427 
428  if (parts.size() < 2) {
429  /* I think it's OK to just have the sound field descriptor with no channels, though
430  * to me it's not clear and I might be wrong.
431  */
432  return;
433  }
434 
435  vector<string> channels;
436  boost::split (channels, parts[1], boost::is_any_of(","));
437 
438  if (channels.size() > 16) {
439  throw MainSoundConfigurationError (s);
440  }
441 
442  for (auto i: channels) {
443  if (i == "-") {
444  _channels.push_back(optional<Channel>());
445  } else {
446  _channels.push_back(mca_id_to_channel(i));
447  }
448  }
449 }
450 
451 
452 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
453  : _field (field)
454 {
455  _channels.resize (channels);
456 }
457 
458 
459 string
460 MainSoundConfiguration::to_string () const
461 {
462  string c;
463  switch (_field) {
464  case MCASoundField::FIVE_POINT_ONE:
465  c = "51/";
466  break;
467  case MCASoundField::SEVEN_POINT_ONE:
468  c = "71/";
469  break;
470  default:
471  DCP_ASSERT(false);
472  }
473 
474  for (auto i: _channels) {
475  if (!i) {
476  c += "-,";
477  } else {
478  c += channel_to_mca_id(*i, _field) + ",";
479  }
480  }
481 
482  if (c.length() > 0) {
483  c = c.substr(0, c.length() - 1);
484  }
485 
486  return c;
487 }
488 
489 
490 optional<Channel>
491 MainSoundConfiguration::mapping (int index) const
492 {
493  DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
494  return _channels[index];
495 }
496 
497 
498 void
499 MainSoundConfiguration::set_mapping (int index, Channel c)
500 {
501  DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
502  _channels[index] = c;
503 }
504 
505 
506 string
507 dcp::status_to_string (Status s)
508 {
509  switch (s) {
510  case Status::FINAL:
511  return "final";
512  case Status::TEMP:
513  return "temp";
514  case Status::PRE:
515  return "pre";
516  default:
517  DCP_ASSERT (false);
518  }
519 }
520 
521 
522 Status
523 dcp::string_to_status (string s)
524 {
525  if (s == "final") {
526  return Status::FINAL;
527  } else if (s == "temp") {
528  return Status::TEMP;
529  } else if (s == "pre") {
530  return Status::PRE;
531  }
532 
533  DCP_ASSERT (false);
534 }
535 
536 
537 Channel
538 dcp::mca_id_to_channel (string id)
539 {
540  transform(id.begin(), id.end(), id.begin(), ::tolower);
541 
542  if (id == "l") {
543  return Channel::LEFT;
544  } else if (id == "r") {
545  return Channel::RIGHT;
546  } else if (id == "c") {
547  return Channel::CENTRE;
548  } else if (id == "lfe") {
549  return Channel::LFE;
550  } else if (id == "ls" || id == "lss" || id == "lslss") {
551  return Channel::LS;
552  } else if (id == "rs" || id == "rss" || id == "rsrss") {
553  return Channel::RS;
554  } else if (id == "hi") {
555  return Channel::HI;
556  } else if (id == "vin" || id == "vi-n") {
557  return Channel::VI;
558  } else if (id == "lc") {
559  return Channel::LC;
560  } else if (id == "rc") {
561  return Channel::RC;
562  } else if (id == "lrs" || id == "lsr") {
563  return Channel::BSL;
564  } else if (id == "rrs" || id == "rsr") {
565  return Channel::BSR;
566  } else if (id == "dbox" || id == "dbox2" || id == "mtn") {
567  return Channel::MOTION_DATA;
568  } else if (id == "sync" || id == "fsksync") {
569  return Channel::SYNC_SIGNAL;
570  } else if (id == "slvs") {
571  return Channel::SIGN_LANGUAGE;
572  }
573 
574  throw UnknownChannelIdError (id);
575 }
576 
577 
578 string
579 dcp::channel_to_mca_id (Channel c, MCASoundField field)
580 {
581  switch (c) {
582  case Channel::LEFT:
583  return "L";
584  case Channel::RIGHT:
585  return "R";
586  case Channel::CENTRE:
587  return "C";
588  case Channel::LFE:
589  return "LFE";
590  case Channel::LS:
591  return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
592  case Channel::RS:
593  return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
594  case Channel::HI:
595  return "HI";
596  case Channel::VI:
597  return "VIN";
598  case Channel::BSL:
599  return "Lrs";
600  case Channel::BSR:
601  return "Rrs";
602  case Channel::MOTION_DATA:
603  return "DBOX";
604  case Channel::SYNC_SIGNAL:
605  return "FSKSync";
606  case Channel::SIGN_LANGUAGE:
607  return "SLVS";
608  default:
609  break;
610  }
611 
612  DCP_ASSERT (false);
613 }
614 
615 
616 string
617 dcp::channel_to_mca_name (Channel c, MCASoundField field)
618 {
619  switch (c) {
620  case Channel::LEFT:
621  return "Left";
622  case Channel::RIGHT:
623  return "Right";
624  case Channel::CENTRE:
625  return "Center";
626  case Channel::LFE:
627  return "LFE";
628  case Channel::LS:
629  return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
630  case Channel::RS:
631  return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
632  case Channel::HI:
633  return "Hearing Impaired";
634  case Channel::VI:
635  return "Visually Impaired-Narrative";
636  case Channel::BSL:
637  return "Left Rear Surround";
638  case Channel::BSR:
639  return "Right Rear Surround";
640  case Channel::MOTION_DATA:
641  return "D-BOX Motion Code Primary Stream";
642  case Channel::SYNC_SIGNAL:
643  return "FSK Sync";
644  case Channel::SIGN_LANGUAGE:
645  return "Sign Language Video Stream";
646  default:
647  break;
648  }
649 
650  DCP_ASSERT (false);
651 }
652 
653 
654 ASDCP::UL
655 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
656 {
657  static byte_t sync_signal[] = {
658  0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
659  };
660 
661  static byte_t sign_language[] = {
662  0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
663  };
664 
665  switch (c) {
666  case Channel::LEFT:
667  return dict->ul(ASDCP::MDD_DCAudioChannel_L);
668  case Channel::RIGHT:
669  return dict->ul(ASDCP::MDD_DCAudioChannel_R);
670  case Channel::CENTRE:
671  return dict->ul(ASDCP::MDD_DCAudioChannel_C);
672  case Channel::LFE:
673  return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
674  case Channel::LS:
675  return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
676  case Channel::RS:
677  return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
678  case Channel::HI:
679  return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
680  case Channel::VI:
681  return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
682  case Channel::BSL:
683  return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
684  case Channel::BSR:
685  return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
686  case Channel::MOTION_DATA:
687  return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
688  case Channel::SYNC_SIGNAL:
689  return ASDCP::UL(sync_signal);
690  case Channel::SIGN_LANGUAGE:
691  return ASDCP::UL(sign_language);
692  default:
693  break;
694  }
695 
696  DCP_ASSERT (false);
697 }
698 
699 
700 vector<dcp::Channel>
701 dcp::used_audio_channels ()
702 {
703  return {
707  Channel::LFE,
708  Channel::LS,
709  Channel::RS,
710  Channel::HI,
711  Channel::VI,
712  Channel::BSL,
713  Channel::BSR,
714  Channel::MOTION_DATA,
715  Channel::SYNC_SIGNAL,
716  Channel::SIGN_LANGUAGE
717  };
718 }
719 
720 
721 string
722 dcp::formulation_to_string (dcp::Formulation formulation)
723 {
724  switch (formulation) {
725  case Formulation::MODIFIED_TRANSITIONAL_1:
726  return "modified-transitional-1";
727  case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
728  return "multiple-modified-transitional-1";
729  case Formulation::DCI_ANY:
730  return "dci-any";
731  case Formulation::DCI_SPECIFIC:
732  return "dci-specific";
733  }
734 
735  DCP_ASSERT (false);
736 }
737 
738 
739 dcp::Formulation
740 dcp::string_to_formulation (string formulation)
741 {
742  if (formulation == "modified-transitional-1") {
743  return Formulation::MODIFIED_TRANSITIONAL_1;
744  } else if (formulation == "multiple-modified-transitional-1") {
745  return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
746  } else if (formulation == "dci-any") {
747  return Formulation::DCI_ANY;
748  } else if (formulation == "dci-specific") {
749  return Formulation::DCI_SPECIFIC;
750  }
751 
752  DCP_ASSERT (false);
753 }
754 
An RGB colour.
Definition: types.h:224
int g
green component, from 0 to 255
Definition: types.h:241
Colour()=default
int b
blue component, from 0 to 255
Definition: types.h:242
int r
red component, from 0 to 255
Definition: types.h:240
std::string to_argb_string() const
Definition: types.cc:131
std::string to_rgb_string() const
Definition: types.cc:140
A fraction (i.e. a thing with an integer numerator and an integer denominator).
Definition: types.h:168
Fraction()=default
A miscellaneous exception.
Definition: exceptions.h:94
Any error that occurs when reading data from a DCP.
Definition: exceptions.h:106
An XML error.
Definition: exceptions.h:191
DCP_ASSERT macro.
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
Definition: array_data.h:50
Marker
Definition: types.h:286
@ FFOB
first frame of ratings band
@ FFOI
first frame of intermission
@ FFEC
first frame of end credits
@ LFMC
last frame of moving credits
@ LFOI
last frame of intermission
@ FFMC
first frame of moving credits
@ FFTC
first frame of title credits
@ LFOC
last frame of composition
@ FFOC
first frame of composition
@ LFOB
last frame of ratings band
@ LFEC
last frame of end credits
@ LFTC
last frame of title credits
Direction
Definition: types.h:145
@ LTR
left-to-right
@ BTT
bottom-to-top
@ RTL
right-to-left
@ TTB
top-to-bottom
Channel
Definition: types.h:93
@ LFE
low-frequency effects (sub)
@ LC
not used, but referred to in MainSoundConfiguration in some CPLs
@ RC
not used, but referred to in MainSoundConfiguration in some CPLs
@ CENTRE
centre
@ RS
right surround
@ LS
left surround
P raw_convert(Q, int precision=16, bool fixed=false)
Definition: raw_convert.h:57
Status
Definition: types.h:307
@ FINAL
final version
@ TEMP
temporary version (picture/sound unfinished)
@ PRE
pre-release (picture/sound finished)
Methods for conversion to/from string.
The integer, two-dimensional size of something.
Definition: types.h:71
Miscellaneous types.