Make Writer tolerate a nullptr Job.
[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 void
521 Writer::finish ()
522 {
523         if (!_thread.joinable()) {
524                 return;
525         }
526
527         LOG_GENERAL_NC ("Terminating writer thread");
528
529         terminate_thread (true);
530
531         LOG_GENERAL_NC ("Finishing ReelWriters");
532
533         BOOST_FOREACH (ReelWriter& i, _reels) {
534                 i.finish ();
535         }
536
537         LOG_GENERAL_NC ("Writing XML");
538
539         dcp::DCP dcp (film()->dir(film()->dcp_name()));
540
541         shared_ptr<dcp::CPL> cpl (
542                 new dcp::CPL (
543                         film()->dcp_name(),
544                         film()->dcp_content_type()->libdcp_kind ()
545                         )
546                 );
547
548         dcp.add (cpl);
549
550         /* Calculate digests for each reel in parallel */
551
552         shared_ptr<Job> job = _job.lock ();
553         if (job) {
554                 job->sub (_("Computing digests"));
555         }
556
557         boost::asio::io_service service;
558         boost::thread_group pool;
559
560         shared_ptr<boost::asio::io_service::work> work (new boost::asio::io_service::work (service));
561
562         int const threads = max (1, Config::instance()->master_encoding_threads ());
563
564         for (int i = 0; i < threads; ++i) {
565                 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
566         }
567
568         boost::function<void (float)> set_progress;
569         if (job) {
570                 set_progress = boost::bind (&Writer::set_digest_progress, this, job.get(), _1);
571         } else {
572                 set_progress = &ignore_progress;
573         }
574
575         BOOST_FOREACH (ReelWriter& i, _reels) {
576                 service.post (boost::bind (&ReelWriter::calculate_digests, &i, set_progress));
577         }
578         service.post (boost::bind (&Writer::calculate_referenced_digests, this, set_progress));
579
580         work.reset ();
581         pool.join_all ();
582         service.stop ();
583
584         /* Add reels */
585
586         BOOST_FOREACH (ReelWriter& i, _reels) {
587                 cpl->add (i.create_reel (_reel_assets, _fonts));
588         }
589
590         /* Add metadata */
591
592         string creator = Config::instance()->dcp_creator();
593         if (creator.empty()) {
594                 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
595         }
596
597         string issuer = Config::instance()->dcp_issuer();
598         if (issuer.empty()) {
599                 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
600         }
601
602         cpl->set_ratings (film()->ratings());
603
604         vector<dcp::ContentVersion> cv;
605         BOOST_FOREACH (string i, film()->content_versions()) {
606                 cv.push_back (dcp::ContentVersion(i));
607         }
608         cpl->set_content_versions (cv);
609
610         cpl->set_full_content_title_text (film()->name());
611         cpl->set_full_content_title_text_language (film()->name_language());
612         cpl->set_release_territory (film()->release_territory());
613         cpl->set_version_number (film()->version_number());
614         cpl->set_status (film()->status());
615         cpl->set_chain (film()->chain());
616         cpl->set_distributor (film()->distributor());
617         cpl->set_facility (film()->facility());
618         cpl->set_luminance (film()->luminance());
619
620         list<int> ac = film()->mapped_audio_channels();
621         dcp::MCASoundField field = (
622                 find(ac.begin(), ac.end(), static_cast<int>(dcp::BSL)) != ac.end() ||
623                 find(ac.begin(), ac.end(), static_cast<int>(dcp::BSR)) != ac.end()
624                 ) ? dcp::SEVEN_POINT_ONE : dcp::FIVE_POINT_ONE;
625
626         dcp::MainSoundConfiguration msc (field, film()->audio_channels());
627         BOOST_FOREACH (int i, ac) {
628                 if (i < film()->audio_channels()) {
629                         msc.set_mapping (i, static_cast<dcp::Channel>(i));
630                 }
631         }
632
633         cpl->set_main_sound_configuration (msc.to_string());
634         cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
635         cpl->set_main_picture_stored_area (film()->frame_size());
636         cpl->set_main_picture_active_area (film()->active_area());
637
638         vector<dcp::LanguageTag> sl = film()->subtitle_languages();
639         if (sl.size() > 1) {
640                 cpl->set_additional_subtitle_languages(std::vector<dcp::LanguageTag>(sl.begin() + 1, sl.end()));
641         }
642
643         shared_ptr<const dcp::CertificateChain> signer;
644         signer = Config::instance()->signer_chain ();
645         /* We did check earlier, but check again here to be on the safe side */
646         string reason;
647         if (!signer->valid (&reason)) {
648                 throw InvalidSignerError (reason);
649         }
650
651         dcp.write_xml (
652                 film()->interop() ? dcp::INTEROP : dcp::SMPTE,
653                 issuer,
654                 creator,
655                 dcp::LocalTime().as_string(),
656                 String::compose("Created by libdcp %1", dcp::version),
657                 signer,
658                 Config::instance()->dcp_metadata_filename_format()
659                 );
660
661         LOG_GENERAL (
662                 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
663                 );
664
665         write_cover_sheet ();
666 }
667
668 void
669 Writer::write_cover_sheet ()
670 {
671         boost::filesystem::path const cover = film()->file("COVER_SHEET.txt");
672         FILE* f = fopen_boost (cover, "w");
673         if (!f) {
674                 throw OpenFileError (cover, errno, OpenFileError::WRITE);
675         }
676
677         string text = Config::instance()->cover_sheet ();
678         boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
679         boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
680         boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
681         boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", film()->isdcf_metadata().audio_language);
682
683         vector<dcp::LanguageTag> subtitle_languages = film()->subtitle_languages();
684         if (subtitle_languages.empty()) {
685                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", "None");
686         } else {
687                 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.front().description());
688         }
689
690         boost::uintmax_t size = 0;
691         for (
692                 boost::filesystem::recursive_directory_iterator i = boost::filesystem::recursive_directory_iterator(film()->dir(film()->dcp_name()));
693                 i != boost::filesystem::recursive_directory_iterator();
694                 ++i) {
695                 if (boost::filesystem::is_regular_file (i->path ())) {
696                         size += boost::filesystem::file_size (i->path ());
697                 }
698         }
699
700         if (size > (1000000000L)) {
701                 boost::algorithm::replace_all (text, "$SIZE", String::compose ("%1GB", dcp::locale_convert<string> (size / 1000000000.0, 1, true)));
702         } else {
703                 boost::algorithm::replace_all (text, "$SIZE", String::compose ("%1MB", dcp::locale_convert<string> (size / 1000000.0, 1, true)));
704         }
705
706         pair<int, int> ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
707         string description = String::compose("%1.%2", ch.first, ch.second);
708
709         if (description == "0.0") {
710                 description = _("None");
711         } else if (description == "1.0") {
712                 description = _("Mono");
713         } else if (description == "2.0") {
714                 description = _("Stereo");
715         }
716         boost::algorithm::replace_all (text, "$AUDIO", description);
717
718         int h, m, s, fr;
719         film()->length().split(film()->video_frame_rate(), h, m, s, fr);
720         string length;
721         if (h == 0 && m == 0) {
722                 length = String::compose("%1s", s);
723         } else if (h == 0 && m > 0) {
724                 length = String::compose("%1m%2s", m, s);
725         } else if (h > 0 && m > 0) {
726                 length = String::compose("%1h%2m%3s", h, m, s);
727         }
728
729         boost::algorithm::replace_all (text, "$LENGTH", length);
730
731         checked_fwrite (text.c_str(), text.length(), f, cover);
732         fclose (f);
733 }
734
735 /** @param frame Frame index within the whole DCP.
736  *  @return true if we can fake-write this frame.
737  */
738 bool
739 Writer::can_fake_write (Frame frame) const
740 {
741         if (film()->encrypted()) {
742                 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
743                 return false;
744         }
745
746         /* We have to do a proper write of the first frame so that we can set up the JPEG2000
747            parameters in the asset writer.
748         */
749
750         ReelWriter const & reel = _reels[video_reel(frame)];
751
752         /* Make frame relative to the start of the reel */
753         frame -= reel.start ();
754         return (frame != 0 && frame < reel.first_nonexistant_frame());
755 }
756
757 /** @param track Closed caption track if type == TEXT_CLOSED_CAPTION */
758 void
759 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
760 {
761         vector<ReelWriter>::iterator* reel = 0;
762
763         switch (type) {
764         case TEXT_OPEN_SUBTITLE:
765                 reel = &_subtitle_reel;
766                 break;
767         case TEXT_CLOSED_CAPTION:
768                 DCPOMATIC_ASSERT (track);
769                 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
770                 reel = &_caption_reels[*track];
771                 break;
772         default:
773                 DCPOMATIC_ASSERT (false);
774         }
775
776         DCPOMATIC_ASSERT (*reel != _reels.end());
777         while ((*reel)->period().to <= period.from) {
778                 ++(*reel);
779                 DCPOMATIC_ASSERT (*reel != _reels.end());
780         }
781
782         (*reel)->write (text, type, track, period);
783 }
784
785 void
786 Writer::write (list<shared_ptr<Font> > fonts)
787 {
788         /* Just keep a list of unique fonts and we'll deal with them in ::finish */
789
790         BOOST_FOREACH (shared_ptr<Font> i, fonts) {
791                 bool got = false;
792                 BOOST_FOREACH (shared_ptr<Font> j, _fonts) {
793                         if (*i == *j) {
794                                 got = true;
795                         }
796                 }
797
798                 if (!got) {
799                         _fonts.push_back (i);
800                 }
801         }
802 }
803
804 bool
805 operator< (QueueItem const & a, QueueItem const & b)
806 {
807         if (a.reel != b.reel) {
808                 return a.reel < b.reel;
809         }
810
811         if (a.frame != b.frame) {
812                 return a.frame < b.frame;
813         }
814
815         return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
816 }
817
818 bool
819 operator== (QueueItem const & a, QueueItem const & b)
820 {
821         return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
822 }
823
824 void
825 Writer::set_encoder_threads (int threads)
826 {
827         boost::mutex::scoped_lock lm (_state_mutex);
828         _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
829         _maximum_queue_size = threads * 16;
830 }
831
832 void
833 Writer::write (ReferencedReelAsset asset)
834 {
835         _reel_assets.push_back (asset);
836 }
837
838 size_t
839 Writer::video_reel (int frame) const
840 {
841         DCPTime t = DCPTime::from_frames (frame, film()->video_frame_rate());
842         size_t i = 0;
843         while (i < _reels.size() && !_reels[i].period().contains (t)) {
844                 ++i;
845         }
846
847         DCPOMATIC_ASSERT (i < _reels.size ());
848         return i;
849 }
850
851 void
852 Writer::set_digest_progress (Job* job, float progress)
853 {
854         boost::mutex::scoped_lock lm (_digest_progresses_mutex);
855
856         _digest_progresses[boost::this_thread::get_id()] = progress;
857         float min_progress = FLT_MAX;
858         for (map<boost::thread::id, float>::const_iterator i = _digest_progresses.begin(); i != _digest_progresses.end(); ++i) {
859                 min_progress = min (min_progress, i->second);
860         }
861
862         job->set_progress (min_progress);
863
864         Waker waker;
865         waker.nudge ();
866 }
867
868
869 /** Calculate hashes for any referenced MXF assets which do not already have one */
870 void
871 Writer::calculate_referenced_digests (boost::function<void (float)> set_progress)
872 {
873         BOOST_FOREACH (ReferencedReelAsset const& i, _reel_assets) {
874                 shared_ptr<dcp::ReelMXF> mxf = dynamic_pointer_cast<dcp::ReelMXF>(i.asset);
875                 if (mxf && !mxf->hash()) {
876                         mxf->asset_ref().asset()->hash (set_progress);
877                         mxf->set_hash (mxf->asset_ref().asset()->hash());
878                 }
879         }
880 }
881