#include <fstream>
#include <cerrno>
#include <iostream>
+#include <cfloat>
#include "i18n.h"
using std::string;
using std::list;
using std::cout;
+using std::map;
+using std::min;
+using std::max;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
, _repeat_written (0)
, _pushed_to_disk (0)
{
- /* Remove any old DCP */
- boost::filesystem::remove_all (_film->dir (_film->dcp_name ()));
-
shared_ptr<Job> job = _job.lock ();
DCPOMATIC_ASSERT (job);
- BOOST_FOREACH (DCPTimePeriod p, _film->reels ()) {
- _reels.push_back (ReelWriter (film, p, job));
+ int reel_index = 0;
+ list<DCPTimePeriod> const reels = _film->reels ();
+ BOOST_FOREACH (DCPTimePeriod p, reels) {
+ _reels.push_back (ReelWriter (film, p, job, reel_index++, reels.size(), _film->content_summary(p)));
}
/* We can keep track of the current audio and subtitle reels easily because audio
_subtitle_reel = _reels.begin ();
/* Check that the signer is OK if we need one */
- if (_film->is_signed() && !Config::instance()->signer_chain()->valid ()) {
- throw InvalidSignerError ();
+ string reason;
+ if (_film->is_signed() && !Config::instance()->signer_chain()->valid(&reason)) {
+ throw InvalidSignerError (reason);
}
}
_empty_condition.notify_all ();
}
-/** Write one video frame's worth of audio frames to the DCP.
+/** Write some audio frames to the DCP.
* @param audio Audio data or 0 if there is no audio to be written here (i.e. it is referenced).
* This method is not thread safe.
*/
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- if (_audio_reel == _reels.end ()) {
- /* This audio is off the end of the last reel; ignore it */
- return;
- }
+ /* The audio we get might span a reel boundary, and if so we have to write it in bits */
- _audio_reel->write (audio);
+ int32_t offset = 0;
+ while (offset < audio->frames ()) {
- /* written is in video frames, not audio frames */
- if (_audio_reel->total_written_audio_frames() >= _audio_reel->period().duration().frames_floor (_film->video_frame_rate())) {
- ++_audio_reel;
+ if (_audio_reel == _reels.end ()) {
+ /* This audio is off the end of the last reel; ignore it */
+ return;
+ }
+
+ int32_t const this_time = min (
+ audio->frames() - offset,
+ (int32_t) (_audio_reel->period().duration().frames_floor(_film->audio_frame_rate()) - _audio_reel->total_written_audio_frames())
+ );
+
+ if (this_time == audio->frames()) {
+ /* Easy case: we can write all the audio to this reel */
+ _audio_reel->write (audio);
+ } else {
+ /* Write the part we can */
+ shared_ptr<AudioBuffers> part (new AudioBuffers (audio->channels(), this_time));
+ part->copy_from (audio.get(), this_time, offset, 0);
+ _audio_reel->write (part);
+ ++_audio_reel;
+ }
+
+ offset += this_time;
}
}
LOG_WARNING (N_("Finishing writer with a left-over queue of %1:"), _queue.size());
for (list<QueueItem>::const_iterator i = _queue.begin(); i != _queue.end(); ++i) {
if (i->type == QueueItem::FULL) {
- LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i->frame, i->eyes);
+ LOG_WARNING (N_("- type FULL, frame %1, eyes %2"), i->frame, (int) i->eyes);
} else {
- LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i->size, i->frame, i->eyes);
+ LOG_WARNING (N_("- type FAKE, size %1, frame %2, eyes %3"), i->size, i->frame, (int) i->eyes);
}
}
}
switch (qi.type) {
case QueueItem::FULL:
- LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, qi.eyes);
+ LOG_DEBUG_ENCODE (N_("Writer FULL-writes %1 (%2)"), qi.frame, (int) qi.eyes);
if (!qi.encoded) {
qi.encoded = Data (_film->j2c_path (qi.reel, qi.frame, qi.eyes, false));
}
DCPOMATIC_ASSERT (i != _queue.rend());
++_pushed_to_disk;
+ /* For the log message below */
+ int const awaiting = _reels[_queue.front().reel].last_written_video_frame();
lock.unlock ();
/* i is valid here, even though we don't hold a lock on the mutex,
thread could erase the last item in the list.
*/
- LOG_GENERAL ("Writer full; pushes %1 to disk", i->frame);
+ LOG_GENERAL ("Writer full; pushes %1 to disk while awaiting %2", i->frame, awaiting);
i->encoded->write_via_temp (
_film->j2c_path (i->reel, i->frame, i->eyes, true),
dcp.add (cpl);
+ /* Calculate digests for each reel in parallel */
+
+ shared_ptr<Job> job = _job.lock ();
+ job->sub (_("Computing digests"));
+
+ boost::asio::io_service service;
+ boost::thread_group pool;
+
+ shared_ptr<boost::asio::io_service::work> work (new boost::asio::io_service::work (service));
+
+ int const threads = max (1, Config::instance()->num_local_encoding_threads ());
+
+ for (int i = 0; i < threads; ++i) {
+ pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
+ }
+
BOOST_FOREACH (ReelWriter& i, _reels) {
+ boost::function<void (float)> set_progress = boost::bind (&Writer::set_digest_progress, this, job.get(), _1);
+ service.post (boost::bind (&ReelWriter::calculate_digests, &i, set_progress));
+ }
- shared_ptr<Job> job = _job.lock ();
- DCPOMATIC_ASSERT (job);
- i.calculate_digests (job);
+ work.reset ();
+ pool.join_all ();
+ service.stop ();
+ /* Add reels to CPL */
+
+ BOOST_FOREACH (ReelWriter& i, _reels) {
cpl->add (i.create_reel (_reel_assets, _fonts));
}
dcp::XMLMetadata meta;
+ meta.annotation_text = cpl->annotation_text ();
meta.creator = Config::instance()->dcp_creator ();
if (meta.creator.empty ()) {
meta.creator = String::compose ("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
if (_film->is_signed ()) {
signer = Config::instance()->signer_chain ();
/* We did check earlier, but check again here to be on the safe side */
- if (!signer->valid ()) {
- throw InvalidSignerError ();
+ string reason;
+ if (!signer->valid (&reason)) {
+ throw InvalidSignerError (reason);
}
}
- dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer);
+ dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer, Config::instance()->dcp_metadata_filename_format());
LOG_GENERAL (
N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
}
void
-Writer::write (PlayerSubtitles subs)
+Writer::write (PlayerSubtitles subs, DCPTimePeriod period)
{
if (subs.text.empty ()) {
return;
}
- if (_subtitle_reel->period().to <= subs.from) {
+ if (_subtitle_reel->period().to <= period.from) {
++_subtitle_reel;
}
DCPOMATIC_ASSERT (i < _reels.size ());
return i;
}
+
+void
+Writer::set_digest_progress (Job* job, float progress)
+{
+ /* I believe this is thread-safe */
+ _digest_progresses[boost::this_thread::get_id()] = progress;
+
+ boost::mutex::scoped_lock lm (_digest_progresses_mutex);
+ float min_progress = FLT_MAX;
+ for (map<boost::thread::id, float>::const_iterator i = _digest_progresses.begin(); i != _digest_progresses.end(); ++i) {
+ min_progress = min (min_progress, i->second);
+ }
+
+ job->set_progress (min_progress);
+}