/*
- Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
#include "util.h"
#include "reel_writer.h"
#include <dcp/cpl.h>
+#include <dcp/locale_convert.h>
#include <boost/foreach.hpp>
#include <fstream>
#include <cerrno>
#include <iostream>
+#include <cfloat>
#include "i18n.h"
, _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);
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()));
+ _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 ()) {
+
+ if (_audio_reel == _reels.end ()) {
+ /* This audio is off the end of the last reel; ignore it */
+ return;
+ }
- /* 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;
+ int32_t const remaining = audio->frames() - offset;
+ int32_t const reel_space = _audio_reel->period().duration().frames_floor(_film->audio_frame_rate()) - _audio_reel->total_written_audio_frames();
+
+ if (remaining <= reel_space) {
+ /* Easy case: we can write all the audio to this reel */
+ _audio_reel->write (audio);
+ offset += remaining;
+ } else {
+ /* Write the part we can */
+ shared_ptr<AudioBuffers> part (new AudioBuffers (audio->channels(), reel_space));
+ part->copy_from (audio.get(), reel_space, offset, 0);
+ _audio_reel->write (part);
+ ++_audio_reel;
+ offset += reel_space;
+ }
}
}
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),
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 ());
+ int const threads = max (1, Config::instance()->master_encoding_threads ());
for (int i = 0; i < threads; ++i) {
pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
}
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, Config::instance()->dcp_filename_format());
+ 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
);
+
+ write_cover_sheet ();
+}
+
+void
+Writer::write_cover_sheet ()
+{
+ boost::filesystem::path const cover = _film->file ("COVER_SHEET.txt");
+ FILE* f = fopen_boost (cover, "w");
+ if (!f) {
+ throw OpenFileError (cover, errno, false);
+ }
+
+ string text = Config::instance()->cover_sheet ();
+ boost::algorithm::replace_all (text, "$CPL_NAME", _film->name());
+ boost::algorithm::replace_all (text, "$TYPE", _film->dcp_content_type()->pretty_name());
+ boost::algorithm::replace_all (text, "$CONTAINER", _film->container()->container_nickname());
+ boost::algorithm::replace_all (text, "$AUDIO_LANGUAGE", _film->isdcf_metadata().audio_language);
+ boost::algorithm::replace_all (text, "$SUBTITLE_LANGUAGE", _film->isdcf_metadata().subtitle_language);
+
+ boost::uintmax_t size = 0;
+ for (
+ boost::filesystem::recursive_directory_iterator i = boost::filesystem::recursive_directory_iterator(_film->dir(_film->dcp_name()));
+ i != boost::filesystem::recursive_directory_iterator();
+ ++i) {
+ if (boost::filesystem::is_regular_file (i->path ())) {
+ size += boost::filesystem::file_size (i->path ());
+ }
+ }
+
+ if (size > (1000000000L)) {
+ boost::algorithm::replace_all (text, "$SIZE", String::compose ("%1GB", dcp::locale_convert<string> (size / 1000000000.0, 1, true)));
+ } else {
+ boost::algorithm::replace_all (text, "$SIZE", String::compose ("%1MB", dcp::locale_convert<string> (size / 1000000.0, 1, true)));
+ }
+
+ pair<int, int> ch = audio_channel_types (_film->mapped_audio_channels(), _film->audio_channels());
+ string description = String::compose("%1.%2", ch.first, ch.second);
+
+ if (description == "0.0") {
+ description = _("None");
+ } else if (description == "1.0") {
+ description = _("Mono");
+ } else if (description == "2.0") {
+ description = _("Stereo");
+ }
+ boost::algorithm::replace_all (text, "$AUDIO", description);
+
+ int h, m, s, fr;
+ _film->length().split (_film->video_frame_rate(), h, m, s, fr);
+ string length;
+ if (h == 0 && m == 0) {
+ length = String::compose("%1s", s);
+ } else if (h == 0 && m > 0) {
+ length = String::compose("%1m%2s", m, s);
+ } else if (h > 0 && m > 0) {
+ length = String::compose("%1h%2m%3s", h, m, s);
+ }
+
+ boost::algorithm::replace_all (text, "$LENGTH", length);
+
+ fwrite (text.c_str(), 1, text.length(), f);
+ fclose (f);
}
/** @param frame Frame index within the whole DCP.
}
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;
}
_digest_progresses[boost::this_thread::get_id()] = progress;
boost::mutex::scoped_lock lm (_digest_progresses_mutex);
- float min_progress = 0;
+ 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);
}