2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
22 #include "audio_buffers.h"
23 #include "audio_mapping.h"
24 #include "compose.hpp"
26 #include "constants.h"
28 #include "dcp_content_type.h"
29 #include "dcp_video.h"
30 #include "dcpomatic_log.h"
32 #include "film_util.h"
36 #include "reel_writer.h"
37 #include "text_content.h"
42 #include <dcp/locale_convert.h>
43 #include <dcp/raw_convert.h>
44 #include <dcp/reel_file_asset.h>
52 /* OS X strikes again */
57 using std::dynamic_pointer_cast;
58 using std::make_shared;
61 using std::shared_ptr;
66 using boost::optional;
67 #if BOOST_VERSION >= 106100
68 using namespace boost::placeholders;
72 using namespace dcpomatic;
75 /** @param weak_job Job to report progress to, or 0.
76 * @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
78 Writer::Writer(weak_ptr<const Film> weak_film, weak_ptr<Job> weak_job, bool text_only)
79 : WeakConstFilm (weak_film)
81 /* These will be reset to sensible values when J2KEncoder is created */
82 , _maximum_frames_in_memory (8)
83 , _maximum_queue_size (8)
84 , _text_only (text_only)
86 auto job = _job.lock ();
89 auto const reels = film()->reels();
91 _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
94 _last_written.resize (reels.size());
96 /* We can keep track of the current audio, subtitle and closed caption reels easily because audio
97 and captions arrive to the Writer in sequence. This is not so for video.
99 _audio_reel = _reels.begin ();
100 _subtitle_reel = _reels.begin ();
101 for (auto i: film()->closed_caption_tracks()) {
102 _caption_reels[i] = _reels.begin ();
104 _atmos_reel = _reels.begin ();
106 /* Check that the signer is OK */
108 if (!Config::instance()->signer_chain()->valid(&reason)) {
109 throw InvalidSignerError (reason);
118 _thread = boost::thread (boost::bind(&Writer::thread, this));
119 #ifdef DCPOMATIC_LINUX
120 pthread_setname_np (_thread.native_handle(), "writer");
129 terminate_thread (false);
134 /** Pass a video frame to the writer for writing to disk at some point.
135 * This method can be called with frames out of order.
136 * @param encoded JPEG2000-encoded data.
137 * @param frame Frame index within the DCP.
138 * @param eyes Eyes that this frame image is for.
141 Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
143 boost::mutex::scoped_lock lock (_state_mutex);
145 while (_queued_full_in_memory > _maximum_frames_in_memory) {
146 /* There are too many full frames in memory; wake the main writer thread and
147 wait until it sorts everything out */
148 _empty_condition.notify_all ();
149 _full_condition.wait (lock);
153 qi.type = QueueItem::Type::FULL;
154 qi.encoded = encoded;
155 qi.reel = video_reel (frame);
156 qi.frame = frame - _reels[qi.reel].start ();
158 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
161 _queue.push_back(qi);
162 ++_queued_full_in_memory;
164 /* Now there's something to do: wake anything wait()ing on _empty_condition */
165 _empty_condition.notify_all ();
170 Writer::can_repeat (Frame frame) const
172 return frame > _reels[video_reel(frame)].start();
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.
181 Writer::repeat (Frame frame, Eyes eyes)
183 boost::mutex::scoped_lock lock (_state_mutex);
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.
189 _empty_condition.notify_all ();
190 _full_condition.wait (lock);
194 qi.type = QueueItem::Type::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);
204 _queue.push_back (qi);
207 /* Now there's something to do: wake anything wait()ing on _empty_condition */
208 _empty_condition.notify_all ();
213 Writer::fake_write (Frame frame, Eyes eyes)
215 boost::mutex::scoped_lock lock (_state_mutex);
217 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
218 /* The queue is too big, and the main writer thread can run and fix it, so
219 wake it and wait until it has done.
221 _empty_condition.notify_all ();
222 _full_condition.wait (lock);
225 size_t const reel = video_reel (frame);
226 Frame const frame_in_reel = frame - _reels[reel].start ();
229 qi.type = QueueItem::Type::FAKE;
232 shared_ptr<InfoFileHandle> info_file = film()->info_file_handle(_reels[reel].period(), true);
233 qi.size = _reels[reel].read_frame_info(info_file, frame_in_reel, eyes).size;
236 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
239 qi.frame = frame_in_reel;
241 _queue.push_back(qi);
243 /* Now there's something to do: wake anything wait()ing on _empty_condition */
244 _empty_condition.notify_all ();
248 /** Write some audio frames to the DCP.
249 * @param audio Audio data.
250 * @param time Time of this data within the DCP.
251 * This method is not thread safe.
254 Writer::write (shared_ptr<const AudioBuffers> audio, DCPTime const time)
256 DCPOMATIC_ASSERT (audio);
258 int const afr = film()->audio_frame_rate();
260 DCPTime const end = time + DCPTime::from_frames(audio->frames(), afr);
262 /* The audio we get might span a reel boundary, and if so we have to write it in bits */
267 if (_audio_reel == _reels.end ()) {
268 /* This audio is off the end of the last reel; ignore it */
272 if (end <= _audio_reel->period().to) {
273 /* Easy case: we can write all the audio to this reel */
274 _audio_reel->write (audio);
276 } else if (_audio_reel->period().to <= t) {
277 /* This reel is entirely before the start of our audio; just skip the reel */
280 /* This audio is over a reel boundary; split the audio into two and write the first part */
281 DCPTime part_lengths[2] = {
282 _audio_reel->period().to - t,
283 end - _audio_reel->period().to
286 /* Be careful that part_lengths[0] + part_lengths[1] can't be bigger than audio->frames() */
287 Frame part_frames[2] = {
288 part_lengths[0].frames_ceil(afr),
289 part_lengths[1].frames_floor(afr)
292 DCPOMATIC_ASSERT ((part_frames[0] + part_frames[1]) <= audio->frames());
294 if (part_frames[0]) {
295 auto part = make_shared<AudioBuffers>(audio, part_frames[0], 0);
296 _audio_reel->write (part);
299 if (part_frames[1]) {
300 audio = make_shared<AudioBuffers>(audio, part_frames[1], part_frames[0]);
306 t += part_lengths[0];
313 Writer::write (shared_ptr<const dcp::AtmosFrame> atmos, DCPTime time, AtmosMetadata metadata)
315 if (_atmos_reel->period().to == time) {
317 DCPOMATIC_ASSERT (_atmos_reel != _reels.end());
320 /* We assume that we get a video frame's worth of data here */
321 _atmos_reel->write (atmos, metadata);
325 /** Caller must hold a lock on _state_mutex */
327 Writer::have_sequenced_image_at_queue_head ()
329 if (_queue.empty ()) {
334 auto const & f = _queue.front();
335 return _last_written[f.reel].next(f);
340 Writer::LastWritten::next (QueueItem qi) const
342 if (qi.eyes == Eyes::BOTH) {
344 return qi.frame == (_frame + 1);
349 if (_eyes == Eyes::LEFT && qi.frame == _frame && qi.eyes == Eyes::RIGHT) {
353 if (_eyes == Eyes::RIGHT && qi.frame == (_frame + 1) && qi.eyes == Eyes::LEFT) {
362 Writer::LastWritten::update (QueueItem qi)
373 start_of_thread ("Writer");
377 boost::mutex::scoped_lock lock (_state_mutex);
381 if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
382 /* We've got something to do: go and do it */
386 /* Nothing to do: wait until something happens which may indicate that we do */
387 LOG_TIMING (N_("writer-sleep queue=%1"), _queue.size());
388 _empty_condition.wait (lock);
389 LOG_TIMING (N_("writer-wake queue=%1"), _queue.size());
392 /* We stop here if we have been asked to finish, and if either the queue
393 is empty or we do not have a sequenced image at its head (if this is the
394 case we will never terminate as no new frames will be sent once
397 if (_finish && (!have_sequenced_image_at_queue_head() || _queue.empty())) {
398 /* (Hopefully temporarily) log anything that was not written */
399 if (!_queue.empty() && !have_sequenced_image_at_queue_head()) {
400 LOG_WARNING (N_("Finishing writer with a left-over queue of %1:"), _queue.size());
401 for (auto const& i: _queue) {
402 if (i.type == QueueItem::Type::FULL) {
403 LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
405 LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
412 /* Write any frames that we can write; i.e. those that are in sequence. */
413 while (have_sequenced_image_at_queue_head ()) {
414 auto qi = _queue.front ();
415 _last_written[qi.reel].update (qi);
417 if (qi.type == QueueItem::Type::FULL && qi.encoded) {
418 --_queued_full_in_memory;
423 auto& reel = _reels[qi.reel];
426 case QueueItem::Type::FULL:
427 LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
429 qi.encoded.reset (new ArrayData(film()->j2c_path(qi.reel, qi.frame, qi.eyes, false)));
431 reel.write (qi.encoded, qi.frame, qi.eyes);
434 case QueueItem::Type::FAKE:
435 LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
436 reel.fake_write (qi.size);
439 case QueueItem::Type::REPEAT:
440 LOG_DEBUG_ENCODE (N_("Writer REPEAT-writes %1"), qi.frame);
441 reel.repeat_write (qi.frame, qi.eyes);
447 _full_condition.notify_all ();
450 while (_queued_full_in_memory > _maximum_frames_in_memory) {
451 /* Too many frames in memory which can't yet be written to the stream.
452 Write some FULL frames to disk.
455 /* Find one from the back of the queue */
457 auto item = _queue.rbegin();
458 while (item != _queue.rend() && (item->type != QueueItem::Type::FULL || !item->encoded)) {
462 DCPOMATIC_ASSERT(item != _queue.rend());
464 /* For the log message below */
465 int const awaiting = _last_written[_queue.front().reel].frame() + 1;
468 /* i is valid here, even though we don't hold a lock on the mutex,
469 since list iterators are unaffected by insertion and only this
470 thread could erase the last item in the list.
473 LOG_GENERAL("Writer full; pushes %1 to disk while awaiting %2", item->frame, awaiting);
475 item->encoded->write_via_temp(
476 film()->j2c_path(item->reel, item->frame, item->eyes, true),
477 film()->j2c_path(item->reel, item->frame, item->eyes, false)
481 item->encoded.reset();
482 --_queued_full_in_memory;
483 _full_condition.notify_all ();
494 Writer::terminate_thread (bool can_throw)
496 boost::this_thread::disable_interruption dis;
498 boost::mutex::scoped_lock lock (_state_mutex);
501 _empty_condition.notify_all ();
502 _full_condition.notify_all ();
516 Writer::calculate_digests ()
518 auto job = _job.lock ();
520 job->sub (_("Computing digests"));
523 boost::asio::io_service service;
524 boost::thread_group pool;
526 auto work = make_shared<boost::asio::io_service::work>(service);
528 int const threads = max (1, Config::instance()->master_encoding_threads());
530 for (int i = 0; i < threads; ++i) {
531 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
534 std::function<void (int, int64_t, int64_t)> set_progress;
536 set_progress = boost::bind(&Writer::set_digest_progress, this, job.get(), _1, _2, _3);
538 set_progress = [](int, int64_t, int64_t) {
539 boost::this_thread::interruption_point();
545 for (auto& i: _reels) {
548 &ReelWriter::calculate_digests,
550 std::function<void (int64_t, int64_t)>(boost::bind(set_progress, index, _1, _2))
556 &Writer::calculate_referenced_digests,
558 std::function<void (int64_t, int64_t)>(boost::bind(set_progress, index, _1, _2))
565 } catch (boost::thread_interrupted) {
566 /* join_all was interrupted, so we need to interrupt the threads
567 * in our pool then try again to join them.
569 pool.interrupt_all ();
577 /** @param output_dcp Path to DCP folder to write */
579 Writer::finish (boost::filesystem::path output_dcp)
581 if (_thread.joinable()) {
582 LOG_GENERAL_NC ("Terminating writer thread");
583 terminate_thread (true);
586 LOG_GENERAL_NC ("Finishing ReelWriters");
588 for (auto& reel: _reels) {
589 write_hanging_text(reel);
590 reel.finish(output_dcp);
593 LOG_GENERAL_NC ("Writing XML");
595 dcp::DCP dcp (output_dcp);
597 auto cpl = make_shared<dcp::CPL>(
599 film()->dcp_content_type()->libdcp_kind(),
600 film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE
605 calculate_digests ();
609 for (auto& i: _reels) {
610 cpl->add(i.create_reel(_reel_assets, output_dcp, _have_subtitles, _have_closed_captions));
615 auto creator = Config::instance()->dcp_creator();
616 if (creator.empty()) {
617 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
620 auto issuer = Config::instance()->dcp_issuer();
621 if (issuer.empty()) {
622 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
625 cpl->set_creator (creator);
626 cpl->set_issuer (issuer);
628 cpl->set_ratings (film()->ratings());
630 vector<dcp::ContentVersion> cv;
631 for (auto i: film()->content_versions()) {
632 /* Make sure we don't end up writing an empty <LabelText> node as some validators
633 * complain about that.
635 cv.push_back(!i.empty() ? dcp::ContentVersion(i) : dcp::ContentVersion("1"));
638 cv = { dcp::ContentVersion("1") };
640 cpl->set_content_versions (cv);
642 cpl->set_full_content_title_text (film()->name());
643 cpl->set_full_content_title_text_language (film()->name_language());
644 if (film()->release_territory()) {
645 cpl->set_release_territory (*film()->release_territory());
647 cpl->set_version_number (film()->version_number());
648 cpl->set_status (film()->status());
649 if (film()->chain()) {
650 cpl->set_chain (*film()->chain());
652 if (film()->distributor()) {
653 cpl->set_distributor (*film()->distributor());
655 if (film()->facility()) {
656 cpl->set_facility (*film()->facility());
658 if (film()->luminance()) {
659 cpl->set_luminance (*film()->luminance());
661 if (film()->sign_language_video_language()) {
662 cpl->set_sign_language_video_language (*film()->sign_language_video_language());
665 dcp::MCASoundField field;
666 if (channel_is_mapped(film(), dcp::Channel::BSL) || channel_is_mapped(film(), dcp::Channel::BSR)) {
667 field = dcp::MCASoundField::SEVEN_POINT_ONE;
669 field = dcp::MCASoundField::FIVE_POINT_ONE;
672 auto const audio_channels = film()->audio_channels();
673 dcp::MainSoundConfiguration msc(field, audio_channels);
674 for (auto i: film()->mapped_audio_channels()) {
675 if (i < audio_channels) {
676 msc.set_mapping(i, static_cast<dcp::Channel>(i));
680 cpl->set_main_sound_configuration(msc);
681 cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
682 cpl->set_main_picture_stored_area (film()->frame_size());
684 auto active_area = film()->active_area();
685 if (active_area.width > 0 && active_area.height > 0) {
686 /* It's not allowed to have a zero active area width or height, and the sizes must be multiples of 2 */
687 cpl->set_main_picture_active_area({ active_area.width & ~1, active_area.height & ~1});
690 auto sl = film()->subtitle_languages().second;
692 cpl->set_additional_subtitle_languages(sl);
695 auto signer = Config::instance()->signer_chain();
696 /* We did check earlier, but check again here to be on the safe side */
698 if (!signer->valid (&reason)) {
699 throw InvalidSignerError (reason);
702 dcp.set_issuer(issuer);
703 dcp.set_creator(creator);
704 dcp.set_annotation_text(film()->dcp_name());
706 dcp.write_xml(signer, !film()->limit_to_smpte_bv20(), Config::instance()->dcp_metadata_filename_format());
709 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
712 write_cover_sheet (output_dcp);
717 Writer::write_cover_sheet (boost::filesystem::path output_dcp)
719 auto const cover = film()->file("COVER_SHEET.txt");
720 dcp::File file(cover, "w");
722 throw OpenFileError (cover, errno, OpenFileError::WRITE);
725 auto text = Config::instance()->cover_sheet ();
726 boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
727 auto cpls = film()->cpls();
729 boost::algorithm::replace_all (text, "$CPL_FILENAME", cpls[0].cpl_file.filename().string());
731 boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
732 boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
734 auto audio_language = film()->audio_language();
735 if (audio_language) {
736 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", audio_language->description());
738 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _("None"));
741 auto subtitle_languages = film()->subtitle_languages();
742 if (subtitle_languages.first) {
743 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.first->description());
745 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", _("None"));
748 boost::uintmax_t size = 0;
750 auto i = dcp::filesystem::recursive_directory_iterator(output_dcp);
751 i != dcp::filesystem::recursive_directory_iterator();
753 if (dcp::filesystem::is_regular_file(i->path())) {
754 size += dcp::filesystem::file_size(i->path());
758 if (size > (1000000000L)) {
759 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1GB", dcp::locale_convert<string>(size / 1000000000.0, 1, true)));
761 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1MB", dcp::locale_convert<string>(size / 1000000.0, 1, true)));
764 auto ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
765 auto description = String::compose("%1.%2", ch.first, ch.second);
767 if (description == "0.0") {
768 description = _("None");
769 } else if (description == "1.0") {
770 description = _("Mono");
771 } else if (description == "2.0") {
772 description = _("Stereo");
774 boost::algorithm::replace_all (text, "$AUDIO", description);
776 auto const hmsf = film()->length().split(film()->video_frame_rate());
778 if (hmsf.h == 0 && hmsf.m == 0) {
779 length = String::compose("%1s", hmsf.s);
780 } else if (hmsf.h == 0 && hmsf.m > 0) {
781 length = String::compose("%1m%2s", hmsf.m, hmsf.s);
782 } else if (hmsf.h > 0 && hmsf.m > 0) {
783 length = String::compose("%1h%2m%3s", hmsf.h, hmsf.m, hmsf.s);
786 boost::algorithm::replace_all (text, "$LENGTH", length);
788 file.checked_write(text.c_str(), text.length());
792 /** @param frame Frame index within the whole DCP.
793 * @return true if we can fake-write this frame.
796 Writer::can_fake_write (Frame frame) const
798 if (film()->encrypted()) {
799 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
803 /* We have to do a proper write of the first frame so that we can set up the JPEG2000
804 parameters in the asset writer.
807 auto const & reel = _reels[video_reel(frame)];
809 /* Make frame relative to the start of the reel */
810 frame -= reel.start ();
811 return (frame != 0 && frame < reel.first_nonexistent_frame());
815 /** @param track Closed caption track if type == TextType::CLOSED_CAPTION */
817 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
819 vector<ReelWriter>::iterator* reel = nullptr;
822 case TextType::OPEN_SUBTITLE:
823 reel = &_subtitle_reel;
824 _have_subtitles = true;
826 case TextType::CLOSED_CAPTION:
827 DCPOMATIC_ASSERT (track);
828 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
829 reel = &_caption_reels[*track];
830 _have_closed_captions.insert (*track);
833 DCPOMATIC_ASSERT (false);
836 DCPOMATIC_ASSERT (*reel != _reels.end());
837 while ((*reel)->period().to <= period.from) {
839 DCPOMATIC_ASSERT (*reel != _reels.end());
840 write_hanging_text (**reel);
843 auto back_off = [this](DCPTimePeriod period) {
844 period.to -= DCPTime::from_frames(2, film()->video_frame_rate());
848 if (period.to > (*reel)->period().to) {
849 /* This text goes off the end of the reel. Store parts of it that should go into
852 for (auto i = std::next(*reel); i != _reels.end(); ++i) {
853 auto overlap = i->period().overlap(period);
855 _hanging_texts.push_back (HangingText{text, type, track, back_off(*overlap)});
858 /* Back off from the reel boundary by a couple of frames to avoid tripping checks
859 * for subtitles being too close together.
861 period.to = (*reel)->period().to;
862 period = back_off(period);
865 (*reel)->write(text, type, track, period, _fonts, _chosen_interop_font);
870 Writer::write (vector<shared_ptr<Font>> fonts)
876 /* Fonts may come in with empty IDs but we don't want to put those in the DCP */
877 auto fix_id = [](string id) {
878 return id.empty() ? "font" : id;
881 if (film()->interop()) {
882 /* Interop will ignore second and subsequent <LoadFont>s so we don't want to
883 * even write them as they upset some validators. Set up _fonts so that every
884 * font used by any subtitle will be written with the same ID.
886 for (size_t i = 0; i < fonts.size(); ++i) {
887 _fonts.put(fonts[i], fix_id(fonts[0]->id()));
889 _chosen_interop_font = fonts[0];
891 for (auto font: fonts) {
892 _fonts.put(font, fix_id(font->id()));
899 operator< (QueueItem const & a, QueueItem const & b)
901 if (a.reel != b.reel) {
902 return a.reel < b.reel;
905 if (a.frame != b.frame) {
906 return a.frame < b.frame;
909 return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
914 operator== (QueueItem const & a, QueueItem const & b)
916 return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
921 Writer::set_encoder_threads (int threads)
923 boost::mutex::scoped_lock lm (_state_mutex);
924 _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
925 _maximum_queue_size = threads * 16;
930 Writer::write (ReferencedReelAsset asset)
932 _reel_assets.push_back (asset);
937 Writer::video_reel (int frame) const
939 auto t = DCPTime::from_frames (frame, film()->video_frame_rate());
940 size_t reel_index = 0;
941 while (reel_index < _reels.size() && !_reels[reel_index].period().contains(t)) {
945 DCPOMATIC_ASSERT(reel_index < _reels.size ());
950 /** Update job progress with information about the progress of a single digest calculation
952 * @param id Unique identifier for the thread whose progress has changed.
953 * @param done Number of bytes that this thread has processed.
954 * @param size Total number of bytes that this thread must process.
957 Writer::set_digest_progress(Job* job, int id, int64_t done, int64_t size)
959 boost::mutex::scoped_lock lm (_digest_progresses_mutex);
961 /* Update the progress for this thread */
962 _digest_progresses[id] = std::make_pair(done, size);
964 /* Get the total progress across all threads and use it to set job progress */
965 int64_t total_done = 0;
966 int64_t total_size = 0;
967 for (auto const& i: _digest_progresses) {
968 total_done += i.second.first;
969 total_size += i.second.second;
972 job->set_progress(float(total_done) / total_size);
977 boost::this_thread::interruption_point();
981 /** Calculate hashes for any referenced MXF assets which do not already have one */
983 Writer::calculate_referenced_digests(std::function<void (int64_t, int64_t)> set_progress)
986 int64_t total_size = 0;
987 for (auto const& i: _reel_assets) {
988 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
989 if (file && !file->hash()) {
990 auto filename = file->asset_ref().asset()->file();
991 DCPOMATIC_ASSERT(filename);
992 total_size += boost::filesystem::file_size(*filename);
996 int64_t total_done = 0;
997 for (auto const& i: _reel_assets) {
998 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
999 if (file && !file->hash()) {
1000 file->asset_ref().asset()->hash([&total_done, total_size, set_progress](int64_t done, int64_t) {
1001 set_progress(total_done + done, total_size);
1003 total_done += boost::filesystem::file_size(*file->asset_ref().asset()->file());
1004 file->set_hash (file->asset_ref().asset()->hash());
1007 } catch (boost::thread_interrupted) {
1008 /* set_progress contains an interruption_point, so any of these methods
1009 * may throw thread_interrupted, at which point we just give up.
1015 Writer::write_hanging_text (ReelWriter& reel)
1017 vector<HangingText> new_hanging_texts;
1018 for (auto i: _hanging_texts) {
1019 if (i.period.from == reel.period().from) {
1020 reel.write(i.text, i.type, i.track, i.period, _fonts, _chosen_interop_font);
1022 new_hanging_texts.push_back (i);
1025 _hanging_texts = new_hanging_texts;