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