Add some possibly-useful markers for debugging threads from coredumps.
[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                         /* Be careful that part_lengths[0] + part_lengths[1] can't be bigger than audio->frames() */
310                         Frame part_frames[2] = {
311                                 part_lengths[0].frames_ceil(afr),
312                                 part_lengths[1].frames_floor(afr)
313                         };
314
315                         DCPOMATIC_ASSERT ((part_frames[0] + part_frames[1]) <= audio->frames());
316
317                         if (part_frames[0]) {
318                                 shared_ptr<AudioBuffers> part (new AudioBuffers(audio, part_frames[0], 0));
319                                 _audio_reel->write (part);
320                         }
321
322                         if (part_frames[1]) {
323                                 audio.reset (new AudioBuffers(audio, part_frames[1], part_frames[0]));
324                         } else {
325                                 audio.reset ();
326                         }
327
328                         ++_audio_reel;
329                         t += part_lengths[0];
330                 }
331         }
332 }
333
334
335 void
336 Writer::write (shared_ptr<const dcp::AtmosFrame> atmos, DCPTime time, AtmosMetadata metadata)
337 {
338         if (_atmos_reel->period().to == time) {
339                 ++_atmos_reel;
340                 DCPOMATIC_ASSERT (_atmos_reel != _reels.end());
341         }
342
343         /* We assume that we get a video frame's worth of data here */
344         _atmos_reel->write (atmos, metadata);
345 }
346
347
348 /** Caller must hold a lock on _state_mutex */
349 bool
350 Writer::have_sequenced_image_at_queue_head ()
351 {
352         if (_queue.empty ()) {
353                 return false;
354         }
355
356         _queue.sort ();
357         auto const & f = _queue.front();
358         return _last_written[f.reel].next(f);
359 }
360
361
362 bool
363 Writer::LastWritten::next (QueueItem qi) const
364 {
365         if (qi.eyes == Eyes::BOTH) {
366                 /* 2D */
367                 return qi.frame == (_frame + 1);
368         }
369
370         /* 3D */
371
372         if (_eyes == Eyes::LEFT && qi.frame == _frame && qi.eyes == Eyes::RIGHT) {
373                 return true;
374         }
375
376         if (_eyes == Eyes::RIGHT && qi.frame == (_frame + 1) && qi.eyes == Eyes::LEFT) {
377                 return true;
378         }
379
380         return false;
381 }
382
383
384 void
385 Writer::LastWritten::update (QueueItem qi)
386 {
387         _frame = qi.frame;
388         _eyes = qi.eyes;
389 }
390
391
392 void
393 Writer::thread ()
394 try
395 {
396         start_of_thread ("Writer");
397
398         while (true)
399         {
400                 boost::mutex::scoped_lock lock (_state_mutex);
401
402                 while (true) {
403
404                         if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
405                                 /* We've got something to do: go and do it */
406                                 break;
407                         }
408
409                         /* Nothing to do: wait until something happens which may indicate that we do */
410                         LOG_TIMING (N_("writer-sleep queue=%1"), _queue.size());
411                         _empty_condition.wait (lock);
412                         LOG_TIMING (N_("writer-wake queue=%1"), _queue.size());
413                 }
414
415                 /* We stop here if we have been asked to finish, and if either the queue
416                    is empty or we do not have a sequenced image at its head (if this is the
417                    case we will never terminate as no new frames will be sent once
418                    _finish is true).
419                 */
420                 if (_finish && (!have_sequenced_image_at_queue_head() || _queue.empty())) {
421                         /* (Hopefully temporarily) log anything that was not written */
422                         if (!_queue.empty() && !have_sequenced_image_at_queue_head()) {
423                                 LOG_WARNING (N_("Finishing writer with a left-over queue of %1:"), _queue.size());
424                                 for (auto const& i: _queue) {
425                                         if (i.type == QueueItem::Type::FULL) {
426                                                 LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
427                                         } else {
428                                                 LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
429                                         }
430                                 }
431                         }
432                         return;
433                 }
434
435                 /* Write any frames that we can write; i.e. those that are in sequence. */
436                 while (have_sequenced_image_at_queue_head ()) {
437                         auto qi = _queue.front ();
438                         _last_written[qi.reel].update (qi);
439                         _queue.pop_front ();
440                         if (qi.type == QueueItem::Type::FULL && qi.encoded) {
441                                 --_queued_full_in_memory;
442                         }
443
444                         lock.unlock ();
445
446                         auto& reel = _reels[qi.reel];
447
448                         switch (qi.type) {
449                         case QueueItem::Type::FULL:
450                                 LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
451                                 if (!qi.encoded) {
452                                         qi.encoded.reset (new ArrayData(film()->j2c_path(qi.reel, qi.frame, qi.eyes, false)));
453                                 }
454                                 reel.write (qi.encoded, qi.frame, qi.eyes);
455                                 ++_full_written;
456                                 break;
457                         case QueueItem::Type::FAKE:
458                                 LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
459                                 reel.fake_write (qi.size);
460                                 ++_fake_written;
461                                 break;
462                         case QueueItem::Type::REPEAT:
463                                 LOG_DEBUG_ENCODE (N_("Writer REPEAT-writes %1"), qi.frame);
464                                 reel.repeat_write (qi.frame, qi.eyes);
465                                 ++_repeat_written;
466                                 break;
467                         }
468
469                         lock.lock ();
470                         _full_condition.notify_all ();
471                 }
472
473                 while (_queued_full_in_memory > _maximum_frames_in_memory) {
474                         /* Too many frames in memory which can't yet be written to the stream.
475                            Write some FULL frames to disk.
476                         */
477
478                         /* Find one from the back of the queue */
479                         _queue.sort ();
480                         auto i = _queue.rbegin ();
481                         while (i != _queue.rend() && (i->type != QueueItem::Type::FULL || !i->encoded)) {
482                                 ++i;
483                         }
484
485                         DCPOMATIC_ASSERT (i != _queue.rend());
486                         ++_pushed_to_disk;
487                         /* For the log message below */
488                         int const awaiting = _last_written[_queue.front().reel].frame() + 1;
489                         lock.unlock ();
490
491                         /* i is valid here, even though we don't hold a lock on the mutex,
492                            since list iterators are unaffected by insertion and only this
493                            thread could erase the last item in the list.
494                         */
495
496                         LOG_GENERAL ("Writer full; pushes %1 to disk while awaiting %2", i->frame, awaiting);
497
498                         i->encoded->write_via_temp (
499                                 film()->j2c_path(i->reel, i->frame, i->eyes, true),
500                                 film()->j2c_path(i->reel, i->frame, i->eyes, false)
501                                 );
502
503                         lock.lock ();
504                         i->encoded.reset ();
505                         --_queued_full_in_memory;
506                         _full_condition.notify_all ();
507                 }
508         }
509 }
510 catch (...)
511 {
512         store_current ();
513 }
514
515
516 void
517 Writer::terminate_thread (bool can_throw)
518 {
519         boost::this_thread::disable_interruption dis;
520
521         boost::mutex::scoped_lock lock (_state_mutex);
522
523         _finish = true;
524         _empty_condition.notify_all ();
525         _full_condition.notify_all ();
526         lock.unlock ();
527
528         try {
529                 _thread.join ();
530         } catch (...) {}
531
532         if (can_throw) {
533                 rethrow ();
534         }
535 }
536
537
538 /** @param output_dcp Path to DCP folder to write */
539 void
540 Writer::finish (boost::filesystem::path output_dcp)
541 {
542         if (_thread.joinable()) {
543                 LOG_GENERAL_NC ("Terminating writer thread");
544                 terminate_thread (true);
545         }
546
547         LOG_GENERAL_NC ("Finishing ReelWriters");
548
549         for (auto& i: _reels) {
550                 write_hanging_text (i);
551                 i.finish (output_dcp);
552         }
553
554         LOG_GENERAL_NC ("Writing XML");
555
556         dcp::DCP dcp (output_dcp);
557
558         auto cpl = make_shared<dcp::CPL>(
559                 film()->dcp_name(),
560                 film()->dcp_content_type()->libdcp_kind(),
561                 film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE
562                 );
563
564         dcp.add (cpl);
565
566         /* Calculate digests for each reel in parallel */
567
568         auto job = _job.lock ();
569         if (job) {
570                 job->sub (_("Computing digests"));
571         }
572
573         boost::asio::io_service service;
574         boost::thread_group pool;
575
576         auto work = make_shared<boost::asio::io_service::work>(service);
577
578         int const threads = max (1, Config::instance()->master_encoding_threads ());
579
580         for (int i = 0; i < threads; ++i) {
581                 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
582         }
583
584         boost::function<void (float)> set_progress;
585         if (job) {
586                 set_progress = boost::bind (&Writer::set_digest_progress, this, job.get(), _1);
587         } else {
588                 set_progress = &ignore_progress;
589         }
590
591         for (auto& i: _reels) {
592                 service.post (boost::bind (&ReelWriter::calculate_digests, &i, set_progress));
593         }
594         service.post (boost::bind (&Writer::calculate_referenced_digests, this, set_progress));
595
596         work.reset ();
597         pool.join_all ();
598         service.stop ();
599
600         /* Add reels */
601
602         for (auto& i: _reels) {
603                 cpl->add (i.create_reel(_reel_assets, _fonts, output_dcp, _have_subtitles, _have_closed_captions));
604         }
605
606         /* Add metadata */
607
608         auto creator = Config::instance()->dcp_creator();
609         if (creator.empty()) {
610                 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
611         }
612
613         auto issuer = Config::instance()->dcp_issuer();
614         if (issuer.empty()) {
615                 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
616         }
617
618         cpl->set_ratings (film()->ratings());
619
620         vector<dcp::ContentVersion> cv;
621         for (auto i: film()->content_versions()) {
622                 cv.push_back (dcp::ContentVersion(i));
623         }
624         cpl->set_content_versions (cv);
625
626         cpl->set_full_content_title_text (film()->name());
627         cpl->set_full_content_title_text_language (film()->name_language());
628         if (film()->release_territory()) {
629                 cpl->set_release_territory (*film()->release_territory());
630         }
631         cpl->set_version_number (film()->version_number());
632         cpl->set_status (film()->status());
633         if (film()->chain()) {
634                 cpl->set_chain (*film()->chain());
635         }
636         if (film()->distributor()) {
637                 cpl->set_distributor (*film()->distributor());
638         }
639         if (film()->facility()) {
640                 cpl->set_facility (*film()->facility());
641         }
642         if (film()->luminance()) {
643                 cpl->set_luminance (*film()->luminance());
644         }
645
646         auto ac = film()->mapped_audio_channels();
647         dcp::MCASoundField field = (
648                 find(ac.begin(), ac.end(), static_cast<int>(dcp::Channel::BSL)) != ac.end() ||
649                 find(ac.begin(), ac.end(), static_cast<int>(dcp::Channel::BSR)) != ac.end()
650                 ) ? dcp::MCASoundField::SEVEN_POINT_ONE : dcp::MCASoundField::FIVE_POINT_ONE;
651
652         dcp::MainSoundConfiguration msc (field, film()->audio_channels());
653         for (auto i: ac) {
654                 if (static_cast<int>(i) < film()->audio_channels()) {
655                         msc.set_mapping (i, static_cast<dcp::Channel>(i));
656                 }
657         }
658
659         cpl->set_main_sound_configuration (msc.to_string());
660         cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
661         cpl->set_main_picture_stored_area (film()->frame_size());
662
663         auto active_area = film()->active_area();
664         if (active_area.width > 0 && active_area.height > 0) {
665                 /* It's not allowed to have a zero active area width or height */
666                 cpl->set_main_picture_active_area (active_area);
667         }
668
669         auto sl = film()->subtitle_languages().second;
670         if (!sl.empty()) {
671                 cpl->set_additional_subtitle_languages(sl);
672         }
673
674         auto signer = Config::instance()->signer_chain();
675         /* We did check earlier, but check again here to be on the safe side */
676         string reason;
677         if (!signer->valid (&reason)) {
678                 throw InvalidSignerError (reason);
679         }
680
681         dcp.write_xml (
682                 issuer,
683                 creator,
684                 dcp::LocalTime().as_string(),
685                 film()->dcp_name(),
686                 signer,
687                 Config::instance()->dcp_metadata_filename_format()
688                 );
689
690         LOG_GENERAL (
691                 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
692                 );
693
694         write_cover_sheet (output_dcp);
695 }
696
697
698 void
699 Writer::write_cover_sheet (boost::filesystem::path output_dcp)
700 {
701         auto const cover = film()->file("COVER_SHEET.txt");
702         auto f = fopen_boost (cover, "w");
703         if (!f) {
704                 throw OpenFileError (cover, errno, OpenFileError::WRITE);
705         }
706
707         auto text = Config::instance()->cover_sheet ();
708         boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
709         boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
710         boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
711
712         auto audio_languages = film()->audio_languages();
713         if (!audio_languages.empty()) {
714                 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", audio_languages.front().description());
715         } else {
716                 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _("None"));
717         }
718
719         auto subtitle_languages = film()->subtitle_languages();
720         if (subtitle_languages.first) {
721                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.first->description());
722         } else {
723                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", _("None"));
724         }
725
726         boost::uintmax_t size = 0;
727         for (
728                 auto i = boost::filesystem::recursive_directory_iterator(output_dcp);
729                 i != boost::filesystem::recursive_directory_iterator();
730                 ++i) {
731                 if (boost::filesystem::is_regular_file (i->path())) {
732                         size += boost::filesystem::file_size (i->path());
733                 }
734         }
735
736         if (size > (1000000000L)) {
737                 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1GB", dcp::locale_convert<string>(size / 1000000000.0, 1, true)));
738         } else {
739                 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1MB", dcp::locale_convert<string>(size / 1000000.0, 1, true)));
740         }
741
742         auto ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
743         auto description = String::compose("%1.%2", ch.first, ch.second);
744
745         if (description == "0.0") {
746                 description = _("None");
747         } else if (description == "1.0") {
748                 description = _("Mono");
749         } else if (description == "2.0") {
750                 description = _("Stereo");
751         }
752         boost::algorithm::replace_all (text, "$AUDIO", description);
753
754         auto const hmsf = film()->length().split(film()->video_frame_rate());
755         string length;
756         if (hmsf.h == 0 && hmsf.m == 0) {
757                 length = String::compose("%1s", hmsf.s);
758         } else if (hmsf.h == 0 && hmsf.m > 0) {
759                 length = String::compose("%1m%2s", hmsf.m, hmsf.s);
760         } else if (hmsf.h > 0 && hmsf.m > 0) {
761                 length = String::compose("%1h%2m%3s", hmsf.h, hmsf.m, hmsf.s);
762         }
763
764         boost::algorithm::replace_all (text, "$LENGTH", length);
765
766         checked_fwrite (text.c_str(), text.length(), f, cover);
767         fclose (f);
768 }
769
770
771 /** @param frame Frame index within the whole DCP.
772  *  @return true if we can fake-write this frame.
773  */
774 bool
775 Writer::can_fake_write (Frame frame) const
776 {
777         if (film()->encrypted()) {
778                 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
779                 return false;
780         }
781
782         /* We have to do a proper write of the first frame so that we can set up the JPEG2000
783            parameters in the asset writer.
784         */
785
786         auto const & reel = _reels[video_reel(frame)];
787
788         /* Make frame relative to the start of the reel */
789         frame -= reel.start ();
790         return (frame != 0 && frame < reel.first_nonexistant_frame());
791 }
792
793
794 /** @param track Closed caption track if type == TextType::CLOSED_CAPTION */
795 void
796 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
797 {
798         vector<ReelWriter>::iterator* reel = nullptr;
799
800         switch (type) {
801         case TextType::OPEN_SUBTITLE:
802                 reel = &_subtitle_reel;
803                 _have_subtitles = true;
804                 break;
805         case TextType::CLOSED_CAPTION:
806                 DCPOMATIC_ASSERT (track);
807                 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
808                 reel = &_caption_reels[*track];
809                 _have_closed_captions.insert (*track);
810                 break;
811         default:
812                 DCPOMATIC_ASSERT (false);
813         }
814
815         DCPOMATIC_ASSERT (*reel != _reels.end());
816         while ((*reel)->period().to <= period.from) {
817                 ++(*reel);
818                 DCPOMATIC_ASSERT (*reel != _reels.end());
819                 write_hanging_text (**reel);
820         }
821
822         auto back_off = [this](DCPTimePeriod period) {
823                 period.to -= DCPTime::from_frames(2, film()->video_frame_rate());
824                 return period;
825         };
826
827         if (period.to > (*reel)->period().to) {
828                 /* This text goes off the end of the reel.  Store parts of it that should go into
829                  * other reels.
830                  */
831                 for (auto i = std::next(*reel); i != _reels.end(); ++i) {
832                         auto overlap = i->period().overlap(period);
833                         if (overlap) {
834                                 _hanging_texts.push_back (HangingText{text, type, track, back_off(*overlap)});
835                         }
836                 }
837                 /* Back off from the reel boundary by a couple of frames to avoid tripping checks
838                  * for subtitles being too close together.
839                  */
840                 period.to = (*reel)->period().to;
841                 period = back_off(period);
842         }
843
844         (*reel)->write (text, type, track, period);
845 }
846
847
848 void
849 Writer::write (vector<FontData> fonts)
850 {
851         /* Just keep a list of unique fonts and we'll deal with them in ::finish */
852
853         for (auto const& i: fonts) {
854                 bool got = false;
855                 for (auto& j: _fonts) {
856                         if (i == j) {
857                                 got = true;
858                         }
859                 }
860
861                 if (!got) {
862                         _fonts.push_back (i);
863                 }
864         }
865 }
866
867
868 bool
869 operator< (QueueItem const & a, QueueItem const & b)
870 {
871         if (a.reel != b.reel) {
872                 return a.reel < b.reel;
873         }
874
875         if (a.frame != b.frame) {
876                 return a.frame < b.frame;
877         }
878
879         return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
880 }
881
882
883 bool
884 operator== (QueueItem const & a, QueueItem const & b)
885 {
886         return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
887 }
888
889
890 void
891 Writer::set_encoder_threads (int threads)
892 {
893         boost::mutex::scoped_lock lm (_state_mutex);
894         _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
895         _maximum_queue_size = threads * 16;
896 }
897
898
899 void
900 Writer::write (ReferencedReelAsset asset)
901 {
902         _reel_assets.push_back (asset);
903 }
904
905
906 size_t
907 Writer::video_reel (int frame) const
908 {
909         auto t = DCPTime::from_frames (frame, film()->video_frame_rate());
910         size_t i = 0;
911         while (i < _reels.size() && !_reels[i].period().contains (t)) {
912                 ++i;
913         }
914
915         DCPOMATIC_ASSERT (i < _reels.size ());
916         return i;
917 }
918
919
920 void
921 Writer::set_digest_progress (Job* job, float progress)
922 {
923         boost::mutex::scoped_lock lm (_digest_progresses_mutex);
924
925         _digest_progresses[boost::this_thread::get_id()] = progress;
926         float min_progress = FLT_MAX;
927         for (auto const& i: _digest_progresses) {
928                 min_progress = min (min_progress, i.second);
929         }
930
931         job->set_progress (min_progress);
932
933         Waker waker;
934         waker.nudge ();
935 }
936
937
938 /** Calculate hashes for any referenced MXF assets which do not already have one */
939 void
940 Writer::calculate_referenced_digests (boost::function<void (float)> set_progress)
941 {
942         for (auto const& i: _reel_assets) {
943                 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
944                 if (file && !file->hash()) {
945                         file->asset_ref().asset()->hash (set_progress);
946                         file->set_hash (file->asset_ref().asset()->hash());
947                 }
948         }
949 }
950
951
952 void
953 Writer::write_hanging_text (ReelWriter& reel)
954 {
955         vector<HangingText> new_hanging_texts;
956         for (auto i: _hanging_texts) {
957                 if (i.period.from == reel.period().from) {
958                         reel.write (i.text, i.type, i.track, i.period);
959                 } else {
960                         new_hanging_texts.push_back (i);
961                 }
962         }
963         _hanging_texts = new_hanging_texts;
964 }