Remove all use of add_child() from xmlpp.
[dcpomatic.git] / src / lib / dcp_content.cc
1 /*
2     Copyright (C) 2014-2023 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic 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     DCP-o-matic 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 DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "atmos_content.h"
23 #include "audio_content.h"
24 #include "compose.hpp"
25 #include "config.h"
26 #include "dcp_content.h"
27 #include "dcp_decoder.h"
28 #include "dcp_examiner.h"
29 #include "dcpomatic_log.h"
30 #include "film.h"
31 #include "job.h"
32 #include "log.h"
33 #include "overlaps.h"
34 #include "text_content.h"
35 #include "video_content.h"
36 #include <dcp/dcp.h>
37 #include <dcp/raw_convert.h>
38 #include <dcp/exceptions.h>
39 #include <dcp/reel_closed_caption_asset.h>
40 #include <dcp/reel_picture_asset.h>
41 #include <dcp/reel_subtitle_asset.h>
42 #include <dcp/reel.h>
43 #include <dcp/scope_guard.h>
44 #include <libxml++/libxml++.h>
45 #include <iterator>
46 #include <iostream>
47
48 #include "i18n.h"
49
50
51 using std::cout;
52 using std::dynamic_pointer_cast;
53 using std::exception;
54 using std::function;
55 using std::list;
56 using std::make_shared;
57 using std::shared_ptr;
58 using std::string;
59 using std::vector;
60 using boost::optional;
61 using boost::scoped_ptr;
62 #if BOOST_VERSION >= 106100
63 using namespace boost::placeholders;
64 #endif
65 using dcp::raw_convert;
66 using namespace dcpomatic;
67
68
69 int const DCPContentProperty::NEEDS_ASSETS       = 600;
70 int const DCPContentProperty::NEEDS_KDM          = 601;
71 int const DCPContentProperty::REFERENCE_VIDEO    = 602;
72 int const DCPContentProperty::REFERENCE_AUDIO    = 603;
73 int const DCPContentProperty::REFERENCE_TEXT     = 604;
74 int const DCPContentProperty::NAME               = 605;
75 int const DCPContentProperty::TEXTS              = 606;
76 int const DCPContentProperty::CPL                = 607;
77
78
79 DCPContent::DCPContent (boost::filesystem::path p)
80         : _encrypted (false)
81         , _needs_assets (false)
82         , _kdm_valid (false)
83         , _reference_video (false)
84         , _reference_audio (false)
85         , _three_d (false)
86 {
87         LOG_GENERAL ("Creating DCP content from %1", p.string());
88
89         read_directory (p);
90         set_default_colour_conversion ();
91 }
92
93 DCPContent::DCPContent (cxml::ConstNodePtr node, int version)
94         : Content (node)
95 {
96         video = VideoContent::from_xml (this, node, version, VideoRange::FULL);
97         audio = AudioContent::from_xml (this, node, version);
98         list<string> notes;
99         text = TextContent::from_xml (this, node, version, notes);
100         atmos = AtmosContent::from_xml (this, node);
101
102         if (video && audio) {
103                 audio->set_stream (
104                         make_shared<AudioStream> (
105                                 node->number_child<int> ("AudioFrameRate"),
106                                 /* AudioLength was not present in some old metadata versions */
107                                 node->optional_number_child<Frame>("AudioLength").get_value_or (
108                                         video->length() * node->number_child<int>("AudioFrameRate") / video_frame_rate().get()
109                                         ),
110                                 AudioMapping(node->node_child("AudioMapping"), version),
111                                 24
112                                 )
113                         );
114         }
115
116         _name = node->string_child ("Name");
117         _encrypted = node->bool_child ("Encrypted");
118         _needs_assets = node->optional_bool_child("NeedsAssets").get_value_or (false);
119         if (node->optional_node_child ("KDM")) {
120                 _kdm = dcp::EncryptedKDM (node->string_child ("KDM"));
121         }
122         _kdm_valid = node->bool_child ("KDMValid");
123         _reference_video = node->optional_bool_child ("ReferenceVideo").get_value_or (false);
124         _reference_audio = node->optional_bool_child ("ReferenceAudio").get_value_or (false);
125         if (version >= 37) {
126                 _reference_text[TextType::OPEN_SUBTITLE] = node->optional_bool_child("ReferenceOpenSubtitle").get_value_or(false);
127                 _reference_text[TextType::CLOSED_CAPTION] = node->optional_bool_child("ReferenceClosedCaption").get_value_or(false);
128         } else {
129                 _reference_text[TextType::OPEN_SUBTITLE] = node->optional_bool_child("ReferenceSubtitle").get_value_or(false);
130                 _reference_text[TextType::CLOSED_CAPTION] = false;
131         }
132         if (node->optional_string_child("Standard")) {
133                 auto const s = node->optional_string_child("Standard").get();
134                 if (s == "Interop") {
135                         _standard = dcp::Standard::INTEROP;
136                 } else if (s == "SMPTE") {
137                         _standard = dcp::Standard::SMPTE;
138                 } else {
139                         DCPOMATIC_ASSERT (false);
140                 }
141         }
142         _three_d = node->optional_bool_child("ThreeD").get_value_or (false);
143
144         auto ck = node->optional_string_child("ContentKind");
145         if (ck) {
146                 _content_kind = dcp::ContentKind::from_name(*ck);
147         }
148         _cpl = node->optional_string_child("CPL");
149         for (auto i: node->node_children("ReelLength")) {
150                 _reel_lengths.push_back (raw_convert<int64_t> (i->content ()));
151         }
152
153         for (auto i: node->node_children("Marker")) {
154                 _markers[dcp::marker_from_string(i->string_attribute("type"))] = ContentTime(raw_convert<int64_t>(i->content()));
155         }
156
157         for (auto i: node->node_children("Rating")) {
158                 _ratings.push_back (dcp::Rating(i));
159         }
160
161         for (auto i: node->node_children("ContentVersion")) {
162                 _content_versions.push_back (i->content());
163         }
164
165         _active_audio_channels = node->optional_number_child<int>("ActiveAudioChannels");
166
167         for (auto non_zero: node->node_children("HasNonZeroEntryPoint")) {
168                 try {
169                         auto type = string_to_text_type(non_zero->string_attribute("type"));
170                         _has_non_zero_entry_point[type] = non_zero->content() == "1";
171                 } catch (MetadataError&) {}
172         }
173 }
174
175 void
176 DCPContent::read_directory (boost::filesystem::path p)
177 {
178         using namespace boost::filesystem;
179
180         bool have_assetmap = false;
181         bool have_metadata = false;
182
183         for (auto i: directory_iterator(p)) {
184                 if (i.path().filename() == "ASSETMAP" || i.path().filename() == "ASSETMAP.xml") {
185                         have_assetmap = true;
186                 } else if (i.path().filename() == "metadata.xml") {
187                         have_metadata = true;
188                 }
189         }
190
191         if (!have_assetmap) {
192                 if (!have_metadata) {
193                         throw DCPError ("No ASSETMAP or ASSETMAP.xml file found: is this a DCP?");
194                 } else {
195                         throw ProjectFolderError ();
196                 }
197         }
198
199         read_sub_directory (p);
200 }
201
202 void
203 DCPContent::read_sub_directory (boost::filesystem::path p)
204 {
205         using namespace boost::filesystem;
206
207         LOG_GENERAL ("DCPContent::read_sub_directory reads %1", p.string());
208         try {
209                 for (auto i: directory_iterator(p)) {
210                         if (is_regular_file(i.path())) {
211                                 LOG_GENERAL ("Inside there's regular file %1", i.path().string());
212                                 add_path (i.path());
213                         } else if (is_directory(i.path()) && i.path().filename() != ".AppleDouble") {
214                                 LOG_GENERAL ("Inside there's directory %1", i.path().string());
215                                 read_sub_directory (i.path());
216                         } else {
217                                 LOG_GENERAL("Ignoring %1 from inside: status is %2", i.path().string(), static_cast<int>(status(i.path()).type()));
218                         }
219                 }
220         } catch (exception& e) {
221                 LOG_GENERAL("Failed to iterate over %1: %2", p.string(), e.what());
222         }
223 }
224
225 /** @param film Film, or 0 */
226 void
227 DCPContent::examine (shared_ptr<const Film> film, shared_ptr<Job> job)
228 {
229         bool const needed_assets = needs_assets ();
230         bool const needed_kdm = needs_kdm ();
231         string const old_name = name ();
232
233         ContentChangeSignalDespatcher::instance()->suspend();
234         dcp::ScopeGuard sg = []() {
235                 ContentChangeSignalDespatcher::instance()->resume();
236         };
237
238         ContentChangeSignaller cc_texts (this, DCPContentProperty::TEXTS);
239         ContentChangeSignaller cc_assets (this, DCPContentProperty::NEEDS_ASSETS);
240         ContentChangeSignaller cc_kdm (this, DCPContentProperty::NEEDS_KDM);
241         ContentChangeSignaller cc_name (this, DCPContentProperty::NAME);
242
243         if (job) {
244                 job->set_progress_unknown ();
245         }
246         Content::examine (film, job);
247
248         auto examiner = make_shared<DCPExaminer>(shared_from_this(), film ? film->tolerant() : true);
249
250         if (examiner->has_video()) {
251                 {
252                         boost::mutex::scoped_lock lm (_mutex);
253                         video = make_shared<VideoContent>(this);
254                 }
255                 video->take_from_examiner(film, examiner);
256                 set_default_colour_conversion ();
257         }
258
259         if (examiner->has_audio()) {
260                 {
261                         boost::mutex::scoped_lock lm (_mutex);
262                         audio = make_shared<AudioContent>(this);
263                 }
264                 auto as = make_shared<AudioStream>(examiner->audio_frame_rate(), examiner->audio_length(), examiner->audio_channels(), 24);
265                 audio->set_stream (as);
266                 auto m = as->mapping ();
267                 m.make_default (film ? film->audio_processor() : 0);
268                 as->set_mapping (m);
269
270                 _active_audio_channels = examiner->active_audio_channels();
271         }
272
273         if (examiner->has_atmos()) {
274                 {
275                         boost::mutex::scoped_lock lm (_mutex);
276                         atmos = make_shared<AtmosContent>(this);
277                 }
278                 /* Setting length will cause calculations to be made based on edit rate, so that must
279                  * be set up first otherwise hard-to-spot exceptions will be thrown.
280                  */
281                 atmos->set_edit_rate (examiner->atmos_edit_rate());
282                 atmos->set_length (examiner->atmos_length());
283         }
284
285         vector<shared_ptr<TextContent>> new_text;
286
287         for (int i = 0; i < examiner->text_count(TextType::OPEN_SUBTITLE); ++i) {
288                 auto c = make_shared<TextContent>(this, TextType::OPEN_SUBTITLE, TextType::OPEN_SUBTITLE);
289                 c->set_language (examiner->open_subtitle_language());
290                 examiner->add_fonts(c);
291                 new_text.push_back (c);
292         }
293
294         for (int i = 0; i < examiner->text_count(TextType::CLOSED_CAPTION); ++i) {
295                 auto c = make_shared<TextContent>(this, TextType::CLOSED_CAPTION, TextType::CLOSED_CAPTION);
296                 c->set_dcp_track (examiner->dcp_text_track(i));
297                 examiner->add_fonts(c);
298                 new_text.push_back (c);
299         }
300
301         {
302                 boost::mutex::scoped_lock lm (_mutex);
303                 text = new_text;
304                 _name = examiner->name ();
305                 _encrypted = examiner->encrypted ();
306                 _needs_assets = examiner->needs_assets ();
307                 _kdm_valid = examiner->kdm_valid ();
308                 _standard = examiner->standard ();
309                 _three_d = examiner->three_d ();
310                 _content_kind = examiner->content_kind ();
311                 _cpl = examiner->cpl ();
312                 _reel_lengths = examiner->reel_lengths ();
313                 for (auto const& i: examiner->markers()) {
314                         _markers[i.first] = ContentTime(i.second.as_editable_units_ceil(DCPTime::HZ));
315                 }
316                 _ratings = examiner->ratings ();
317                 _content_versions = examiner->content_versions ();
318                 _has_non_zero_entry_point = examiner->has_non_zero_entry_point();
319         }
320
321         if (needed_assets == needs_assets()) {
322                 cc_assets.abort ();
323         }
324
325         if (needed_kdm == needs_kdm()) {
326                 cc_kdm.abort ();
327         }
328
329         if (old_name == name()) {
330                 cc_name.abort ();
331         }
332
333         if (video) {
334                 video->set_frame_type (_three_d ? VideoFrameType::THREE_D : VideoFrameType::TWO_D);
335         }
336 }
337
338 string
339 DCPContent::summary () const
340 {
341         boost::mutex::scoped_lock lm (_mutex);
342         return String::compose (_("%1 [DCP]"), _name);
343 }
344
345 string
346 DCPContent::technical_summary () const
347 {
348         string s = Content::technical_summary() + " - ";
349         if (video) {
350                 s += video->technical_summary() + " - ";
351         }
352         if (audio) {
353                 s += audio->technical_summary() + " - ";
354         }
355         return s;
356 }
357
358
359 void
360 DCPContent::as_xml(xmlpp::Element* element, bool with_paths) const
361 {
362         cxml::add_text_child(element, "Type", "DCP");
363
364         Content::as_xml(element, with_paths);
365
366         if (video) {
367                 video->as_xml(element);
368         }
369
370         if (audio) {
371                 audio->as_xml(element);
372                 cxml::add_text_child(element, "AudioFrameRate", raw_convert<string>(audio->stream()->frame_rate()));
373                 cxml::add_text_child(element, "AudioLength", raw_convert<string>(audio->stream()->length()));
374                 audio->stream()->mapping().as_xml(cxml::add_child(element, "AudioMapping"));
375         }
376
377         for (auto i: text) {
378                 i->as_xml(element);
379         }
380
381         if (atmos) {
382                 atmos->as_xml(element);
383         }
384
385         boost::mutex::scoped_lock lm (_mutex);
386
387         cxml::add_text_child(element, "Name", _name);
388         cxml::add_text_child(element, "Encrypted", _encrypted ? "1" : "0");
389         cxml::add_text_child(element, "NeedsAssets", _needs_assets ? "1" : "0");
390         if (_kdm) {
391                 cxml::add_text_child(element, "KDM", _kdm->as_xml());
392         }
393         cxml::add_text_child(element, "KDMValid", _kdm_valid ? "1" : "0");
394         cxml::add_text_child(element, "ReferenceVideo", _reference_video ? "1" : "0");
395         cxml::add_text_child(element, "ReferenceAudio", _reference_audio ? "1" : "0");
396         cxml::add_text_child(element, "ReferenceOpenSubtitle", _reference_text[TextType::OPEN_SUBTITLE] ? "1" : "0");
397         cxml::add_text_child(element, "ReferenceClosedCaption", _reference_text[TextType::CLOSED_CAPTION] ? "1" : "0");
398         if (_standard) {
399                 switch (_standard.get ()) {
400                 case dcp::Standard::INTEROP:
401                         cxml::add_text_child(element, "Standard", "Interop");
402                         break;
403                 case dcp::Standard::SMPTE:
404                         cxml::add_text_child(element, "Standard", "SMPTE");
405                         break;
406                 default:
407                         DCPOMATIC_ASSERT (false);
408                 }
409         }
410         cxml::add_text_child(element, "ThreeD", _three_d ? "1" : "0");
411         if (_content_kind) {
412                 cxml::add_text_child(element, "ContentKind", _content_kind->name());
413         }
414         if (_cpl) {
415                 cxml::add_text_child(element, "CPL", _cpl.get());
416         }
417         for (auto i: _reel_lengths) {
418                 cxml::add_text_child(element, "ReelLength", raw_convert<string>(i));
419         }
420
421         for (auto const& i: _markers) {
422                 auto marker = cxml::add_child(element, "Marker");
423                 marker->set_attribute("type", dcp::marker_to_string(i.first));
424                 marker->add_child_text(raw_convert<string>(i.second.get()));
425         }
426
427         for (auto i: _ratings) {
428                 auto rating = cxml::add_child(element, "Rating");
429                 i.as_xml (rating);
430         }
431
432         for (auto i: _content_versions) {
433                 cxml::add_text_child(element, "ContentVersion", i);
434         }
435
436         if (_active_audio_channels) {
437                 cxml::add_text_child(element, "ActiveAudioChannels", raw_convert<string>(*_active_audio_channels));
438         }
439
440         for (auto i = 0; i < static_cast<int>(TextType::COUNT); ++i) {
441                 if (_has_non_zero_entry_point[i]) {
442                         auto has = cxml::add_child(element, "HasNonZeroEntryPoint");
443                         has->add_child_text("1");
444                         has->set_attribute("type", text_type_to_string(static_cast<TextType>(i)));
445                 }
446         }
447 }
448
449
450 DCPTime
451 DCPContent::full_length (shared_ptr<const Film> film) const
452 {
453         if (!video) {
454                 return {};
455         }
456         FrameRateChange const frc (film, shared_from_this());
457         return DCPTime::from_frames (llrint(video->length() * frc.factor()), film->video_frame_rate());
458 }
459
460 DCPTime
461 DCPContent::approximate_length () const
462 {
463         if (!video) {
464                 return {};
465         }
466         return DCPTime::from_frames (video->length(), 24);
467 }
468
469 string
470 DCPContent::identifier () const
471 {
472         string s = Content::identifier() + "_";
473
474         if (video) {
475                 s += video->identifier() + "_";
476         }
477
478         for (auto i: text) {
479                 s += i->identifier () + " ";
480         }
481
482         boost::mutex::scoped_lock lm(_mutex);
483
484         s += string (_reference_video ? "1" : "0");
485         for (auto text: _reference_text) {
486                 s += string(text ? "1" : "0");
487         }
488         return s;
489 }
490
491 void
492 DCPContent::add_kdm (dcp::EncryptedKDM k)
493 {
494         _kdm = k;
495 }
496
497 void
498 DCPContent::add_ov (boost::filesystem::path ov)
499 {
500         read_directory (ov);
501 }
502
503 bool
504 DCPContent::can_be_played () const
505 {
506         return !needs_kdm() && !needs_assets();
507 }
508
509 bool
510 DCPContent::needs_kdm () const
511 {
512         boost::mutex::scoped_lock lm (_mutex);
513         return _encrypted && !_kdm_valid;
514 }
515
516 bool
517 DCPContent::needs_assets () const
518 {
519         boost::mutex::scoped_lock lm (_mutex);
520         return _needs_assets;
521 }
522
523 vector<boost::filesystem::path>
524 DCPContent::directories () const
525 {
526         return dcp::DCP::directories_from_files (paths());
527 }
528
529 void
530 DCPContent::add_properties (shared_ptr<const Film> film, list<UserProperty>& p) const
531 {
532         Content::add_properties (film, p);
533         if (video) {
534                 video->add_properties (p);
535         }
536         if (audio) {
537                 audio->add_properties (film, p);
538         }
539 }
540
541 void
542 DCPContent::set_default_colour_conversion ()
543 {
544         /* Default to no colour conversion for DCPs */
545         if (video) {
546                 video->unset_colour_conversion ();
547         }
548 }
549
550 void
551 DCPContent::set_reference_video (bool r)
552 {
553         ContentChangeSignaller cc (this, DCPContentProperty::REFERENCE_VIDEO);
554
555         {
556                 boost::mutex::scoped_lock lm (_mutex);
557                 _reference_video = r;
558         }
559 }
560
561 void
562 DCPContent::set_reference_audio (bool r)
563 {
564         ContentChangeSignaller cc (this, DCPContentProperty::REFERENCE_AUDIO);
565
566         {
567                 boost::mutex::scoped_lock lm (_mutex);
568                 _reference_audio = r;
569         }
570 }
571
572 void
573 DCPContent::set_reference_text (TextType type, bool r)
574 {
575         ContentChangeSignaller cc (this, DCPContentProperty::REFERENCE_TEXT);
576
577         {
578                 boost::mutex::scoped_lock lm (_mutex);
579                 _reference_text[type] = r;
580         }
581 }
582
583 list<DCPTimePeriod>
584 DCPContent::reels (shared_ptr<const Film> film) const
585 {
586         auto reel_lengths = _reel_lengths;
587         if (reel_lengths.empty()) {
588                 /* Old metadata with no reel lengths; get them here instead */
589                 try {
590                         scoped_ptr<DCPExaminer> examiner (new DCPExaminer(shared_from_this(), film->tolerant()));
591                         reel_lengths = examiner->reel_lengths ();
592                 } catch (...) {
593                         /* Could not examine the DCP; guess reels */
594                         reel_lengths.push_back (length_after_trim(film).frames_round(film->video_frame_rate()));
595                 }
596         }
597
598         list<DCPTimePeriod> p;
599
600         /* This content's frame rate must be the same as the output DCP rate, so we can
601            convert `directly' from ContentTime to DCPTime.
602         */
603
604         /* The starting point of this content on the timeline */
605         auto pos = position() - DCPTime (trim_start().get());
606
607         for (auto i: reel_lengths) {
608                 /* This reel runs from `pos' to `to' */
609                 DCPTime const to = pos + DCPTime::from_frames (i, film->video_frame_rate());
610                 if (to > position()) {
611                         p.push_back (DCPTimePeriod(max(position(), pos), min(end(film), to)));
612                         if (to > end(film)) {
613                                 break;
614                         }
615                 }
616                 pos = to;
617         }
618
619         return p;
620 }
621
622 list<DCPTime>
623 DCPContent::reel_split_points (shared_ptr<const Film> film) const
624 {
625         list<DCPTime> s;
626         for (auto i: reels(film)) {
627                 s.push_back (i.from);
628         }
629         return s;
630 }
631
632 bool
633 DCPContent::can_reference_anything(shared_ptr<const Film> film, string& why_not) const
634 {
635         /* We must be using the same standard as the film */
636         if (_standard) {
637                 if (_standard.get() == dcp::Standard::INTEROP && !film->interop()) {
638                         /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
639                         why_not = _("it is Interop and the film is set to SMPTE.");
640                         return false;
641                 } else if (_standard.get() == dcp::Standard::SMPTE && film->interop()) {
642                         /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
643                         why_not = _("it is SMPTE and the film is set to Interop.");
644                         return false;
645                 }
646         }
647
648         /* And the same frame rate */
649         if (!video_frame_rate() || (lrint(video_frame_rate().get()) != film->video_frame_rate())) {
650                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
651                 why_not = _("it has a different frame rate to the film.");
652                 return false;
653         }
654
655         auto const fr = film->reels ();
656
657         list<DCPTimePeriod> reel_list;
658         try {
659                 reel_list = reels (film);
660         } catch (dcp::ReadError &) {
661                 /* We couldn't read the DCP; it's probably missing */
662                 return false;
663         } catch (dcp::KDMDecryptionError &) {
664                 /* We have an incorrect KDM */
665                 return false;
666         }
667
668         /* fr must contain reels().  It can also contain other reels, but it must at
669            least contain reels().
670         */
671         for (auto i: reel_list) {
672                 if (find (fr.begin(), fr.end(), i) == fr.end ()) {
673                         /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
674                         why_not = _("its reel lengths differ from those in the film; set the reel mode to 'split by video content'.");
675                         return false;
676                 }
677         }
678
679         return true;
680 }
681
682 bool
683 DCPContent::overlaps(shared_ptr<const Film> film, function<bool (shared_ptr<const Content>)> part) const
684 {
685         auto const a = dcpomatic::overlaps(film, film->content(), part, position(), end(film));
686         return a.size() != 1 || a.front().get() != this;
687 }
688
689
690 bool
691 DCPContent::can_reference_video (shared_ptr<const Film> film, string& why_not) const
692 {
693         if (!video) {
694                 why_not = _("There is no video in this DCP");
695                 return false;
696         }
697
698         if (film->resolution() != resolution()) {
699                 if (resolution() == Resolution::FOUR_K) {
700                         /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
701                         why_not = _("it is 4K and the film is 2K.");
702                 } else {
703                         /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
704                         why_not = _("it is 2K and the film is 4K.");
705                 }
706                 return false;
707         } else if (film->frame_size() != video->size()) {
708                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
709                 why_not = _("its video frame size differs from the film's.");
710                 return false;
711         }
712
713         auto part = [](shared_ptr<const Content> c) {
714                 return static_cast<bool>(c->video) && c->video->use();
715         };
716
717         if (overlaps(film, part)) {
718                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
719                 why_not = _("it overlaps other video content.");
720                 return false;
721         }
722
723         return can_reference_anything(film, why_not);
724 }
725
726
727 bool
728 DCPContent::can_reference_audio (shared_ptr<const Film> film, string& why_not) const
729 {
730         if (audio && audio->stream()) {
731                 auto const channels = audio->stream()->channels();
732                 if (channels != film->audio_channels()) {
733                         why_not = String::compose(_("it has a different number of audio channels than the project; set the project to have %1 channels."), channels);
734                         return false;
735                 }
736         }
737
738         auto part = [](shared_ptr<const Content> c) {
739                 return static_cast<bool>(c->audio) && !c->audio->mapping().mapped_output_channels().empty();
740         };
741
742         if (overlaps(film, part)) {
743                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
744                 why_not = _("it overlaps other audio content.");
745                 return false;
746         }
747
748         return can_reference_anything(film, why_not);
749 }
750
751
752 bool
753 DCPContent::can_reference_text (shared_ptr<const Film> film, TextType type, string& why_not) const
754 {
755         if (_has_non_zero_entry_point[TextType::OPEN_SUBTITLE]) {
756                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
757                 why_not = _("one of its subtitle reels has a non-zero entry point so it must be re-written.");
758                 return false;
759         }
760
761         if (_has_non_zero_entry_point[TextType::CLOSED_CAPTION]) {
762                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
763                 why_not = _("one of its closed caption has a non-zero entry point so it must be re-written.");
764                 return false;
765         }
766
767         if (trim_start() != dcpomatic::ContentTime()) {
768                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
769                 why_not = _("it has a start trim so its subtitles or closed captions must be re-written.");
770                 return false;
771         }
772
773         auto part = [type](shared_ptr<const Content> c) {
774                 return std::find_if(c->text.begin(), c->text.end(), [type](shared_ptr<const TextContent> t) { return t->type() == type; }) != c->text.end();
775         };
776
777         if (overlaps(film, part)) {
778                 /// TRANSLATORS: this string will follow "Cannot reference this DCP: "
779                 why_not = _("it overlaps other text content.");
780                 return false;
781         }
782
783         return can_reference_anything(film, why_not);
784 }
785
786 void
787 DCPContent::take_settings_from (shared_ptr<const Content> c)
788 {
789         auto dc = dynamic_pointer_cast<const DCPContent>(c);
790         if (!dc) {
791                 return;
792         }
793
794         if (this == dc.get()) {
795                 return;
796         }
797
798         boost::mutex::scoped_lock lm(_mutex);
799         boost::mutex::scoped_lock lm2(dc->_mutex);
800
801         _reference_video = dc->_reference_video;
802         _reference_audio = dc->_reference_audio;
803         _reference_text = dc->_reference_text;
804 }
805
806 void
807 DCPContent::set_cpl (string id)
808 {
809         ContentChangeSignaller cc (this, DCPContentProperty::CPL);
810
811         {
812                 boost::mutex::scoped_lock lm (_mutex);
813                 _cpl = id;
814         }
815 }
816
817 bool
818 DCPContent::kdm_timing_window_valid () const
819 {
820         if (!_kdm) {
821                 return true;
822         }
823
824         dcp::LocalTime now;
825         return _kdm->not_valid_before() < now && now < _kdm->not_valid_after();
826 }
827
828
829 Resolution
830 DCPContent::resolution () const
831 {
832         if (video->size() && (video->size()->width > 2048 || video->size()->height > 1080)) {
833                 return Resolution::FOUR_K;
834         }
835
836         return Resolution::TWO_K;
837 }
838
839
840 void
841 DCPContent::check_font_ids()
842 {
843         if (text.empty()) {
844                 return;
845         }
846
847         DCPExaminer examiner(shared_from_this(), true);
848         examiner.add_fonts(text.front());
849 }
850
851
852 int
853 DCPContent::active_audio_channels() const
854 {
855         return _active_audio_channels.get_value_or(
856                 (audio && audio->stream()) ? audio->stream()->channels() : 0
857                 );
858 }
859