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