Don't write active picture area with zero width or height as it makes verification...
[dcpomatic.git] / src / lib / writer.cc
1 /*
2     Copyright (C) 2012-2021 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 "writer.h"
23 #include "compose.hpp"
24 #include "film.h"
25 #include "ratio.h"
26 #include "log.h"
27 #include "dcpomatic_log.h"
28 #include "dcp_video.h"
29 #include "dcp_content_type.h"
30 #include "audio_mapping.h"
31 #include "config.h"
32 #include "job.h"
33 #include "cross.h"
34 #include "audio_buffers.h"
35 #include "version.h"
36 #include "font_data.h"
37 #include "util.h"
38 #include "reel_writer.h"
39 #include "text_content.h"
40 #include <dcp/cpl.h>
41 #include <dcp/locale_convert.h>
42 #include <dcp/reel_file_asset.h>
43 #include <fstream>
44 #include <cerrno>
45 #include <iostream>
46 #include <cfloat>
47
48 #include "i18n.h"
49
50
51 /* OS X strikes again */
52 #undef set_key
53
54
55 using std::cout;
56 using std::dynamic_pointer_cast;
57 using std::list;
58 using std::make_pair;
59 using std::make_shared;
60 using std::map;
61 using std::max;
62 using std::min;
63 using std::pair;
64 using std::shared_ptr;
65 using std::string;
66 using std::vector;
67 using std::weak_ptr;
68 using boost::optional;
69 #if BOOST_VERSION >= 106100
70 using namespace boost::placeholders;
71 #endif
72 using dcp::Data;
73 using dcp::ArrayData;
74 using namespace dcpomatic;
75
76
77 static
78 void
79 ignore_progress (float)
80 {
81
82 }
83
84
85 /** @param j Job to report progress to, or 0.
86  *  @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
87  */
88 Writer::Writer (weak_ptr<const Film> weak_film, weak_ptr<Job> j, bool text_only)
89         : WeakConstFilm (weak_film)
90         , _job (j)
91         /* These will be reset to sensible values when J2KEncoder is created */
92         , _maximum_frames_in_memory (8)
93         , _maximum_queue_size (8)
94         , _text_only (text_only)
95 {
96         auto job = _job.lock ();
97
98         int reel_index = 0;
99         auto const reels = film()->reels();
100         for (auto p: reels) {
101                 _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
102         }
103
104         _last_written.resize (reels.size());
105
106         /* We can keep track of the current audio, subtitle and closed caption reels easily because audio
107            and captions arrive to the Writer in sequence.  This is not so for video.
108         */
109         _audio_reel = _reels.begin ();
110         _subtitle_reel = _reels.begin ();
111         for (auto i: film()->closed_caption_tracks()) {
112                 _caption_reels[i] = _reels.begin ();
113         }
114         _atmos_reel = _reels.begin ();
115
116         /* Check that the signer is OK */
117         string reason;
118         if (!Config::instance()->signer_chain()->valid(&reason)) {
119                 throw InvalidSignerError (reason);
120         }
121 }
122
123
124 void
125 Writer::start ()
126 {
127         if (!_text_only) {
128                 _thread = boost::thread (boost::bind(&Writer::thread, this));
129 #ifdef DCPOMATIC_LINUX
130                 pthread_setname_np (_thread.native_handle(), "writer");
131 #endif
132         }
133 }
134
135
136 Writer::~Writer ()
137 {
138         if (!_text_only) {
139                 terminate_thread (false);
140         }
141 }
142
143
144 /** Pass a video frame to the writer for writing to disk at some point.
145  *  This method can be called with frames out of order.
146  *  @param encoded JPEG2000-encoded data.
147  *  @param frame Frame index within the DCP.
148  *  @param eyes Eyes that this frame image is for.
149  */
150 void
151 Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
152 {
153         boost::mutex::scoped_lock lock (_state_mutex);
154
155         while (_queued_full_in_memory > _maximum_frames_in_memory) {
156                 /* There are too many full frames in memory; wake the main writer thread and
157                    wait until it sorts everything out */
158                 _empty_condition.notify_all ();
159                 _full_condition.wait (lock);
160         }
161
162         QueueItem qi;
163         qi.type = QueueItem::Type::FULL;
164         qi.encoded = encoded;
165         qi.reel = video_reel (frame);
166         qi.frame = frame - _reels[qi.reel].start ();
167
168         if (film()->three_d() && eyes == Eyes::BOTH) {
169                 /* 2D material in a 3D DCP; fake the 3D */
170                 qi.eyes = Eyes::LEFT;
171                 _queue.push_back (qi);
172                 ++_queued_full_in_memory;
173                 qi.eyes = Eyes::RIGHT;
174                 _queue.push_back (qi);
175                 ++_queued_full_in_memory;
176         } else {
177                 qi.eyes = eyes;
178                 _queue.push_back (qi);
179                 ++_queued_full_in_memory;
180         }
181
182         /* Now there's something to do: wake anything wait()ing on _empty_condition */
183         _empty_condition.notify_all ();
184 }
185
186
187 bool
188 Writer::can_repeat (Frame frame) const
189 {
190         return frame > _reels[video_reel(frame)].start();
191 }
192
193
194 /** Repeat the last frame that was written to a reel as a new frame.
195  *  @param frame Frame index within the DCP of the new (repeated) frame.
196  *  @param eyes Eyes that this repeated frame image is for.
197  */
198 void
199 Writer::repeat (Frame frame, Eyes eyes)
200 {
201         boost::mutex::scoped_lock lock (_state_mutex);
202
203         while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
204                 /* The queue is too big, and the main writer thread can run and fix it, so
205                    wake it and wait until it has done.
206                 */
207                 _empty_condition.notify_all ();
208                 _full_condition.wait (lock);
209         }
210
211         QueueItem qi;
212         qi.type = QueueItem::Type::REPEAT;
213         qi.reel = video_reel (frame);
214         qi.frame = frame - _reels[qi.reel].start ();
215         if (film()->three_d() && eyes == Eyes::BOTH) {
216                 qi.eyes = Eyes::LEFT;
217                 _queue.push_back (qi);
218                 qi.eyes = Eyes::RIGHT;
219                 _queue.push_back (qi);
220         } else {
221                 qi.eyes = eyes;
222                 _queue.push_back (qi);
223         }
224
225         /* Now there's something to do: wake anything wait()ing on _empty_condition */
226         _empty_condition.notify_all ();
227 }
228
229
230 void
231 Writer::fake_write (Frame frame, Eyes eyes)
232 {
233         boost::mutex::scoped_lock lock (_state_mutex);
234
235         while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
236                 /* The queue is too big, and the main writer thread can run and fix it, so
237                    wake it and wait until it has done.
238                 */
239                 _empty_condition.notify_all ();
240                 _full_condition.wait (lock);
241         }
242
243         size_t const reel = video_reel (frame);
244         Frame const frame_in_reel = frame - _reels[reel].start ();
245
246         QueueItem qi;
247         qi.type = QueueItem::Type::FAKE;
248
249         {
250                 shared_ptr<InfoFileHandle> info_file = film()->info_file_handle(_reels[reel].period(), true);
251                 qi.size = _reels[reel].read_frame_info(info_file, frame_in_reel, eyes).size;
252         }
253
254         qi.reel = reel;
255         qi.frame = frame_in_reel;
256         if (film()->three_d() && eyes == Eyes::BOTH) {
257                 qi.eyes = Eyes::LEFT;
258                 _queue.push_back (qi);
259                 qi.eyes = Eyes::RIGHT;
260                 _queue.push_back (qi);
261         } else {
262                 qi.eyes = eyes;
263                 _queue.push_back (qi);
264         }
265
266         /* Now there's something to do: wake anything wait()ing on _empty_condition */
267         _empty_condition.notify_all ();
268 }
269
270
271 /** Write some audio frames to the DCP.
272  *  @param audio Audio data.
273  *  @param time Time of this data within the DCP.
274  *  This method is not thread safe.
275  */
276 void
277 Writer::write (shared_ptr<const AudioBuffers> audio, DCPTime const time)
278 {
279         DCPOMATIC_ASSERT (audio);
280
281         int const afr = film()->audio_frame_rate();
282
283         DCPTime const end = time + DCPTime::from_frames(audio->frames(), afr);
284
285         /* The audio we get might span a reel boundary, and if so we have to write it in bits */
286
287         DCPTime t = time;
288         while (t < end) {
289
290                 if (_audio_reel == _reels.end ()) {
291                         /* This audio is off the end of the last reel; ignore it */
292                         return;
293                 }
294
295                 if (end <= _audio_reel->period().to) {
296                         /* Easy case: we can write all the audio to this reel */
297                         _audio_reel->write (audio);
298                         t = end;
299                 } else if (_audio_reel->period().to <= t) {
300                         /* This reel is entirely before the start of our audio; just skip the reel */
301                         ++_audio_reel;
302                 } else {
303                         /* This audio is over a reel boundary; split the audio into two and write the first part */
304                         DCPTime part_lengths[2] = {
305                                 _audio_reel->period().to - t,
306                                 end - _audio_reel->period().to
307                         };
308
309                         Frame part_frames[2] = {
310                                 part_lengths[0].frames_ceil(afr),
311                                 part_lengths[1].frames_ceil(afr)
312                         };
313
314                         if (part_frames[0]) {
315                                 shared_ptr<AudioBuffers> part (new AudioBuffers(audio, part_frames[0], 0));
316                                 _audio_reel->write (part);
317                         }
318
319                         if (part_frames[1]) {
320                                 audio.reset (new AudioBuffers(audio, part_frames[1], part_frames[0]));
321                         } else {
322                                 audio.reset ();
323                         }
324
325                         ++_audio_reel;
326                         t += part_lengths[0];
327                 }
328         }
329 }
330
331
332 void
333 Writer::write (shared_ptr<const dcp::AtmosFrame> atmos, DCPTime time, AtmosMetadata metadata)
334 {
335         if (_atmos_reel->period().to == time) {
336                 ++_atmos_reel;
337                 DCPOMATIC_ASSERT (_atmos_reel != _reels.end());
338         }
339
340         /* We assume that we get a video frame's worth of data here */
341         _atmos_reel->write (atmos, metadata);
342 }
343
344
345 /** Caller must hold a lock on _state_mutex */
346 bool
347 Writer::have_sequenced_image_at_queue_head ()
348 {
349         if (_queue.empty ()) {
350                 return false;
351         }
352
353         _queue.sort ();
354         auto const & f = _queue.front();
355         return _last_written[f.reel].next(f);
356 }
357
358
359 bool
360 Writer::LastWritten::next (QueueItem qi) const
361 {
362         if (qi.eyes == Eyes::BOTH) {
363                 /* 2D */
364                 return qi.frame == (_frame + 1);
365         }
366
367         /* 3D */
368
369         if (_eyes == Eyes::LEFT && qi.frame == _frame && qi.eyes == Eyes::RIGHT) {
370                 return true;
371         }
372
373         if (_eyes == Eyes::RIGHT && qi.frame == (_frame + 1) && qi.eyes == Eyes::LEFT) {
374                 return true;
375         }
376
377         return false;
378 }
379
380
381 void
382 Writer::LastWritten::update (QueueItem qi)
383 {
384         _frame = qi.frame;
385         _eyes = qi.eyes;
386 }
387
388
389 void
390 Writer::thread ()
391 try
392 {
393         while (true)
394         {
395                 boost::mutex::scoped_lock lock (_state_mutex);
396
397                 while (true) {
398
399                         if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
400                                 /* We've got something to do: go and do it */
401                                 break;
402                         }
403
404                         /* Nothing to do: wait until something happens which may indicate that we do */
405                         LOG_TIMING (N_("writer-sleep queue=%1"), _queue.size());
406                         _empty_condition.wait (lock);
407                         LOG_TIMING (N_("writer-wake queue=%1"), _queue.size());
408                 }
409
410                 /* We stop here if we have been asked to finish, and if either the queue
411                    is empty or we do not have a sequenced image at its head (if this is the
412                    case we will never terminate as no new frames will be sent once
413                    _finish is true).
414                 */
415                 if (_finish && (!have_sequenced_image_at_queue_head() || _queue.empty())) {
416                         /* (Hopefully temporarily) log anything that was not written */
417                         if (!_queue.empty() && !have_sequenced_image_at_queue_head()) {
418                                 LOG_WARNING (N_("Finishing writer with a left-over queue of %1:"), _queue.size());
419                                 for (auto const& i: _queue) {
420                                         if (i.type == QueueItem::Type::FULL) {
421                                                 LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
422                                         } else {
423                                                 LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
424                                         }
425                                 }
426                         }
427                         return;
428                 }
429
430                 /* Write any frames that we can write; i.e. those that are in sequence. */
431                 while (have_sequenced_image_at_queue_head ()) {
432                         auto qi = _queue.front ();
433                         _last_written[qi.reel].update (qi);
434                         _queue.pop_front ();
435                         if (qi.type == QueueItem::Type::FULL && qi.encoded) {
436                                 --_queued_full_in_memory;
437                         }
438
439                         lock.unlock ();
440
441                         auto& reel = _reels[qi.reel];
442
443                         switch (qi.type) {
444                         case QueueItem::Type::FULL:
445                                 LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
446                                 if (!qi.encoded) {
447                                         qi.encoded.reset (new ArrayData(film()->j2c_path(qi.reel, qi.frame, qi.eyes, false)));
448                                 }
449                                 reel.write (qi.encoded, qi.frame, qi.eyes);
450                                 ++_full_written;
451                                 break;
452                         case QueueItem::Type::FAKE:
453                                 LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
454                                 reel.fake_write (qi.size);
455                                 ++_fake_written;
456                                 break;
457                         case QueueItem::Type::REPEAT:
458                                 LOG_DEBUG_ENCODE (N_("Writer REPEAT-writes %1"), qi.frame);
459                                 reel.repeat_write (qi.frame, qi.eyes);
460                                 ++_repeat_written;
461                                 break;
462                         }
463
464                         lock.lock ();
465                         _full_condition.notify_all ();
466                 }
467
468                 while (_queued_full_in_memory > _maximum_frames_in_memory) {
469                         /* Too many frames in memory which can't yet be written to the stream.
470                            Write some FULL frames to disk.
471                         */
472
473                         /* Find one from the back of the queue */
474                         _queue.sort ();
475                         auto i = _queue.rbegin ();
476                         while (i != _queue.rend() && (i->type != QueueItem::Type::FULL || !i->encoded)) {
477                                 ++i;
478                         }
479
480                         DCPOMATIC_ASSERT (i != _queue.rend());
481                         ++_pushed_to_disk;
482                         /* For the log message below */
483                         int const awaiting = _last_written[_queue.front().reel].frame() + 1;
484                         lock.unlock ();
485
486                         /* i is valid here, even though we don't hold a lock on the mutex,
487                            since list iterators are unaffected by insertion and only this
488                            thread could erase the last item in the list.
489                         */
490
491                         LOG_GENERAL ("Writer full; pushes %1 to disk while awaiting %2", i->frame, awaiting);
492
493                         i->encoded->write_via_temp (
494                                 film()->j2c_path(i->reel, i->frame, i->eyes, true),
495                                 film()->j2c_path(i->reel, i->frame, i->eyes, false)
496                                 );
497
498                         lock.lock ();
499                         i->encoded.reset ();
500                         --_queued_full_in_memory;
501                         _full_condition.notify_all ();
502                 }
503         }
504 }
505 catch (...)
506 {
507         store_current ();
508 }
509
510
511 void
512 Writer::terminate_thread (bool can_throw)
513 {
514         boost::this_thread::disable_interruption dis;
515
516         boost::mutex::scoped_lock lock (_state_mutex);
517
518         _finish = true;
519         _empty_condition.notify_all ();
520         _full_condition.notify_all ();
521         lock.unlock ();
522
523         try {
524                 _thread.join ();
525         } catch (...) {}
526
527         if (can_throw) {
528                 rethrow ();
529         }
530 }
531
532
533 /** @param output_dcp Path to DCP folder to write */
534 void
535 Writer::finish (boost::filesystem::path output_dcp)
536 {
537         if (_thread.joinable()) {
538                 LOG_GENERAL_NC ("Terminating writer thread");
539                 terminate_thread (true);
540         }
541
542         LOG_GENERAL_NC ("Finishing ReelWriters");
543
544         for (auto& i: _reels) {
545                 i.finish (output_dcp);
546         }
547
548         LOG_GENERAL_NC ("Writing XML");
549
550         dcp::DCP dcp (output_dcp);
551
552         auto cpl = make_shared<dcp::CPL>(
553                 film()->dcp_name(),
554                 film()->dcp_content_type()->libdcp_kind()
555                 );
556
557         dcp.add (cpl);
558
559         /* Calculate digests for each reel in parallel */
560
561         auto job = _job.lock ();
562         if (job) {
563                 job->sub (_("Computing digests"));
564         }
565
566         boost::asio::io_service service;
567         boost::thread_group pool;
568
569         auto work = make_shared<boost::asio::io_service::work>(service);
570
571         int const threads = max (1, Config::instance()->master_encoding_threads ());
572
573         for (int i = 0; i < threads; ++i) {
574                 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
575         }
576
577         boost::function<void (float)> set_progress;
578         if (job) {
579                 set_progress = boost::bind (&Writer::set_digest_progress, this, job.get(), _1);
580         } else {
581                 set_progress = &ignore_progress;
582         }
583
584         for (auto& i: _reels) {
585                 service.post (boost::bind (&ReelWriter::calculate_digests, &i, set_progress));
586         }
587         service.post (boost::bind (&Writer::calculate_referenced_digests, this, set_progress));
588
589         work.reset ();
590         pool.join_all ();
591         service.stop ();
592
593         /* Add reels */
594
595         for (auto& i: _reels) {
596                 cpl->add (i.create_reel(_reel_assets, _fonts, output_dcp, _have_subtitles, _have_closed_captions));
597         }
598
599         /* Add metadata */
600
601         auto creator = Config::instance()->dcp_creator();
602         if (creator.empty()) {
603                 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
604         }
605
606         auto issuer = Config::instance()->dcp_issuer();
607         if (issuer.empty()) {
608                 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
609         }
610
611         cpl->set_ratings (film()->ratings());
612
613         vector<dcp::ContentVersion> cv;
614         for (auto i: film()->content_versions()) {
615                 cv.push_back (dcp::ContentVersion(i));
616         }
617         cpl->set_content_versions (cv);
618
619         cpl->set_full_content_title_text (film()->name());
620         cpl->set_full_content_title_text_language (film()->name_language());
621         cpl->set_release_territory (film()->release_territory());
622         cpl->set_version_number (film()->version_number());
623         cpl->set_status (film()->status());
624         cpl->set_chain (film()->chain());
625         cpl->set_distributor (film()->distributor());
626         cpl->set_facility (film()->facility());
627         cpl->set_luminance (film()->luminance());
628
629         auto ac = film()->mapped_audio_channels();
630         dcp::MCASoundField field = (
631                 find(ac.begin(), ac.end(), static_cast<int>(dcp::Channel::BSL)) != ac.end() ||
632                 find(ac.begin(), ac.end(), static_cast<int>(dcp::Channel::BSR)) != ac.end()
633                 ) ? dcp::MCASoundField::SEVEN_POINT_ONE : dcp::MCASoundField::FIVE_POINT_ONE;
634
635         dcp::MainSoundConfiguration msc (field, film()->audio_channels());
636         for (auto i: ac) {
637                 if (static_cast<int>(i) < film()->audio_channels()) {
638                         msc.set_mapping (i, static_cast<dcp::Channel>(i));
639                 }
640         }
641
642         cpl->set_main_sound_configuration (msc.to_string());
643         cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
644         cpl->set_main_picture_stored_area (film()->frame_size());
645
646         auto active_area = film()->active_area();
647         if (active_area.width > 0 && active_area.height > 0) {
648                 /* It's not allowed to have a zero active area width or height */
649                 cpl->set_main_picture_active_area (active_area);
650         }
651
652         vector<dcp::LanguageTag> sl = film()->subtitle_languages();
653         if (sl.size() > 1) {
654                 cpl->set_additional_subtitle_languages(std::vector<dcp::LanguageTag>(sl.begin() + 1, sl.end()));
655         }
656
657         auto signer = Config::instance()->signer_chain();
658         /* We did check earlier, but check again here to be on the safe side */
659         string reason;
660         if (!signer->valid (&reason)) {
661                 throw InvalidSignerError (reason);
662         }
663
664         dcp.write_xml (
665                 film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE,
666                 issuer,
667                 creator,
668                 dcp::LocalTime().as_string(),
669                 film()->dcp_name(),
670                 signer,
671                 Config::instance()->dcp_metadata_filename_format()
672                 );
673
674         LOG_GENERAL (
675                 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
676                 );
677
678         write_cover_sheet (output_dcp);
679 }
680
681
682 void
683 Writer::write_cover_sheet (boost::filesystem::path output_dcp)
684 {
685         auto const cover = film()->file("COVER_SHEET.txt");
686         auto f = fopen_boost (cover, "w");
687         if (!f) {
688                 throw OpenFileError (cover, errno, OpenFileError::WRITE);
689         }
690
691         auto text = Config::instance()->cover_sheet ();
692         boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
693         boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
694         boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
695         boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", film()->isdcf_metadata().audio_language);
696
697         auto subtitle_languages = film()->subtitle_languages();
698         if (subtitle_languages.empty()) {
699                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", "None");
700         } else {
701                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.front().description());
702         }
703
704         boost::uintmax_t size = 0;
705         for (
706                 auto i = boost::filesystem::recursive_directory_iterator(output_dcp);
707                 i != boost::filesystem::recursive_directory_iterator();
708                 ++i) {
709                 if (boost::filesystem::is_regular_file (i->path())) {
710                         size += boost::filesystem::file_size (i->path());
711                 }
712         }
713
714         if (size > (1000000000L)) {
715                 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1GB", dcp::locale_convert<string>(size / 1000000000.0, 1, true)));
716         } else {
717                 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1MB", dcp::locale_convert<string>(size / 1000000.0, 1, true)));
718         }
719
720         auto ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
721         auto description = String::compose("%1.%2", ch.first, ch.second);
722
723         if (description == "0.0") {
724                 description = _("None");
725         } else if (description == "1.0") {
726                 description = _("Mono");
727         } else if (description == "2.0") {
728                 description = _("Stereo");
729         }
730         boost::algorithm::replace_all (text, "$AUDIO", description);
731
732         int h, m, s, fr;
733         film()->length().split(film()->video_frame_rate(), h, m, s, fr);
734         string length;
735         if (h == 0 && m == 0) {
736                 length = String::compose("%1s", s);
737         } else if (h == 0 && m > 0) {
738                 length = String::compose("%1m%2s", m, s);
739         } else if (h > 0 && m > 0) {
740                 length = String::compose("%1h%2m%3s", h, m, s);
741         }
742
743         boost::algorithm::replace_all (text, "$LENGTH", length);
744
745         checked_fwrite (text.c_str(), text.length(), f, cover);
746         fclose (f);
747 }
748
749
750 /** @param frame Frame index within the whole DCP.
751  *  @return true if we can fake-write this frame.
752  */
753 bool
754 Writer::can_fake_write (Frame frame) const
755 {
756         if (film()->encrypted()) {
757                 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
758                 return false;
759         }
760
761         /* We have to do a proper write of the first frame so that we can set up the JPEG2000
762            parameters in the asset writer.
763         */
764
765         auto const & reel = _reels[video_reel(frame)];
766
767         /* Make frame relative to the start of the reel */
768         frame -= reel.start ();
769         return (frame != 0 && frame < reel.first_nonexistant_frame());
770 }
771
772
773 /** @param track Closed caption track if type == TextType::CLOSED_CAPTION */
774 void
775 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
776 {
777         vector<ReelWriter>::iterator* reel = nullptr;
778
779         switch (type) {
780         case TextType::OPEN_SUBTITLE:
781                 reel = &_subtitle_reel;
782                 _have_subtitles = true;
783                 break;
784         case TextType::CLOSED_CAPTION:
785                 DCPOMATIC_ASSERT (track);
786                 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
787                 reel = &_caption_reels[*track];
788                 _have_closed_captions.insert (*track);
789                 break;
790         default:
791                 DCPOMATIC_ASSERT (false);
792         }
793
794         DCPOMATIC_ASSERT (*reel != _reels.end());
795         while ((*reel)->period().to <= period.from) {
796                 ++(*reel);
797                 DCPOMATIC_ASSERT (*reel != _reels.end());
798         }
799
800         (*reel)->write (text, type, track, period);
801 }
802
803
804 void
805 Writer::write (vector<FontData> fonts)
806 {
807         /* Just keep a list of unique fonts and we'll deal with them in ::finish */
808
809         for (auto const& i: fonts) {
810                 bool got = false;
811                 for (auto& j: _fonts) {
812                         if (i == j) {
813                                 got = true;
814                         }
815                 }
816
817                 if (!got) {
818                         _fonts.push_back (i);
819                 }
820         }
821 }
822
823
824 bool
825 operator< (QueueItem const & a, QueueItem const & b)
826 {
827         if (a.reel != b.reel) {
828                 return a.reel < b.reel;
829         }
830
831         if (a.frame != b.frame) {
832                 return a.frame < b.frame;
833         }
834
835         return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
836 }
837
838
839 bool
840 operator== (QueueItem const & a, QueueItem const & b)
841 {
842         return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
843 }
844
845
846 void
847 Writer::set_encoder_threads (int threads)
848 {
849         boost::mutex::scoped_lock lm (_state_mutex);
850         _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
851         _maximum_queue_size = threads * 16;
852 }
853
854
855 void
856 Writer::write (ReferencedReelAsset asset)
857 {
858         _reel_assets.push_back (asset);
859 }
860
861
862 size_t
863 Writer::video_reel (int frame) const
864 {
865         auto t = DCPTime::from_frames (frame, film()->video_frame_rate());
866         size_t i = 0;
867         while (i < _reels.size() && !_reels[i].period().contains (t)) {
868                 ++i;
869         }
870
871         DCPOMATIC_ASSERT (i < _reels.size ());
872         return i;
873 }
874
875
876 void
877 Writer::set_digest_progress (Job* job, float progress)
878 {
879         boost::mutex::scoped_lock lm (_digest_progresses_mutex);
880
881         _digest_progresses[boost::this_thread::get_id()] = progress;
882         float min_progress = FLT_MAX;
883         for (auto const& i: _digest_progresses) {
884                 min_progress = min (min_progress, i.second);
885         }
886
887         job->set_progress (min_progress);
888
889         Waker waker;
890         waker.nudge ();
891 }
892
893
894 /** Calculate hashes for any referenced MXF assets which do not already have one */
895 void
896 Writer::calculate_referenced_digests (boost::function<void (float)> set_progress)
897 {
898         for (auto const& i: _reel_assets) {
899                 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
900                 if (file && !file->hash()) {
901                         file->asset_ref().asset()->hash (set_progress);
902                         file->set_hash (file->asset_ref().asset()->hash());
903                 }
904         }
905 }
906