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