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