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