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"
27 #include "dcp_content_type.h"
28 #include "dcp_video.h"
29 #include "dcpomatic_log.h"
34 #include "reel_writer.h"
35 #include "text_content.h"
40 #include <dcp/locale_convert.h>
41 #include <dcp/raw_convert.h>
42 #include <dcp/reel_file_asset.h>
50 /* OS X strikes again */
55 using std::dynamic_pointer_cast;
56 using std::make_shared;
59 using std::shared_ptr;
64 using boost::optional;
65 #if BOOST_VERSION >= 106100
66 using namespace boost::placeholders;
70 using namespace dcpomatic;
73 /** @param j Job to report progress to, or 0.
74 * @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
76 Writer::Writer (weak_ptr<const Film> weak_film, weak_ptr<Job> j, bool text_only)
77 : WeakConstFilm (weak_film)
79 /* These will be reset to sensible values when J2KEncoder is created */
80 , _maximum_frames_in_memory (8)
81 , _maximum_queue_size (8)
82 , _text_only (text_only)
84 auto job = _job.lock ();
87 auto const reels = film()->reels();
89 _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
92 _last_written.resize (reels.size());
94 /* We can keep track of the current audio, subtitle and closed caption reels easily because audio
95 and captions arrive to the Writer in sequence. This is not so for video.
97 _audio_reel = _reels.begin ();
98 _subtitle_reel = _reels.begin ();
99 for (auto i: film()->closed_caption_tracks()) {
100 _caption_reels[i] = _reels.begin ();
102 _atmos_reel = _reels.begin ();
104 /* Check that the signer is OK */
106 if (!Config::instance()->signer_chain()->valid(&reason)) {
107 throw InvalidSignerError (reason);
116 _thread = boost::thread (boost::bind(&Writer::thread, this));
117 #ifdef DCPOMATIC_LINUX
118 pthread_setname_np (_thread.native_handle(), "writer");
127 terminate_thread (false);
132 /** Pass a video frame to the writer for writing to disk at some point.
133 * This method can be called with frames out of order.
134 * @param encoded JPEG2000-encoded data.
135 * @param frame Frame index within the DCP.
136 * @param eyes Eyes that this frame image is for.
139 Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
141 boost::mutex::scoped_lock lock (_state_mutex);
143 while (_queued_full_in_memory > _maximum_frames_in_memory) {
144 /* There are too many full frames in memory; wake the main writer thread and
145 wait until it sorts everything out */
146 _empty_condition.notify_all ();
147 _full_condition.wait (lock);
151 qi.type = QueueItem::Type::FULL;
152 qi.encoded = encoded;
153 qi.reel = video_reel (frame);
154 qi.frame = frame - _reels[qi.reel].start ();
156 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
159 _queue.push_back(qi);
160 ++_queued_full_in_memory;
162 /* Now there's something to do: wake anything wait()ing on _empty_condition */
163 _empty_condition.notify_all ();
168 Writer::can_repeat (Frame frame) const
170 return frame > _reels[video_reel(frame)].start();
174 /** Repeat the last frame that was written to a reel as a new frame.
175 * @param frame Frame index within the DCP of the new (repeated) frame.
176 * @param eyes Eyes that this repeated frame image is for.
179 Writer::repeat (Frame frame, Eyes eyes)
181 boost::mutex::scoped_lock lock (_state_mutex);
183 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
184 /* The queue is too big, and the main writer thread can run and fix it, so
185 wake it and wait until it has done.
187 _empty_condition.notify_all ();
188 _full_condition.wait (lock);
192 qi.type = QueueItem::Type::REPEAT;
193 qi.reel = video_reel (frame);
194 qi.frame = frame - _reels[qi.reel].start ();
195 if (film()->three_d() && eyes == Eyes::BOTH) {
196 qi.eyes = Eyes::LEFT;
197 _queue.push_back (qi);
198 qi.eyes = Eyes::RIGHT;
199 _queue.push_back (qi);
202 _queue.push_back (qi);
205 /* Now there's something to do: wake anything wait()ing on _empty_condition */
206 _empty_condition.notify_all ();
211 Writer::fake_write (Frame frame, Eyes eyes)
213 boost::mutex::scoped_lock lock (_state_mutex);
215 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
216 /* The queue is too big, and the main writer thread can run and fix it, so
217 wake it and wait until it has done.
219 _empty_condition.notify_all ();
220 _full_condition.wait (lock);
223 size_t const reel = video_reel (frame);
224 Frame const frame_in_reel = frame - _reels[reel].start ();
227 qi.type = QueueItem::Type::FAKE;
230 shared_ptr<InfoFileHandle> info_file = film()->info_file_handle(_reels[reel].period(), true);
231 qi.size = _reels[reel].read_frame_info(info_file, frame_in_reel, eyes).size;
234 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
237 qi.frame = frame_in_reel;
239 _queue.push_back(qi);
241 /* Now there's something to do: wake anything wait()ing on _empty_condition */
242 _empty_condition.notify_all ();
246 /** Write some audio frames to the DCP.
247 * @param audio Audio data.
248 * @param time Time of this data within the DCP.
249 * This method is not thread safe.
252 Writer::write (shared_ptr<const AudioBuffers> audio, DCPTime const time)
254 DCPOMATIC_ASSERT (audio);
256 int const afr = film()->audio_frame_rate();
258 DCPTime const end = time + DCPTime::from_frames(audio->frames(), afr);
260 /* The audio we get might span a reel boundary, and if so we have to write it in bits */
265 if (_audio_reel == _reels.end ()) {
266 /* This audio is off the end of the last reel; ignore it */
270 if (end <= _audio_reel->period().to) {
271 /* Easy case: we can write all the audio to this reel */
272 _audio_reel->write (audio);
274 } else if (_audio_reel->period().to <= t) {
275 /* This reel is entirely before the start of our audio; just skip the reel */
278 /* This audio is over a reel boundary; split the audio into two and write the first part */
279 DCPTime part_lengths[2] = {
280 _audio_reel->period().to - t,
281 end - _audio_reel->period().to
284 /* Be careful that part_lengths[0] + part_lengths[1] can't be bigger than audio->frames() */
285 Frame part_frames[2] = {
286 part_lengths[0].frames_ceil(afr),
287 part_lengths[1].frames_floor(afr)
290 DCPOMATIC_ASSERT ((part_frames[0] + part_frames[1]) <= audio->frames());
292 if (part_frames[0]) {
293 auto part = make_shared<AudioBuffers>(audio, part_frames[0], 0);
294 _audio_reel->write (part);
297 if (part_frames[1]) {
298 audio = make_shared<AudioBuffers>(audio, part_frames[1], part_frames[0]);
304 t += part_lengths[0];
311 Writer::write (shared_ptr<const dcp::AtmosFrame> atmos, DCPTime time, AtmosMetadata metadata)
313 if (_atmos_reel->period().to == time) {
315 DCPOMATIC_ASSERT (_atmos_reel != _reels.end());
318 /* We assume that we get a video frame's worth of data here */
319 _atmos_reel->write (atmos, metadata);
323 /** Caller must hold a lock on _state_mutex */
325 Writer::have_sequenced_image_at_queue_head ()
327 if (_queue.empty ()) {
332 auto const & f = _queue.front();
333 return _last_written[f.reel].next(f);
338 Writer::LastWritten::next (QueueItem qi) const
340 if (qi.eyes == Eyes::BOTH) {
342 return qi.frame == (_frame + 1);
347 if (_eyes == Eyes::LEFT && qi.frame == _frame && qi.eyes == Eyes::RIGHT) {
351 if (_eyes == Eyes::RIGHT && qi.frame == (_frame + 1) && qi.eyes == Eyes::LEFT) {
360 Writer::LastWritten::update (QueueItem qi)
371 start_of_thread ("Writer");
375 boost::mutex::scoped_lock lock (_state_mutex);
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 */
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());
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
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 for (auto const& i: _queue) {
400 if (i.type == QueueItem::Type::FULL) {
401 LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
403 LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
410 /* Write any frames that we can write; i.e. those that are in sequence. */
411 while (have_sequenced_image_at_queue_head ()) {
412 auto qi = _queue.front ();
413 _last_written[qi.reel].update (qi);
415 if (qi.type == QueueItem::Type::FULL && qi.encoded) {
416 --_queued_full_in_memory;
421 auto& reel = _reels[qi.reel];
424 case QueueItem::Type::FULL:
425 LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
427 qi.encoded.reset (new ArrayData(film()->j2c_path(qi.reel, qi.frame, qi.eyes, false)));
429 reel.write (qi.encoded, qi.frame, qi.eyes);
432 case QueueItem::Type::FAKE:
433 LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
434 reel.fake_write (qi.size);
437 case QueueItem::Type::REPEAT:
438 LOG_DEBUG_ENCODE (N_("Writer REPEAT-writes %1"), qi.frame);
439 reel.repeat_write (qi.frame, qi.eyes);
445 _full_condition.notify_all ();
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.
453 /* Find one from the back of the queue */
455 auto i = _queue.rbegin ();
456 while (i != _queue.rend() && (i->type != QueueItem::Type::FULL || !i->encoded)) {
460 DCPOMATIC_ASSERT (i != _queue.rend());
462 /* For the log message below */
463 int const awaiting = _last_written[_queue.front().reel].frame() + 1;
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.
471 LOG_GENERAL ("Writer full; pushes %1 to disk while awaiting %2", i->frame, awaiting);
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)
480 --_queued_full_in_memory;
481 _full_condition.notify_all ();
492 Writer::terminate_thread (bool can_throw)
494 boost::this_thread::disable_interruption dis;
496 boost::mutex::scoped_lock lock (_state_mutex);
499 _empty_condition.notify_all ();
500 _full_condition.notify_all ();
514 Writer::calculate_digests ()
516 auto job = _job.lock ();
518 job->sub (_("Computing digests"));
521 boost::asio::io_service service;
522 boost::thread_group pool;
524 auto work = make_shared<boost::asio::io_service::work>(service);
526 int const threads = max (1, Config::instance()->master_encoding_threads());
528 for (int i = 0; i < threads; ++i) {
529 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
532 std::function<void (float)> set_progress;
534 set_progress = boost::bind (&Writer::set_digest_progress, this, job.get(), _1);
536 set_progress = [](float) {
537 boost::this_thread::interruption_point();
541 for (auto& i: _reels) {
542 service.post (boost::bind (&ReelWriter::calculate_digests, &i, set_progress));
544 service.post (boost::bind (&Writer::calculate_referenced_digests, this, set_progress));
550 } catch (boost::thread_interrupted) {
551 /* join_all was interrupted, so we need to interrupt the threads
552 * in our pool then try again to join them.
554 pool.interrupt_all ();
562 /** @param output_dcp Path to DCP folder to write */
564 Writer::finish (boost::filesystem::path output_dcp)
566 if (_thread.joinable()) {
567 LOG_GENERAL_NC ("Terminating writer thread");
568 terminate_thread (true);
571 LOG_GENERAL_NC ("Finishing ReelWriters");
573 for (auto& i: _reels) {
574 write_hanging_text (i);
575 i.finish (output_dcp);
578 LOG_GENERAL_NC ("Writing XML");
580 dcp::DCP dcp (output_dcp);
582 auto cpl = make_shared<dcp::CPL>(
584 film()->dcp_content_type()->libdcp_kind(),
585 film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE
590 calculate_digests ();
594 for (auto& i: _reels) {
595 cpl->add (i.create_reel(_reel_assets, _fonts, _chosen_interop_font, output_dcp, _have_subtitles, _have_closed_captions));
600 auto creator = Config::instance()->dcp_creator();
601 if (creator.empty()) {
602 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
605 auto issuer = Config::instance()->dcp_issuer();
606 if (issuer.empty()) {
607 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
610 cpl->set_creator (creator);
611 cpl->set_issuer (issuer);
613 cpl->set_ratings (film()->ratings());
615 vector<dcp::ContentVersion> cv;
616 for (auto i: film()->content_versions()) {
617 cv.push_back (dcp::ContentVersion(i));
620 cv = { dcp::ContentVersion("1") };
622 cpl->set_content_versions (cv);
624 cpl->set_full_content_title_text (film()->name());
625 cpl->set_full_content_title_text_language (film()->name_language());
626 if (film()->release_territory()) {
627 cpl->set_release_territory (*film()->release_territory());
629 cpl->set_version_number (film()->version_number());
630 cpl->set_status (film()->status());
631 if (film()->chain()) {
632 cpl->set_chain (*film()->chain());
634 if (film()->distributor()) {
635 cpl->set_distributor (*film()->distributor());
637 if (film()->facility()) {
638 cpl->set_facility (*film()->facility());
640 if (film()->luminance()) {
641 cpl->set_luminance (*film()->luminance());
643 if (film()->sign_language_video_language()) {
644 cpl->set_sign_language_video_language (*film()->sign_language_video_language());
647 dcp::MCASoundField field;
648 if (film()->audio_channels() <= 6) {
649 field = dcp::MCASoundField::FIVE_POINT_ONE;
651 field = dcp::MCASoundField::SEVEN_POINT_ONE;
654 dcp::MainSoundConfiguration msc (field, film()->audio_channels());
655 for (auto i: film()->mapped_audio_channels()) {
656 if (static_cast<int>(i) < film()->audio_channels()) {
657 msc.set_mapping (i, static_cast<dcp::Channel>(i));
661 cpl->set_main_sound_configuration (msc.to_string());
662 cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
663 cpl->set_main_picture_stored_area (film()->frame_size());
665 auto active_area = film()->active_area();
666 if (active_area.width > 0 && active_area.height > 0) {
667 /* It's not allowed to have a zero active area width or height, and the sizes must be multiples of 2 */
668 cpl->set_main_picture_active_area({ active_area.width & ~1, active_area.height & ~1});
671 auto sl = film()->subtitle_languages().second;
673 cpl->set_additional_subtitle_languages(sl);
676 auto signer = Config::instance()->signer_chain();
677 /* We did check earlier, but check again here to be on the safe side */
679 if (!signer->valid (&reason)) {
680 throw InvalidSignerError (reason);
683 dcp.set_issuer(issuer);
684 dcp.set_creator(creator);
685 dcp.set_annotation_text(film()->dcp_name());
687 dcp.write_xml(signer, !film()->limit_to_smpte_bv20(), Config::instance()->dcp_metadata_filename_format());
690 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
693 write_cover_sheet (output_dcp);
698 Writer::write_cover_sheet (boost::filesystem::path output_dcp)
700 auto const cover = film()->file("COVER_SHEET.txt");
701 dcp::File f(cover, "w");
703 throw OpenFileError (cover, errno, OpenFileError::WRITE);
706 auto text = Config::instance()->cover_sheet ();
707 boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
708 auto cpls = film()->cpls();
710 boost::algorithm::replace_all (text, "$CPL_FILENAME", cpls[0].cpl_file.filename().string());
712 boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
713 boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
715 auto audio_language = film()->audio_language();
716 if (audio_language) {
717 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", audio_language->description());
719 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _("None"));
722 auto subtitle_languages = film()->subtitle_languages();
723 if (subtitle_languages.first) {
724 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.first->description());
726 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", _("None"));
729 boost::uintmax_t size = 0;
731 auto i = boost::filesystem::recursive_directory_iterator(output_dcp);
732 i != boost::filesystem::recursive_directory_iterator();
734 if (boost::filesystem::is_regular_file (i->path())) {
735 size += boost::filesystem::file_size (i->path());
739 if (size > (1000000000L)) {
740 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1GB", dcp::locale_convert<string>(size / 1000000000.0, 1, true)));
742 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1MB", dcp::locale_convert<string>(size / 1000000.0, 1, true)));
745 auto ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
746 auto description = String::compose("%1.%2", ch.first, ch.second);
748 if (description == "0.0") {
749 description = _("None");
750 } else if (description == "1.0") {
751 description = _("Mono");
752 } else if (description == "2.0") {
753 description = _("Stereo");
755 boost::algorithm::replace_all (text, "$AUDIO", description);
757 auto const hmsf = film()->length().split(film()->video_frame_rate());
759 if (hmsf.h == 0 && hmsf.m == 0) {
760 length = String::compose("%1s", hmsf.s);
761 } else if (hmsf.h == 0 && hmsf.m > 0) {
762 length = String::compose("%1m%2s", hmsf.m, hmsf.s);
763 } else if (hmsf.h > 0 && hmsf.m > 0) {
764 length = String::compose("%1h%2m%3s", hmsf.h, hmsf.m, hmsf.s);
767 boost::algorithm::replace_all (text, "$LENGTH", length);
769 f.checked_write(text.c_str(), text.length());
773 /** @param frame Frame index within the whole DCP.
774 * @return true if we can fake-write this frame.
777 Writer::can_fake_write (Frame frame) const
779 if (film()->encrypted()) {
780 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
784 /* We have to do a proper write of the first frame so that we can set up the JPEG2000
785 parameters in the asset writer.
788 auto const & reel = _reels[video_reel(frame)];
790 /* Make frame relative to the start of the reel */
791 frame -= reel.start ();
792 return (frame != 0 && frame < reel.first_nonexistent_frame());
796 /** @param track Closed caption track if type == TextType::CLOSED_CAPTION */
798 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
800 vector<ReelWriter>::iterator* reel = nullptr;
803 case TextType::OPEN_SUBTITLE:
804 reel = &_subtitle_reel;
805 _have_subtitles = true;
807 case TextType::CLOSED_CAPTION:
808 DCPOMATIC_ASSERT (track);
809 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
810 reel = &_caption_reels[*track];
811 _have_closed_captions.insert (*track);
814 DCPOMATIC_ASSERT (false);
817 DCPOMATIC_ASSERT (*reel != _reels.end());
818 while ((*reel)->period().to <= period.from) {
820 DCPOMATIC_ASSERT (*reel != _reels.end());
821 write_hanging_text (**reel);
824 auto back_off = [this](DCPTimePeriod period) {
825 period.to -= DCPTime::from_frames(2, film()->video_frame_rate());
829 if (period.to > (*reel)->period().to) {
830 /* This text goes off the end of the reel. Store parts of it that should go into
833 for (auto i = std::next(*reel); i != _reels.end(); ++i) {
834 auto overlap = i->period().overlap(period);
836 _hanging_texts.push_back (HangingText{text, type, track, back_off(*overlap)});
839 /* Back off from the reel boundary by a couple of frames to avoid tripping checks
840 * for subtitles being too close together.
842 period.to = (*reel)->period().to;
843 period = back_off(period);
846 (*reel)->write(text, type, track, period, _fonts);
851 Writer::write (vector<shared_ptr<Font>> fonts)
857 /* Fonts may come in with empty IDs but we don't want to put those in the DCP */
858 auto fix_id = [](string id) {
859 return id.empty() ? "font" : id;
862 if (film()->interop()) {
863 /* Interop will ignore second and subsequent <LoadFont>s so we don't want to
864 * even write them as they upset some validators. Set up _fonts so that every
865 * font used by any subtitle will be written with the same ID.
867 for (size_t i = 0; i < fonts.size(); ++i) {
868 _fonts.put(fonts[i], fix_id(fonts[0]->id()));
870 _chosen_interop_font = fonts[0];
872 set<string> used_ids;
874 /* Return the index of a _N at the end of a string, or string::npos */
875 auto underscore_number_position = [](string s) {
876 auto last_underscore = s.find_last_of("_");
877 if (last_underscore == string::npos) {
881 for (auto i = last_underscore + 1; i < s.size(); ++i) {
882 if (!isdigit(s[i])) {
887 return last_underscore;
890 /* Write fonts to _fonts, changing any duplicate IDs so that they are unique */
891 for (auto font: fonts) {
892 auto id = fix_id(font->id());
893 if (used_ids.find(id) == used_ids.end()) {
894 /* This ID is unique so we can just use it as-is */
895 _fonts.put(font, id);
898 auto end = underscore_number_position(id);
899 if (end == string::npos) {
900 /* This string has no _N suffix, so add one */
902 end = underscore_number_position(id);
907 /* Increment the suffix until we find a unique one */
908 auto number = dcp::raw_convert<int>(id.substr(end));
909 while (used_ids.find(id) != used_ids.end()) {
911 id = String::compose("%1_%2", id.substr(0, end - 1), number);
915 _fonts.put(font, id);
918 DCPOMATIC_ASSERT(_fonts.map().size() == used_ids.size());
924 operator< (QueueItem const & a, QueueItem const & b)
926 if (a.reel != b.reel) {
927 return a.reel < b.reel;
930 if (a.frame != b.frame) {
931 return a.frame < b.frame;
934 return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
939 operator== (QueueItem const & a, QueueItem const & b)
941 return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
946 Writer::set_encoder_threads (int threads)
948 boost::mutex::scoped_lock lm (_state_mutex);
949 _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
950 _maximum_queue_size = threads * 16;
955 Writer::write (ReferencedReelAsset asset)
957 _reel_assets.push_back (asset);
962 Writer::video_reel (int frame) const
964 auto t = DCPTime::from_frames (frame, film()->video_frame_rate());
966 while (i < _reels.size() && !_reels[i].period().contains (t)) {
970 DCPOMATIC_ASSERT (i < _reels.size ());
976 Writer::set_digest_progress (Job* job, float progress)
978 boost::mutex::scoped_lock lm (_digest_progresses_mutex);
980 _digest_progresses[boost::this_thread::get_id()] = progress;
981 float min_progress = FLT_MAX;
982 for (auto const& i: _digest_progresses) {
983 min_progress = min (min_progress, i.second);
986 job->set_progress (min_progress);
991 boost::this_thread::interruption_point();
995 /** Calculate hashes for any referenced MXF assets which do not already have one */
997 Writer::calculate_referenced_digests (std::function<void (float)> set_progress)
1000 for (auto const& i: _reel_assets) {
1001 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
1002 if (file && !file->hash()) {
1003 file->asset_ref().asset()->hash (set_progress);
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);
1022 new_hanging_texts.push_back (i);
1025 _hanging_texts = new_hanging_texts;