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