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"
33 #include "frame_info.h"
37 #include "reel_writer.h"
38 #include "text_content.h"
43 #include <dcp/locale_convert.h>
44 #include <dcp/raw_convert.h>
45 #include <dcp/reel_closed_caption_asset.h>
46 #include <dcp/reel_file_asset.h>
47 #include <dcp/reel_subtitle_asset.h>
55 /* OS X strikes again */
60 using std::dynamic_pointer_cast;
61 using std::make_shared;
64 using std::shared_ptr;
69 using boost::optional;
70 #if BOOST_VERSION >= 106100
71 using namespace boost::placeholders;
75 using namespace dcpomatic;
78 /** @param weak_job Job to report progress to, or 0.
79 * @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
81 Writer::Writer(weak_ptr<const Film> weak_film, weak_ptr<Job> weak_job, bool text_only)
82 : WeakConstFilm (weak_film)
84 /* These will be reset to sensible values when J2KEncoder is created */
85 , _maximum_frames_in_memory (8)
86 , _maximum_queue_size (8)
87 , _text_only (text_only)
89 auto job = _job.lock ();
92 auto const reels = film()->reels();
94 _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
97 _last_written.resize (reels.size());
99 /* We can keep track of the current audio, subtitle and closed caption reels easily because audio
100 and captions arrive to the Writer in sequence. This is not so for video.
102 _audio_reel = _reels.begin ();
103 _subtitle_reel = _reels.begin ();
104 for (auto i: film()->closed_caption_tracks()) {
105 _caption_reels[i] = _reels.begin ();
107 _atmos_reel = _reels.begin ();
109 /* Check that the signer is OK */
111 if (!Config::instance()->signer_chain()->valid(&reason)) {
112 throw InvalidSignerError (reason);
121 _thread = boost::thread (boost::bind(&Writer::thread, this));
122 #ifdef DCPOMATIC_LINUX
123 pthread_setname_np (_thread.native_handle(), "writer");
132 terminate_thread (false);
137 /** Pass a video frame to the writer for writing to disk at some point.
138 * This method can be called with frames out of order.
139 * @param encoded JPEG2000-encoded data.
140 * @param frame Frame index within the DCP.
141 * @param eyes Eyes that this frame image is for.
144 Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
146 boost::mutex::scoped_lock lock (_state_mutex);
148 while (_queued_full_in_memory > _maximum_frames_in_memory) {
149 /* There are too many full frames in memory; wake the main writer thread and
150 wait until it sorts everything out */
151 _empty_condition.notify_all ();
152 _full_condition.wait (lock);
156 qi.type = QueueItem::Type::FULL;
157 qi.encoded = encoded;
158 qi.reel = video_reel (frame);
159 qi.frame = frame - _reels[qi.reel].start ();
161 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
164 _queue.push_back(qi);
165 ++_queued_full_in_memory;
167 /* Now there's something to do: wake anything wait()ing on _empty_condition */
168 _empty_condition.notify_all ();
173 Writer::can_repeat (Frame frame) const
175 return frame > _reels[video_reel(frame)].start();
179 /** Repeat the last frame that was written to a reel as a new frame.
180 * @param frame Frame index within the DCP of the new (repeated) frame.
181 * @param eyes Eyes that this repeated frame image is for.
184 Writer::repeat (Frame frame, Eyes eyes)
186 boost::mutex::scoped_lock lock (_state_mutex);
188 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
189 /* The queue is too big, and the main writer thread can run and fix it, so
190 wake it and wait until it has done.
192 _empty_condition.notify_all ();
193 _full_condition.wait (lock);
197 qi.type = QueueItem::Type::REPEAT;
198 qi.reel = video_reel (frame);
199 qi.frame = frame - _reels[qi.reel].start ();
200 if (film()->three_d() && eyes == Eyes::BOTH) {
201 qi.eyes = Eyes::LEFT;
202 _queue.push_back (qi);
203 qi.eyes = Eyes::RIGHT;
204 _queue.push_back (qi);
207 _queue.push_back (qi);
210 /* Now there's something to do: wake anything wait()ing on _empty_condition */
211 _empty_condition.notify_all ();
216 Writer::fake_write (Frame frame, Eyes eyes)
218 boost::mutex::scoped_lock lock (_state_mutex);
220 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
221 /* The queue is too big, and the main writer thread can run and fix it, so
222 wake it and wait until it has done.
224 _empty_condition.notify_all ();
225 _full_condition.wait (lock);
228 size_t const reel = video_reel (frame);
229 Frame const frame_in_reel = frame - _reels[reel].start ();
232 qi.type = QueueItem::Type::FAKE;
233 qi.size = J2KFrameInfo(film()->info_file_handle(_reels[reel].period(), true), frame_in_reel, eyes).size;
235 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
238 qi.frame = frame_in_reel;
240 _queue.push_back(qi);
242 /* Now there's something to do: wake anything wait()ing on _empty_condition */
243 _empty_condition.notify_all ();
247 /** Write some audio frames to the DCP.
248 * @param audio Audio data.
249 * @param time Time of this data within the DCP.
250 * This method is not thread safe.
253 Writer::write (shared_ptr<const AudioBuffers> audio, DCPTime const time)
255 DCPOMATIC_ASSERT (audio);
257 int const afr = film()->audio_frame_rate();
259 DCPTime const end = time + DCPTime::from_frames(audio->frames(), afr);
261 /* The audio we get might span a reel boundary, and if so we have to write it in bits */
266 if (_audio_reel == _reels.end ()) {
267 /* This audio is off the end of the last reel; ignore it */
271 if (end <= _audio_reel->period().to) {
272 /* Easy case: we can write all the audio to this reel */
273 _audio_reel->write (audio);
275 } else if (_audio_reel->period().to <= t) {
276 /* This reel is entirely before the start of our audio; just skip the reel */
279 /* This audio is over a reel boundary; split the audio into two and write the first part */
280 DCPTime part_lengths[2] = {
281 _audio_reel->period().to - t,
282 end - _audio_reel->period().to
285 /* Be careful that part_lengths[0] + part_lengths[1] can't be bigger than audio->frames() */
286 Frame part_frames[2] = {
287 part_lengths[0].frames_ceil(afr),
288 part_lengths[1].frames_floor(afr)
291 DCPOMATIC_ASSERT ((part_frames[0] + part_frames[1]) <= audio->frames());
293 if (part_frames[0]) {
294 auto part = make_shared<AudioBuffers>(audio, part_frames[0], 0);
295 _audio_reel->write (part);
298 if (part_frames[1]) {
299 audio = make_shared<AudioBuffers>(audio, part_frames[1], part_frames[0]);
305 t += part_lengths[0];
312 Writer::write (shared_ptr<const dcp::AtmosFrame> atmos, DCPTime time, AtmosMetadata metadata)
314 if (_atmos_reel->period().to == time) {
316 DCPOMATIC_ASSERT (_atmos_reel != _reels.end());
319 /* We assume that we get a video frame's worth of data here */
320 _atmos_reel->write (atmos, metadata);
324 /** Caller must hold a lock on _state_mutex */
326 Writer::have_sequenced_image_at_queue_head ()
328 if (_queue.empty ()) {
333 auto const & f = _queue.front();
334 return _last_written[f.reel].next(f);
339 Writer::LastWritten::next (QueueItem qi) const
341 if (qi.eyes == Eyes::BOTH) {
343 return qi.frame == (_frame + 1);
348 if (_eyes == Eyes::LEFT && qi.frame == _frame && qi.eyes == Eyes::RIGHT) {
352 if (_eyes == Eyes::RIGHT && qi.frame == (_frame + 1) && qi.eyes == Eyes::LEFT) {
361 Writer::LastWritten::update (QueueItem qi)
372 start_of_thread ("Writer");
376 boost::mutex::scoped_lock lock (_state_mutex);
380 if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
381 /* We've got something to do: go and do it */
385 /* Nothing to do: wait until something happens which may indicate that we do */
386 LOG_TIMING (N_("writer-sleep queue=%1"), _queue.size());
387 _empty_condition.wait (lock);
388 LOG_TIMING (N_("writer-wake queue=%1"), _queue.size());
391 /* We stop here if we have been asked to finish, and if either the queue
392 is empty or we do not have a sequenced image at its head (if this is the
393 case we will never terminate as no new frames will be sent once
396 if (_finish && (!have_sequenced_image_at_queue_head() || _queue.empty())) {
397 /* (Hopefully temporarily) log anything that was not written */
398 if (!_queue.empty() && !have_sequenced_image_at_queue_head()) {
399 LOG_WARNING (N_("Finishing writer with a left-over queue of %1:"), _queue.size());
400 for (auto const& i: _queue) {
401 if (i.type == QueueItem::Type::FULL) {
402 LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
404 LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
411 /* Write any frames that we can write; i.e. those that are in sequence. */
412 while (have_sequenced_image_at_queue_head ()) {
413 auto qi = _queue.front ();
414 _last_written[qi.reel].update (qi);
416 if (qi.type == QueueItem::Type::FULL && qi.encoded) {
417 --_queued_full_in_memory;
422 auto& reel = _reels[qi.reel];
425 case QueueItem::Type::FULL:
426 LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
428 qi.encoded.reset (new ArrayData(film()->j2c_path(qi.reel, qi.frame, qi.eyes, false)));
430 reel.write (qi.encoded, qi.frame, qi.eyes);
433 case QueueItem::Type::FAKE:
434 LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
435 reel.fake_write (qi.size);
438 case QueueItem::Type::REPEAT:
439 LOG_DEBUG_ENCODE (N_("Writer REPEAT-writes %1"), qi.frame);
440 reel.repeat_write (qi.frame, qi.eyes);
446 _full_condition.notify_all ();
449 while (_queued_full_in_memory > _maximum_frames_in_memory) {
450 /* Too many frames in memory which can't yet be written to the stream.
451 Write some FULL frames to disk.
454 /* Find one from the back of the queue */
456 auto item = _queue.rbegin();
457 while (item != _queue.rend() && (item->type != QueueItem::Type::FULL || !item->encoded)) {
461 DCPOMATIC_ASSERT(item != _queue.rend());
463 /* For the log message below */
464 int const awaiting = _last_written[_queue.front().reel].frame() + 1;
467 /* i is valid here, even though we don't hold a lock on the mutex,
468 since list iterators are unaffected by insertion and only this
469 thread could erase the last item in the list.
472 LOG_GENERAL("Writer full; pushes %1 to disk while awaiting %2", item->frame, awaiting);
474 item->encoded->write_via_temp(
475 film()->j2c_path(item->reel, item->frame, item->eyes, true),
476 film()->j2c_path(item->reel, item->frame, item->eyes, false)
480 item->encoded.reset();
481 --_queued_full_in_memory;
482 _full_condition.notify_all ();
493 Writer::terminate_thread (bool can_throw)
495 boost::this_thread::disable_interruption dis;
497 boost::mutex::scoped_lock lock (_state_mutex);
500 _empty_condition.notify_all ();
501 _full_condition.notify_all ();
515 Writer::calculate_digests ()
517 auto job = _job.lock ();
519 job->sub (_("Computing digests"));
522 boost::asio::io_service service;
523 boost::thread_group pool;
525 auto work = make_shared<boost::asio::io_service::work>(service);
527 int const threads = max (1, Config::instance()->master_encoding_threads());
529 for (int i = 0; i < threads; ++i) {
530 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
533 std::function<void (int, int64_t, int64_t)> set_progress;
535 set_progress = boost::bind(&Writer::set_digest_progress, this, job.get(), _1, _2, _3);
537 set_progress = [](int, int64_t, int64_t) {
538 boost::this_thread::interruption_point();
544 for (auto& i: _reels) {
547 &ReelWriter::calculate_digests,
549 std::function<void (int64_t, int64_t)>(boost::bind(set_progress, index, _1, _2))
555 &Writer::calculate_referenced_digests,
557 std::function<void (int64_t, int64_t)>(boost::bind(set_progress, index, _1, _2))
564 } catch (boost::thread_interrupted) {
565 /* join_all was interrupted, so we need to interrupt the threads
566 * in our pool then try again to join them.
568 pool.interrupt_all ();
576 /** @param output_dcp Path to DCP folder to write */
578 Writer::finish (boost::filesystem::path output_dcp)
580 if (_thread.joinable()) {
581 LOG_GENERAL_NC ("Terminating writer thread");
582 terminate_thread (true);
585 LOG_GENERAL_NC ("Finishing ReelWriters");
587 for (auto& reel: _reels) {
588 write_hanging_text(reel);
589 reel.finish(output_dcp);
592 LOG_GENERAL_NC ("Writing XML");
594 dcp::DCP dcp (output_dcp);
596 auto cpl = make_shared<dcp::CPL>(
598 film()->dcp_content_type()->libdcp_kind(),
599 film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE
604 calculate_digests ();
608 for (auto& i: _reels) {
609 cpl->add(i.create_reel(_reel_assets, output_dcp, _have_subtitles, _have_closed_captions));
614 auto creator = Config::instance()->dcp_creator();
615 if (creator.empty()) {
616 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
619 auto issuer = Config::instance()->dcp_issuer();
620 if (issuer.empty()) {
621 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
624 cpl->set_creator (creator);
625 cpl->set_issuer (issuer);
627 cpl->set_ratings (film()->ratings());
629 vector<dcp::ContentVersion> cv;
630 for (auto i: film()->content_versions()) {
631 /* Make sure we don't end up writing an empty <LabelText> node as some validators
632 * complain about that.
634 cv.push_back(!i.empty() ? dcp::ContentVersion(i) : dcp::ContentVersion("1"));
637 cv = { dcp::ContentVersion("1") };
639 cpl->set_content_versions (cv);
641 cpl->set_full_content_title_text (film()->name());
642 cpl->set_full_content_title_text_language (film()->name_language());
643 if (film()->release_territory()) {
644 cpl->set_release_territory (*film()->release_territory());
646 cpl->set_version_number (film()->version_number());
647 cpl->set_status (film()->status());
648 if (film()->chain()) {
649 cpl->set_chain (*film()->chain());
651 if (film()->distributor()) {
652 cpl->set_distributor (*film()->distributor());
654 if (film()->facility()) {
655 cpl->set_facility (*film()->facility());
657 if (film()->luminance()) {
658 cpl->set_luminance (*film()->luminance());
660 if (film()->sign_language_video_language()) {
661 cpl->set_sign_language_video_language (*film()->sign_language_video_language());
664 dcp::MCASoundField field;
665 if (channel_is_mapped(film(), dcp::Channel::BSL) || channel_is_mapped(film(), dcp::Channel::BSR)) {
666 field = dcp::MCASoundField::SEVEN_POINT_ONE;
668 field = dcp::MCASoundField::FIVE_POINT_ONE;
671 auto const audio_channels = film()->audio_channels();
672 dcp::MainSoundConfiguration msc(field, audio_channels);
673 for (auto i: film()->mapped_audio_channels()) {
674 if (i < audio_channels) {
675 msc.set_mapping(i, static_cast<dcp::Channel>(i));
679 cpl->set_main_sound_configuration(msc);
680 cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
681 cpl->set_main_picture_stored_area (film()->frame_size());
683 auto active_area = film()->active_area();
684 if (active_area.width > 0 && active_area.height > 0) {
685 /* It's not allowed to have a zero active area width or height, and the sizes must be multiples of 2 */
686 cpl->set_main_picture_active_area({ active_area.width & ~1, active_area.height & ~1});
689 auto sl = film()->subtitle_languages().second;
691 cpl->set_additional_subtitle_languages(sl);
694 auto signer = Config::instance()->signer_chain();
695 /* We did check earlier, but check again here to be on the safe side */
697 if (!signer->valid (&reason)) {
698 throw InvalidSignerError (reason);
701 dcp.set_issuer(issuer);
702 dcp.set_creator(creator);
703 dcp.set_annotation_text(film()->dcp_name());
705 dcp.write_xml(signer, !film()->limit_to_smpte_bv20(), Config::instance()->dcp_metadata_filename_format());
708 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
711 write_cover_sheet (output_dcp);
716 Writer::write_cover_sheet (boost::filesystem::path output_dcp)
718 auto const cover = film()->file("COVER_SHEET.txt");
719 dcp::File file(cover, "w");
721 throw OpenFileError (cover, errno, OpenFileError::WRITE);
724 auto text = Config::instance()->cover_sheet ();
725 boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
726 auto cpls = film()->cpls();
728 boost::algorithm::replace_all (text, "$CPL_FILENAME", cpls[0].cpl_file.filename().string());
730 boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
731 boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
733 auto audio_language = film()->audio_language();
734 if (audio_language) {
735 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", audio_language->description());
737 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _("None"));
740 auto subtitle_languages = film()->subtitle_languages();
741 if (subtitle_languages.first) {
742 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.first->description());
744 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", _("None"));
747 boost::uintmax_t size = 0;
749 auto i = dcp::filesystem::recursive_directory_iterator(output_dcp);
750 i != dcp::filesystem::recursive_directory_iterator();
752 if (dcp::filesystem::is_regular_file(i->path())) {
753 size += dcp::filesystem::file_size(i->path());
757 if (size > (1000000000L)) {
758 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1GB", dcp::locale_convert<string>(size / 1000000000.0, 1, true)));
760 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1MB", dcp::locale_convert<string>(size / 1000000.0, 1, true)));
763 auto ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
764 auto description = String::compose("%1.%2", ch.first, ch.second);
766 if (description == "0.0") {
767 description = _("None");
768 } else if (description == "1.0") {
769 description = _("Mono");
770 } else if (description == "2.0") {
771 description = _("Stereo");
773 boost::algorithm::replace_all (text, "$AUDIO", description);
775 auto const hmsf = film()->length().split(film()->video_frame_rate());
777 if (hmsf.h == 0 && hmsf.m == 0) {
778 length = String::compose("%1s", hmsf.s);
779 } else if (hmsf.h == 0 && hmsf.m > 0) {
780 length = String::compose("%1m%2s", hmsf.m, hmsf.s);
781 } else if (hmsf.h > 0 && hmsf.m > 0) {
782 length = String::compose("%1h%2m%3s", hmsf.h, hmsf.m, hmsf.s);
785 boost::algorithm::replace_all (text, "$LENGTH", length);
787 file.checked_write(text.c_str(), text.length());
791 /** @param frame Frame index within the whole DCP.
792 * @return true if we can fake-write this frame.
795 Writer::can_fake_write (Frame frame) const
797 if (film()->encrypted()) {
798 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
802 /* We have to do a proper write of the first frame so that we can set up the JPEG2000
803 parameters in the asset writer.
806 auto const & reel = _reels[video_reel(frame)];
808 /* Make frame relative to the start of the reel */
809 frame -= reel.start ();
810 return (frame != 0 && frame < reel.first_nonexistent_frame());
814 /** @param track Closed caption track if type == TextType::CLOSED_CAPTION */
816 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
818 vector<ReelWriter>::iterator* reel = nullptr;
821 case TextType::OPEN_SUBTITLE:
822 reel = &_subtitle_reel;
823 _have_subtitles = true;
825 case TextType::CLOSED_CAPTION:
826 DCPOMATIC_ASSERT (track);
827 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
828 reel = &_caption_reels[*track];
829 _have_closed_captions.insert (*track);
832 DCPOMATIC_ASSERT (false);
835 DCPOMATIC_ASSERT (*reel != _reels.end());
836 while ((*reel)->period().to <= period.from) {
838 DCPOMATIC_ASSERT (*reel != _reels.end());
839 write_hanging_text (**reel);
842 auto back_off = [this](DCPTimePeriod period) {
843 period.to -= DCPTime::from_frames(2, film()->video_frame_rate());
847 if (period.to > (*reel)->period().to) {
848 /* This text goes off the end of the reel. Store parts of it that should go into
851 for (auto i = std::next(*reel); i != _reels.end(); ++i) {
852 auto overlap = i->period().overlap(period);
854 _hanging_texts.push_back (HangingText{text, type, track, back_off(*overlap)});
857 /* Back off from the reel boundary by a couple of frames to avoid tripping checks
858 * for subtitles being too close together.
860 period.to = (*reel)->period().to;
861 period = back_off(period);
864 (*reel)->write(text, type, track, period, _fonts, _chosen_interop_font);
869 Writer::write (vector<shared_ptr<Font>> fonts)
875 /* Fonts may come in with empty IDs but we don't want to put those in the DCP */
876 auto fix_id = [](string id) {
877 return id.empty() ? "font" : id;
880 if (film()->interop()) {
881 /* Interop will ignore second and subsequent <LoadFont>s so we don't want to
882 * even write them as they upset some validators. Set up _fonts so that every
883 * font used by any subtitle will be written with the same ID.
885 for (size_t i = 0; i < fonts.size(); ++i) {
886 _fonts.put(fonts[i], fix_id(fonts[0]->id()));
888 _chosen_interop_font = fonts[0];
890 for (auto font: fonts) {
891 _fonts.put(font, fix_id(font->id()));
898 operator< (QueueItem const & a, QueueItem const & b)
900 if (a.reel != b.reel) {
901 return a.reel < b.reel;
904 if (a.frame != b.frame) {
905 return a.frame < b.frame;
908 return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
913 operator== (QueueItem const & a, QueueItem const & b)
915 return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
920 Writer::set_encoder_threads (int threads)
922 boost::mutex::scoped_lock lm (_state_mutex);
923 _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
924 _maximum_queue_size = threads * 16;
929 Writer::write (ReferencedReelAsset asset)
931 _reel_assets.push_back (asset);
933 if (dynamic_pointer_cast<dcp::ReelSubtitleAsset>(asset.asset)) {
934 _have_subtitles = true;
935 } else if (auto ccap = dynamic_pointer_cast<dcp::ReelClosedCaptionAsset>(asset.asset)) {
936 /* This feels quite fragile. We have a referenced reel and want to know if it's
937 * part of a given closed-caption track so that we can fill if it has any
938 * missing reels. I guess for that purpose almost any DCPTextTrack values are
939 * fine so long as they are consistent.
942 track.name = ccap->annotation_text().get_value_or("");
943 track.language = dcp::LanguageTag(ccap->language().get_value_or("en-US"));
944 if (_have_closed_captions.find(track) == _have_closed_captions.end()) {
945 _have_closed_captions.insert(track);
952 Writer::video_reel (int frame) const
954 auto t = DCPTime::from_frames (frame, film()->video_frame_rate());
955 size_t reel_index = 0;
956 while (reel_index < _reels.size() && !_reels[reel_index].period().contains(t)) {
960 DCPOMATIC_ASSERT(reel_index < _reels.size ());
965 /** Update job progress with information about the progress of a single digest calculation
967 * @param id Unique identifier for the thread whose progress has changed.
968 * @param done Number of bytes that this thread has processed.
969 * @param size Total number of bytes that this thread must process.
972 Writer::set_digest_progress(Job* job, int id, int64_t done, int64_t size)
974 boost::mutex::scoped_lock lm (_digest_progresses_mutex);
976 /* Update the progress for this thread */
977 _digest_progresses[id] = std::make_pair(done, size);
979 /* Get the total progress across all threads and use it to set job progress */
980 int64_t total_done = 0;
981 int64_t total_size = 0;
982 for (auto const& i: _digest_progresses) {
983 total_done += i.second.first;
984 total_size += i.second.second;
987 job->set_progress(float(total_done) / total_size);
992 boost::this_thread::interruption_point();
996 /** Calculate hashes for any referenced MXF assets which do not already have one */
998 Writer::calculate_referenced_digests(std::function<void (int64_t, int64_t)> set_progress)
1001 int64_t total_size = 0;
1002 for (auto const& i: _reel_assets) {
1003 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
1004 if (file && !file->hash()) {
1005 auto filename = file->asset_ref().asset()->file();
1006 DCPOMATIC_ASSERT(filename);
1007 total_size += boost::filesystem::file_size(*filename);
1011 int64_t total_done = 0;
1012 for (auto const& i: _reel_assets) {
1013 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
1014 if (file && !file->hash()) {
1015 file->asset_ref().asset()->hash([&total_done, total_size, set_progress](int64_t done, int64_t) {
1016 set_progress(total_done + done, total_size);
1018 total_done += boost::filesystem::file_size(*file->asset_ref().asset()->file());
1019 file->set_hash (file->asset_ref().asset()->hash());
1022 } catch (boost::thread_interrupted) {
1023 /* set_progress contains an interruption_point, so any of these methods
1024 * may throw thread_interrupted, at which point we just give up.
1030 Writer::write_hanging_text (ReelWriter& reel)
1032 vector<HangingText> new_hanging_texts;
1033 for (auto i: _hanging_texts) {
1034 if (i.period.from == reel.period().from) {
1035 reel.write(i.text, i.type, i.track, i.period, _fonts, _chosen_interop_font);
1037 new_hanging_texts.push_back (i);
1040 _hanging_texts = new_hanging_texts;