Remove subtitle language from text content.
[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         shared_ptr<const dcp::CertificateChain> signer;
624         signer = Config::instance()->signer_chain ();
625         /* We did check earlier, but check again here to be on the safe side */
626         string reason;
627         if (!signer->valid (&reason)) {
628                 throw InvalidSignerError (reason);
629         }
630
631         dcp.write_xml (
632                 _film->interop() ? dcp::INTEROP : dcp::SMPTE,
633                 issuer,
634                 creator,
635                 dcp::LocalTime().as_string(),
636                 String::compose("Created by libdcp %1", dcp::version),
637                 signer,
638                 Config::instance()->dcp_metadata_filename_format()
639                 );
640
641         LOG_GENERAL (
642                 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
643                 );
644
645         write_cover_sheet ();
646 }
647
648 void
649 Writer::write_cover_sheet ()
650 {
651         boost::filesystem::path const cover = _film->file ("COVER_SHEET.txt");
652         FILE* f = fopen_boost (cover, "w");
653         if (!f) {
654                 throw OpenFileError (cover, errno, OpenFileError::WRITE);
655         }
656
657         string text = Config::instance()->cover_sheet ();
658         boost::algorithm::replace_all (text, "$CPL_NAME", _film->name());
659         boost::algorithm::replace_all (text, "$TYPE", _film->dcp_content_type()->pretty_name());
660         boost::algorithm::replace_all (text, "$CONTAINER", _film->container()->container_nickname());
661         boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _film->isdcf_metadata().audio_language);
662
663         vector<dcp::LanguageTag> subtitle_languages = _film->subtitle_languages();
664         if (subtitle_languages.empty()) {
665                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", "None");
666         } else {
667                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.front().description());
668         }
669
670         boost::uintmax_t size = 0;
671         for (
672                 boost::filesystem::recursive_directory_iterator i = boost::filesystem::recursive_directory_iterator(_film->dir(_film->dcp_name()));
673                 i != boost::filesystem::recursive_directory_iterator();
674                 ++i) {
675                 if (boost::filesystem::is_regular_file (i->path ())) {
676                         size += boost::filesystem::file_size (i->path ());
677                 }
678         }
679
680         if (size > (1000000000L)) {
681                 boost::algorithm::replace_all (text, "$SIZE", String::compose ("%1GB", dcp::locale_convert<string> (size / 1000000000.0, 1, true)));
682         } else {
683                 boost::algorithm::replace_all (text, "$SIZE", String::compose ("%1MB", dcp::locale_convert<string> (size / 1000000.0, 1, true)));
684         }
685
686         pair<int, int> ch = audio_channel_types (_film->mapped_audio_channels(), _film->audio_channels());
687         string description = String::compose("%1.%2", ch.first, ch.second);
688
689         if (description == "0.0") {
690                 description = _("None");
691         } else if (description == "1.0") {
692                 description = _("Mono");
693         } else if (description == "2.0") {
694                 description = _("Stereo");
695         }
696         boost::algorithm::replace_all (text, "$AUDIO", description);
697
698         int h, m, s, fr;
699         _film->length().split (_film->video_frame_rate(), h, m, s, fr);
700         string length;
701         if (h == 0 && m == 0) {
702                 length = String::compose("%1s", s);
703         } else if (h == 0 && m > 0) {
704                 length = String::compose("%1m%2s", m, s);
705         } else if (h > 0 && m > 0) {
706                 length = String::compose("%1h%2m%3s", h, m, s);
707         }
708
709         boost::algorithm::replace_all (text, "$LENGTH", length);
710
711         checked_fwrite (text.c_str(), text.length(), f, cover);
712         fclose (f);
713 }
714
715 /** @param frame Frame index within the whole DCP.
716  *  @return true if we can fake-write this frame.
717  */
718 bool
719 Writer::can_fake_write (Frame frame) const
720 {
721         if (_film->encrypted()) {
722                 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
723                 return false;
724         }
725
726         /* We have to do a proper write of the first frame so that we can set up the JPEG2000
727            parameters in the asset writer.
728         */
729
730         ReelWriter const & reel = _reels[video_reel(frame)];
731
732         /* Make frame relative to the start of the reel */
733         frame -= reel.start ();
734         return (frame != 0 && frame < reel.first_nonexistant_frame());
735 }
736
737 /** @param track Closed caption track if type == TEXT_CLOSED_CAPTION */
738 void
739 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
740 {
741         vector<ReelWriter>::iterator* reel = 0;
742
743         switch (type) {
744         case TEXT_OPEN_SUBTITLE:
745                 reel = &_subtitle_reel;
746                 break;
747         case TEXT_CLOSED_CAPTION:
748                 DCPOMATIC_ASSERT (track);
749                 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
750                 reel = &_caption_reels[*track];
751                 break;
752         default:
753                 DCPOMATIC_ASSERT (false);
754         }
755
756         DCPOMATIC_ASSERT (*reel != _reels.end());
757         while ((*reel)->period().to <= period.from) {
758                 ++(*reel);
759                 DCPOMATIC_ASSERT (*reel != _reels.end());
760         }
761
762         (*reel)->write (text, type, track, period);
763 }
764
765 void
766 Writer::write (list<shared_ptr<Font> > fonts)
767 {
768         /* Just keep a list of unique fonts and we'll deal with them in ::finish */
769
770         BOOST_FOREACH (shared_ptr<Font> i, fonts) {
771                 bool got = false;
772                 BOOST_FOREACH (shared_ptr<Font> j, _fonts) {
773                         if (*i == *j) {
774                                 got = true;
775                         }
776                 }
777
778                 if (!got) {
779                         _fonts.push_back (i);
780                 }
781         }
782 }
783
784 bool
785 operator< (QueueItem const & a, QueueItem const & b)
786 {
787         if (a.reel != b.reel) {
788                 return a.reel < b.reel;
789         }
790
791         if (a.frame != b.frame) {
792                 return a.frame < b.frame;
793         }
794
795         return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
796 }
797
798 bool
799 operator== (QueueItem const & a, QueueItem const & b)
800 {
801         return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
802 }
803
804 void
805 Writer::set_encoder_threads (int threads)
806 {
807         boost::mutex::scoped_lock lm (_state_mutex);
808         _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
809         _maximum_queue_size = threads * 16;
810 }
811
812 void
813 Writer::write (ReferencedReelAsset asset)
814 {
815         _reel_assets.push_back (asset);
816 }
817
818 size_t
819 Writer::video_reel (int frame) const
820 {
821         DCPTime t = DCPTime::from_frames (frame, _film->video_frame_rate ());
822         size_t i = 0;
823         while (i < _reels.size() && !_reels[i].period().contains (t)) {
824                 ++i;
825         }
826
827         DCPOMATIC_ASSERT (i < _reels.size ());
828         return i;
829 }
830
831 void
832 Writer::set_digest_progress (Job* job, float progress)
833 {
834         boost::mutex::scoped_lock lm (_digest_progresses_mutex);
835
836         _digest_progresses[boost::this_thread::get_id()] = progress;
837         float min_progress = FLT_MAX;
838         for (map<boost::thread::id, float>::const_iterator i = _digest_progresses.begin(); i != _digest_progresses.end(); ++i) {
839                 min_progress = min (min_progress, i->second);
840         }
841
842         job->set_progress (min_progress);
843
844         Waker waker;
845         waker.nudge ();
846 }