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_closed_caption_asset.h>
45 #include <dcp/reel_file_asset.h>
46 #include <dcp/reel_subtitle_asset.h>
54 /* OS X strikes again */
59 using std::dynamic_pointer_cast;
60 using std::make_shared;
63 using std::shared_ptr;
68 using boost::optional;
69 #if BOOST_VERSION >= 106100
70 using namespace boost::placeholders;
74 using namespace dcpomatic;
77 /** @param weak_job Job to report progress to, or 0.
78 * @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
80 Writer::Writer(weak_ptr<const Film> weak_film, weak_ptr<Job> weak_job, bool text_only)
81 : WeakConstFilm (weak_film)
83 /* These will be reset to sensible values when J2KEncoder is created */
84 , _maximum_frames_in_memory (8)
85 , _maximum_queue_size (8)
86 , _text_only (text_only)
88 auto job = _job.lock ();
91 auto const reels = film()->reels();
93 _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
96 _last_written.resize (reels.size());
98 /* We can keep track of the current audio, subtitle and closed caption reels easily because audio
99 and captions arrive to the Writer in sequence. This is not so for video.
101 _audio_reel = _reels.begin ();
102 _subtitle_reel = _reels.begin ();
103 for (auto i: film()->closed_caption_tracks()) {
104 _caption_reels[i] = _reels.begin ();
106 _atmos_reel = _reels.begin ();
108 /* Check that the signer is OK */
110 if (!Config::instance()->signer_chain()->valid(&reason)) {
111 throw InvalidSignerError (reason);
120 _thread = boost::thread (boost::bind(&Writer::thread, this));
121 #ifdef DCPOMATIC_LINUX
122 pthread_setname_np (_thread.native_handle(), "writer");
131 terminate_thread (false);
136 /** Pass a video frame to the writer for writing to disk at some point.
137 * This method can be called with frames out of order.
138 * @param encoded JPEG2000-encoded data.
139 * @param frame Frame index within the DCP.
140 * @param eyes Eyes that this frame image is for.
143 Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
145 boost::mutex::scoped_lock lock (_state_mutex);
151 while (_queued_full_in_memory > _maximum_frames_in_memory) {
152 /* There are too many full frames in memory; wake the main writer thread and
153 wait until it sorts everything out */
154 _empty_condition.notify_all ();
155 _full_condition.wait (lock);
159 qi.type = QueueItem::Type::FULL;
160 qi.encoded = encoded;
161 qi.reel = video_reel (frame);
162 qi.frame = frame - _reels[qi.reel].start ();
164 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
167 _queue.push_back(qi);
168 ++_queued_full_in_memory;
170 /* Now there's something to do: wake anything wait()ing on _empty_condition */
171 _empty_condition.notify_all ();
176 Writer::can_repeat (Frame frame) const
178 return frame > _reels[video_reel(frame)].start();
182 /** Repeat the last frame that was written to a reel as a new frame.
183 * @param frame Frame index within the DCP of the new (repeated) frame.
184 * @param eyes Eyes that this repeated frame image is for.
187 Writer::repeat (Frame frame, Eyes eyes)
189 boost::mutex::scoped_lock lock (_state_mutex);
191 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
192 /* The queue is too big, and the main writer thread can run and fix it, so
193 wake it and wait until it has done.
195 _empty_condition.notify_all ();
196 _full_condition.wait (lock);
200 qi.type = QueueItem::Type::REPEAT;
201 qi.reel = video_reel (frame);
202 qi.frame = frame - _reels[qi.reel].start ();
203 if (film()->three_d() && eyes == Eyes::BOTH) {
204 qi.eyes = Eyes::LEFT;
205 _queue.push_back (qi);
206 qi.eyes = Eyes::RIGHT;
207 _queue.push_back (qi);
210 _queue.push_back (qi);
213 /* Now there's something to do: wake anything wait()ing on _empty_condition */
214 _empty_condition.notify_all ();
219 Writer::fake_write (Frame frame, Eyes eyes)
221 boost::mutex::scoped_lock lock (_state_mutex);
223 while (_queue.size() > _maximum_queue_size && have_sequenced_image_at_queue_head()) {
224 /* The queue is too big, and the main writer thread can run and fix it, so
225 wake it and wait until it has done.
227 _empty_condition.notify_all ();
228 _full_condition.wait (lock);
231 size_t const reel = video_reel (frame);
232 Frame const frame_in_reel = frame - _reels[reel].start ();
235 qi.type = QueueItem::Type::FAKE;
238 shared_ptr<InfoFileHandle> info_file = film()->info_file_handle(_reels[reel].period(), true);
239 qi.size = _reels[reel].read_frame_info(info_file, frame_in_reel, eyes).size;
242 DCPOMATIC_ASSERT((film()->three_d() && eyes != Eyes::BOTH) || (!film()->three_d() && eyes == Eyes::BOTH));
245 qi.frame = frame_in_reel;
247 _queue.push_back(qi);
249 /* Now there's something to do: wake anything wait()ing on _empty_condition */
250 _empty_condition.notify_all ();
254 /** Write some audio frames to the DCP.
255 * @param audio Audio data.
256 * @param time Time of this data within the DCP.
257 * This method is not thread safe.
260 Writer::write (shared_ptr<const AudioBuffers> audio, DCPTime const time)
262 DCPOMATIC_ASSERT (audio);
264 int const afr = film()->audio_frame_rate();
266 DCPTime const end = time + DCPTime::from_frames(audio->frames(), afr);
268 /* The audio we get might span a reel boundary, and if so we have to write it in bits */
273 if (_audio_reel == _reels.end ()) {
274 /* This audio is off the end of the last reel; ignore it */
278 if (end <= _audio_reel->period().to) {
279 /* Easy case: we can write all the audio to this reel */
280 _audio_reel->write (audio);
282 } else if (_audio_reel->period().to <= t) {
283 /* This reel is entirely before the start of our audio; just skip the reel */
286 /* This audio is over a reel boundary; split the audio into two and write the first part */
287 DCPTime part_lengths[2] = {
288 _audio_reel->period().to - t,
289 end - _audio_reel->period().to
292 /* Be careful that part_lengths[0] + part_lengths[1] can't be bigger than audio->frames() */
293 Frame part_frames[2] = {
294 part_lengths[0].frames_ceil(afr),
295 part_lengths[1].frames_floor(afr)
298 DCPOMATIC_ASSERT ((part_frames[0] + part_frames[1]) <= audio->frames());
300 if (part_frames[0]) {
301 auto part = make_shared<AudioBuffers>(audio, part_frames[0], 0);
302 _audio_reel->write (part);
305 if (part_frames[1]) {
306 audio = make_shared<AudioBuffers>(audio, part_frames[1], part_frames[0]);
312 t += part_lengths[0];
319 Writer::write (shared_ptr<const dcp::AtmosFrame> atmos, DCPTime time, AtmosMetadata metadata)
321 if (_atmos_reel->period().to == time) {
323 DCPOMATIC_ASSERT (_atmos_reel != _reels.end());
326 /* We assume that we get a video frame's worth of data here */
327 _atmos_reel->write (atmos, metadata);
331 /** Caller must hold a lock on _state_mutex */
333 Writer::have_sequenced_image_at_queue_head ()
335 if (_queue.empty ()) {
340 auto const & f = _queue.front();
341 return _last_written[f.reel].next(f);
346 Writer::LastWritten::next (QueueItem qi) const
348 if (qi.eyes == Eyes::BOTH) {
350 return qi.frame == (_frame + 1);
355 if (_eyes == Eyes::LEFT && qi.frame == _frame && qi.eyes == Eyes::RIGHT) {
359 if (_eyes == Eyes::RIGHT && qi.frame == (_frame + 1) && qi.eyes == Eyes::LEFT) {
368 Writer::LastWritten::update (QueueItem qi)
379 start_of_thread ("Writer");
383 boost::mutex::scoped_lock lock (_state_mutex);
390 if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
391 /* We've got something to do: go and do it */
395 /* Nothing to do: wait until something happens which may indicate that we do */
396 LOG_TIMING (N_("writer-sleep queue=%1"), _queue.size());
397 _empty_condition.wait (lock);
398 LOG_TIMING (N_("writer-wake queue=%1"), _queue.size());
401 /* We stop here if we have been asked to finish, and if either the queue
402 is empty or we do not have a sequenced image at its head (if this is the
403 case we will never terminate as no new frames will be sent once
406 if (_finish && (!have_sequenced_image_at_queue_head() || _queue.empty())) {
407 /* (Hopefully temporarily) log anything that was not written */
408 if (!_queue.empty() && !have_sequenced_image_at_queue_head()) {
409 LOG_WARNING (N_("Finishing writer with a left-over queue of %1:"), _queue.size());
410 for (auto const& i: _queue) {
411 if (i.type == QueueItem::Type::FULL) {
412 LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i.frame, (int) i.eyes);
414 LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i.size, i.frame, (int) i.eyes);
421 /* Write any frames that we can write; i.e. those that are in sequence. */
422 while (have_sequenced_image_at_queue_head ()) {
423 auto qi = _queue.front ();
424 _last_written[qi.reel].update (qi);
426 if (qi.type == QueueItem::Type::FULL && qi.encoded) {
427 --_queued_full_in_memory;
432 auto& reel = _reels[qi.reel];
435 case QueueItem::Type::FULL:
436 LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
438 qi.encoded.reset (new ArrayData(film()->j2c_path(qi.reel, qi.frame, qi.eyes, false)));
440 reel.write (qi.encoded, qi.frame, qi.eyes);
443 case QueueItem::Type::FAKE:
444 LOG_DEBUG_ENCODE (N_("Writer FAKE-writes %1"), qi.frame);
445 reel.fake_write (qi.size);
448 case QueueItem::Type::REPEAT:
449 LOG_DEBUG_ENCODE (N_("Writer REPEAT-writes %1"), qi.frame);
450 reel.repeat_write (qi.frame, qi.eyes);
456 _full_condition.notify_all ();
459 while (_queued_full_in_memory > _maximum_frames_in_memory) {
460 /* Too many frames in memory which can't yet be written to the stream.
461 Write some FULL frames to disk.
464 /* Find one from the back of the queue */
466 auto item = _queue.rbegin();
467 while (item != _queue.rend() && (item->type != QueueItem::Type::FULL || !item->encoded)) {
471 DCPOMATIC_ASSERT(item != _queue.rend());
473 /* For the log message below */
474 int const awaiting = _last_written[_queue.front().reel].frame() + 1;
477 /* i is valid here, even though we don't hold a lock on the mutex,
478 since list iterators are unaffected by insertion and only this
479 thread could erase the last item in the list.
482 LOG_GENERAL("Writer full; pushes %1 to disk while awaiting %2", item->frame, awaiting);
484 item->encoded->write_via_temp(
485 film()->j2c_path(item->reel, item->frame, item->eyes, true),
486 film()->j2c_path(item->reel, item->frame, item->eyes, false)
490 item->encoded.reset();
491 --_queued_full_in_memory;
492 _full_condition.notify_all ();
503 Writer::terminate_thread (bool can_throw)
505 boost::this_thread::disable_interruption dis;
507 boost::mutex::scoped_lock lock (_state_mutex);
510 _empty_condition.notify_all ();
511 _full_condition.notify_all ();
525 Writer::calculate_digests ()
527 auto job = _job.lock ();
529 job->sub (_("Computing digests"));
532 boost::asio::io_service service;
533 boost::thread_group pool;
535 auto work = make_shared<boost::asio::io_service::work>(service);
537 int const threads = max (1, Config::instance()->master_encoding_threads());
539 for (int i = 0; i < threads; ++i) {
540 pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
543 std::function<void (int, int64_t, int64_t)> set_progress;
545 set_progress = boost::bind(&Writer::set_digest_progress, this, job.get(), _1, _2, _3);
547 set_progress = [](int, int64_t, int64_t) {
548 boost::this_thread::interruption_point();
554 for (auto& i: _reels) {
557 &ReelWriter::calculate_digests,
559 std::function<void (int64_t, int64_t)>(boost::bind(set_progress, index, _1, _2))
565 &Writer::calculate_referenced_digests,
567 std::function<void (int64_t, int64_t)>(boost::bind(set_progress, index, _1, _2))
574 } catch (boost::thread_interrupted) {
575 /* join_all was interrupted, so we need to interrupt the threads
576 * in our pool then try again to join them.
578 pool.interrupt_all ();
586 /** @param output_dcp Path to DCP folder to write */
588 Writer::finish (boost::filesystem::path output_dcp)
590 if (_thread.joinable()) {
591 LOG_GENERAL_NC ("Terminating writer thread");
592 terminate_thread (true);
595 LOG_GENERAL_NC ("Finishing ReelWriters");
597 for (auto& reel: _reels) {
598 write_hanging_text(reel);
599 reel.finish(output_dcp);
602 LOG_GENERAL_NC ("Writing XML");
604 dcp::DCP dcp (output_dcp);
606 auto cpl = make_shared<dcp::CPL>(
608 film()->dcp_content_type()->libdcp_kind(),
609 film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE
614 calculate_digests ();
618 for (auto& i: _reels) {
619 cpl->add(i.create_reel(_reel_assets, output_dcp, _have_subtitles, _have_closed_captions));
624 auto creator = Config::instance()->dcp_creator();
625 if (creator.empty()) {
626 creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
629 auto issuer = Config::instance()->dcp_issuer();
630 if (issuer.empty()) {
631 issuer = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
634 cpl->set_creator (creator);
635 cpl->set_issuer (issuer);
637 cpl->set_ratings (film()->ratings());
639 vector<dcp::ContentVersion> cv;
640 for (auto i: film()->content_versions()) {
641 /* Make sure we don't end up writing an empty <LabelText> node as some validators
642 * complain about that.
644 cv.push_back(!i.empty() ? dcp::ContentVersion(i) : dcp::ContentVersion("1"));
647 cv = { dcp::ContentVersion("1") };
649 cpl->set_content_versions (cv);
651 cpl->set_full_content_title_text (film()->name());
652 cpl->set_full_content_title_text_language (film()->name_language());
653 if (film()->release_territory()) {
654 cpl->set_release_territory (*film()->release_territory());
656 cpl->set_version_number (film()->version_number());
657 cpl->set_status (film()->status());
658 if (film()->chain()) {
659 cpl->set_chain (*film()->chain());
661 if (film()->distributor()) {
662 cpl->set_distributor (*film()->distributor());
664 if (film()->facility()) {
665 cpl->set_facility (*film()->facility());
667 if (film()->luminance()) {
668 cpl->set_luminance (*film()->luminance());
670 if (film()->sign_language_video_language()) {
671 cpl->set_sign_language_video_language (*film()->sign_language_video_language());
674 dcp::MCASoundField field;
675 if (channel_is_mapped(film(), dcp::Channel::BSL) || channel_is_mapped(film(), dcp::Channel::BSR)) {
676 field = dcp::MCASoundField::SEVEN_POINT_ONE;
678 field = dcp::MCASoundField::FIVE_POINT_ONE;
681 auto const audio_channels = film()->audio_channels();
682 dcp::MainSoundConfiguration msc(field, audio_channels);
683 for (auto i: film()->mapped_audio_channels()) {
684 if (i < audio_channels) {
685 msc.set_mapping(i, static_cast<dcp::Channel>(i));
689 cpl->set_main_sound_configuration(msc);
690 cpl->set_main_sound_sample_rate (film()->audio_frame_rate());
691 cpl->set_main_picture_stored_area (film()->frame_size());
693 auto active_area = film()->active_area();
694 if (active_area.width > 0 && active_area.height > 0) {
695 /* It's not allowed to have a zero active area width or height, and the sizes must be multiples of 2 */
696 cpl->set_main_picture_active_area({ active_area.width & ~1, active_area.height & ~1});
699 auto sl = film()->subtitle_languages().second;
701 cpl->set_additional_subtitle_languages(sl);
704 auto signer = Config::instance()->signer_chain();
705 /* We did check earlier, but check again here to be on the safe side */
707 if (!signer->valid (&reason)) {
708 throw InvalidSignerError (reason);
711 dcp.set_issuer(issuer);
712 dcp.set_creator(creator);
713 dcp.set_annotation_text(film()->dcp_name());
715 dcp.write_xml(signer, !film()->limit_to_smpte_bv20(), Config::instance()->dcp_metadata_filename_format());
718 N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
721 write_cover_sheet (output_dcp);
726 Writer::write_cover_sheet (boost::filesystem::path output_dcp)
728 auto const cover = film()->file("COVER_SHEET.txt");
729 dcp::File file(cover, "w");
731 throw OpenFileError (cover, errno, OpenFileError::WRITE);
734 auto text = Config::instance()->cover_sheet ();
735 boost::algorithm::replace_all (text, "$CPL_NAME", film()->name());
736 auto cpls = film()->cpls();
738 boost::algorithm::replace_all (text, "$CPL_FILENAME", cpls[0].cpl_file.filename().string());
740 boost::algorithm::replace_all (text, "$TYPE", film()->dcp_content_type()->pretty_name());
741 boost::algorithm::replace_all (text, "$CONTAINER", film()->container()->container_nickname());
743 auto audio_language = film()->audio_language();
744 if (audio_language) {
745 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", audio_language->description());
747 boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _("None"));
750 auto subtitle_languages = film()->subtitle_languages();
751 if (subtitle_languages.first) {
752 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", subtitle_languages.first->description());
754 boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", _("None"));
757 boost::uintmax_t size = 0;
759 auto i = dcp::filesystem::recursive_directory_iterator(output_dcp);
760 i != dcp::filesystem::recursive_directory_iterator();
762 if (dcp::filesystem::is_regular_file(i->path())) {
763 size += dcp::filesystem::file_size(i->path());
767 if (size > (1000000000L)) {
768 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1GB", dcp::locale_convert<string>(size / 1000000000.0, 1, true)));
770 boost::algorithm::replace_all (text, "$SIZE", String::compose("%1MB", dcp::locale_convert<string>(size / 1000000.0, 1, true)));
773 auto ch = audio_channel_types (film()->mapped_audio_channels(), film()->audio_channels());
774 auto description = String::compose("%1.%2", ch.first, ch.second);
776 if (description == "0.0") {
777 description = _("None");
778 } else if (description == "1.0") {
779 description = _("Mono");
780 } else if (description == "2.0") {
781 description = _("Stereo");
783 boost::algorithm::replace_all (text, "$AUDIO", description);
785 auto const hmsf = film()->length().split(film()->video_frame_rate());
787 if (hmsf.h == 0 && hmsf.m == 0) {
788 length = String::compose("%1s", hmsf.s);
789 } else if (hmsf.h == 0 && hmsf.m > 0) {
790 length = String::compose("%1m%2s", hmsf.m, hmsf.s);
791 } else if (hmsf.h > 0 && hmsf.m > 0) {
792 length = String::compose("%1h%2m%3s", hmsf.h, hmsf.m, hmsf.s);
795 boost::algorithm::replace_all (text, "$LENGTH", length);
797 file.checked_write(text.c_str(), text.length());
801 /** @param frame Frame index within the whole DCP.
802 * @return true if we can fake-write this frame.
805 Writer::can_fake_write (Frame frame) const
807 if (film()->encrypted()) {
808 /* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
812 /* We have to do a proper write of the first frame so that we can set up the JPEG2000
813 parameters in the asset writer.
816 auto const & reel = _reels[video_reel(frame)];
818 /* Make frame relative to the start of the reel */
819 frame -= reel.start ();
820 return (frame != 0 && frame < reel.first_nonexistent_frame());
824 /** @param track Closed caption track if type == TextType::CLOSED_CAPTION */
826 Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
828 vector<ReelWriter>::iterator* reel = nullptr;
831 case TextType::OPEN_SUBTITLE:
832 reel = &_subtitle_reel;
833 _have_subtitles = true;
835 case TextType::CLOSED_CAPTION:
836 DCPOMATIC_ASSERT (track);
837 DCPOMATIC_ASSERT (_caption_reels.find(*track) != _caption_reels.end());
838 reel = &_caption_reels[*track];
839 _have_closed_captions.insert (*track);
842 DCPOMATIC_ASSERT (false);
845 DCPOMATIC_ASSERT (*reel != _reels.end());
846 while ((*reel)->period().to <= period.from) {
848 DCPOMATIC_ASSERT (*reel != _reels.end());
849 write_hanging_text (**reel);
852 auto back_off = [this](DCPTimePeriod period) {
853 period.to -= DCPTime::from_frames(2, film()->video_frame_rate());
857 if (period.to > (*reel)->period().to) {
858 /* This text goes off the end of the reel. Store parts of it that should go into
861 for (auto i = std::next(*reel); i != _reels.end(); ++i) {
862 auto overlap = i->period().overlap(period);
864 _hanging_texts.push_back (HangingText{text, type, track, back_off(*overlap)});
867 /* Back off from the reel boundary by a couple of frames to avoid tripping checks
868 * for subtitles being too close together.
870 period.to = (*reel)->period().to;
871 period = back_off(period);
874 (*reel)->write(text, type, track, period, _fonts, _chosen_interop_font);
879 Writer::write (vector<shared_ptr<Font>> fonts)
885 /* Fonts may come in with empty IDs but we don't want to put those in the DCP */
886 auto fix_id = [](string id) {
887 return id.empty() ? "font" : id;
890 if (film()->interop()) {
891 /* Interop will ignore second and subsequent <LoadFont>s so we don't want to
892 * even write them as they upset some validators. Set up _fonts so that every
893 * font used by any subtitle will be written with the same ID.
895 for (size_t i = 0; i < fonts.size(); ++i) {
896 _fonts.put(fonts[i], fix_id(fonts[0]->id()));
898 _chosen_interop_font = fonts[0];
900 for (auto font: fonts) {
901 _fonts.put(font, fix_id(font->id()));
908 operator< (QueueItem const & a, QueueItem const & b)
910 if (a.reel != b.reel) {
911 return a.reel < b.reel;
914 if (a.frame != b.frame) {
915 return a.frame < b.frame;
918 return static_cast<int> (a.eyes) < static_cast<int> (b.eyes);
923 operator== (QueueItem const & a, QueueItem const & b)
925 return a.reel == b.reel && a.frame == b.frame && a.eyes == b.eyes;
930 Writer::set_encoder_threads (int threads)
932 boost::mutex::scoped_lock lm (_state_mutex);
933 _maximum_frames_in_memory = lrint (threads * Config::instance()->frames_in_memory_multiplier());
934 _maximum_queue_size = threads * 16;
939 Writer::write (ReferencedReelAsset asset)
941 _reel_assets.push_back (asset);
943 if (dynamic_pointer_cast<dcp::ReelSubtitleAsset>(asset.asset)) {
944 _have_subtitles = true;
945 } else if (auto ccap = dynamic_pointer_cast<dcp::ReelClosedCaptionAsset>(asset.asset)) {
946 /* This feels quite fragile. We have a referenced reel and want to know if it's
947 * part of a given closed-caption track so that we can fill if it has any
948 * missing reels. I guess for that purpose almost any DCPTextTrack values are
949 * fine so long as they are consistent.
952 track.name = ccap->annotation_text().get_value_or("");
953 track.language = dcp::LanguageTag(ccap->language().get_value_or("en-US"));
954 if (_have_closed_captions.find(track) == _have_closed_captions.end()) {
955 _have_closed_captions.insert(track);
962 Writer::video_reel (int frame) const
964 auto t = DCPTime::from_frames (frame, film()->video_frame_rate());
965 size_t reel_index = 0;
966 while (reel_index < _reels.size() && !_reels[reel_index].period().contains(t)) {
970 DCPOMATIC_ASSERT(reel_index < _reels.size ());
975 /** Update job progress with information about the progress of a single digest calculation
977 * @param id Unique identifier for the thread whose progress has changed.
978 * @param done Number of bytes that this thread has processed.
979 * @param size Total number of bytes that this thread must process.
982 Writer::set_digest_progress(Job* job, int id, int64_t done, int64_t size)
984 boost::mutex::scoped_lock lm (_digest_progresses_mutex);
986 /* Update the progress for this thread */
987 _digest_progresses[id] = std::make_pair(done, size);
989 /* Get the total progress across all threads and use it to set job progress */
990 int64_t total_done = 0;
991 int64_t total_size = 0;
992 for (auto const& i: _digest_progresses) {
993 total_done += i.second.first;
994 total_size += i.second.second;
997 job->set_progress(float(total_done) / total_size);
1002 boost::this_thread::interruption_point();
1006 /** Calculate hashes for any referenced MXF assets which do not already have one */
1008 Writer::calculate_referenced_digests(std::function<void (int64_t, int64_t)> set_progress)
1011 int64_t total_size = 0;
1012 for (auto const& i: _reel_assets) {
1013 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
1014 if (file && !file->hash()) {
1015 auto filename = file->asset_ref().asset()->file();
1016 DCPOMATIC_ASSERT(filename);
1017 total_size += boost::filesystem::file_size(*filename);
1021 int64_t total_done = 0;
1022 for (auto const& i: _reel_assets) {
1023 auto file = dynamic_pointer_cast<dcp::ReelFileAsset>(i.asset);
1024 if (file && !file->hash()) {
1025 file->asset_ref().asset()->hash([&total_done, total_size, set_progress](int64_t done, int64_t) {
1026 set_progress(total_done + done, total_size);
1028 total_done += boost::filesystem::file_size(*file->asset_ref().asset()->file());
1029 file->set_hash (file->asset_ref().asset()->hash());
1032 } catch (boost::thread_interrupted) {
1033 /* set_progress contains an interruption_point, so any of these methods
1034 * may throw thread_interrupted, at which point we just give up.
1040 Writer::write_hanging_text (ReelWriter& reel)
1042 vector<HangingText> new_hanging_texts;
1043 for (auto i: _hanging_texts) {
1044 if (i.period.from == reel.period().from) {
1045 reel.write(i.text, i.type, i.track, i.period, _fonts, _chosen_interop_font);
1047 new_hanging_texts.push_back (i);
1050 _hanging_texts = new_hanging_texts;
1054 /** Set the writer so that it has no queue and drops any pending or future requests to write images */
1058 boost::mutex::scoped_lock lock(_state_mutex);
1061 _queued_full_in_memory = 0;
1063 _full_condition.notify_all();