/*
- Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
- This program is free software; you can redistribute it and/or modify
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
- This program is distributed in the hope that it will be useful,
+ DCP-o-matic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cross.h"
#include "job.h"
#include "log.h"
-#include "md5_digester.h"
+#include "digester.h"
#include "font.h"
#include "compose.hpp"
#include "audio_buffers.h"
using std::list;
using std::string;
+using std::cout;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
+using dcp::Data;
int const ReelWriter::_info_size = 48;
-ReelWriter::ReelWriter (shared_ptr<const Film> film, DCPTimePeriod period, shared_ptr<Job> job)
+ReelWriter::ReelWriter (
+ shared_ptr<const Film> film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, optional<string> content_summary
+ )
: _film (film)
, _period (period)
, _first_nonexistant_frame (0)
, _last_written_video_frame (-1)
, _last_written_eyes (EYES_RIGHT)
, _total_written_audio_frames (0)
+ , _reel_index (reel_index)
+ , _reel_count (reel_count)
+ , _content_summary (content_summary)
{
/* Create our picture asset in a subdirectory, named according to those
film's parameters which affect the video output. We will hard-link
if (_film->encrypted ()) {
_picture_asset->set_key (_film->key ());
+ _picture_asset->set_context_id (_film->context_id ());
}
_picture_asset->set_file (
_sound_asset->set_key (_film->key ());
}
+ DCPOMATIC_ASSERT (_film->directory());
+
/* Write the sound asset into the film directory so that we leave the creation
of the DCP directory until the last minute.
*/
_sound_asset_writer = _sound_asset->start_write (
- _film->directory() / audio_asset_filename (_sound_asset),
+ _film->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
_film->interop() ? dcp::INTEROP : dcp::SMPTE
);
}
{
FILE* file = 0;
boost::filesystem::path info_file = _film->info_file (_period);
- if (boost::filesystem::exists (info_file)) {
+
+ bool const read = boost::filesystem::exists (info_file);
+
+#ifdef DCPOMATIC_WINDOWS
+ if (read) {
+ LOG_GENERAL (
+ "Checked %1 (which exists) length is %2 perms are %3",
+ info_file, boost::filesystem::file_size (info_file), int(boost::filesystem::status(info_file).permissions())
+ );
+ } else {
+ LOG_GENERAL ("Checked %1 (which does not exist)", info_file);
+ }
+#endif
+
+ if (read) {
file = fopen_boost (info_file, "r+b");
} else {
file = fopen_boost (info_file, "wb");
}
if (!file) {
- throw OpenFileError (info_file);
+ throw OpenFileError (info_file, errno, read);
}
dcpomatic_fseek (file, frame_info_position (frame, eyes), SEEK_SET);
fwrite (&info.offset, sizeof (info.offset), 1, file);
ReelWriter::check_existing_picture_asset ()
{
/* Try to open the existing asset */
- FILE* asset_file = fopen_boost (_picture_asset->file(), "rb");
+ DCPOMATIC_ASSERT (_picture_asset->file());
+ FILE* asset_file = fopen_boost (_picture_asset->file().get(), "rb");
if (!asset_file) {
- LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", _picture_asset->file().string(), errno);
+ LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", _picture_asset->file()->string(), errno);
return;
+ } else {
+ LOG_GENERAL ("Opened existing asset at %1", _picture_asset->file()->string());
}
/* Offset of the last dcp::FrameInfo in the info file */
int const n = (boost::filesystem::file_size (_film->info_file(_period)) / _info_size) - 1;
+ LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size (_film->info_file(_period)), _info_size);
FILE* info_file = fopen_boost (_film->info_file(_period), "rb");
if (!info_file) {
_first_nonexistant_frame = n;
}
- bool ok = false;
-
- while (!ok) {
- /* Read the data from the info file; for 3D we just check the left
- frames until we find a good one.
- */
- dcp::FrameInfo info = read_frame_info (info_file, _first_nonexistant_frame, _film->three_d () ? EYES_LEFT : EYES_BOTH);
-
- ok = true;
-
- /* Read the data from the asset and hash it */
- dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
- Data data (info.size);
- size_t const read = fread (data.data().get(), 1, data.size(), asset_file);
- if (read != static_cast<size_t> (data.size ())) {
- LOG_GENERAL ("Existing frame %1 is incomplete", _first_nonexistant_frame);
- ok = false;
- } else {
- MD5Digester digester;
- digester.add (data.data().get(), data.size());
- if (digester.get() != info.hash) {
- LOG_GENERAL ("Existing frame %1 failed hash check", _first_nonexistant_frame);
- ok = false;
- }
- }
-
- if (!ok) {
- --_first_nonexistant_frame;
- }
+ while (!existing_picture_frame_ok(asset_file, info_file) && _first_nonexistant_frame > 0) {
+ --_first_nonexistant_frame;
}
- if (!_film->three_d ()) {
+ if (!_film->three_d() && _first_nonexistant_frame > 0) {
/* If we are doing 3D we might have found a good L frame with no R, so only
do this if we're in 2D and we've just found a good B(oth) frame.
*/
++_first_nonexistant_frame;
}
+ LOG_GENERAL ("Proceeding with first nonexistant frame %1", _first_nonexistant_frame);
+
fclose (asset_file);
fclose (info_file);
}
{
if (!_picture_asset_writer->finalize ()) {
/* Nothing was written to the picture asset */
+ LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
_picture_asset.reset ();
}
/* Hard-link any video asset file into the DCP */
if (_picture_asset) {
- boost::filesystem::path video_from = _picture_asset->file ();
+ DCPOMATIC_ASSERT (_picture_asset->file());
+ boost::filesystem::path video_from = _picture_asset->file().get();
boost::filesystem::path video_to;
video_to /= _film->dir (_film->dcp_name());
- video_to /= video_asset_filename (_picture_asset);
+ video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
boost::system::error_code ec;
boost::filesystem::create_hard_link (video_from, video_to, ec);
if (_sound_asset) {
boost::filesystem::path audio_to;
audio_to /= _film->dir (_film->dcp_name ());
- audio_to /= audio_asset_filename (_sound_asset);
+ string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
+ audio_to /= aaf;
boost::system::error_code ec;
- boost::filesystem::rename (_film->file (audio_asset_filename (_sound_asset)), audio_to, ec);
+ boost::filesystem::rename (_film->file (aaf), audio_to, ec);
if (ec) {
throw FileError (
- String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), audio_asset_filename (_sound_asset)
+ String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
);
}
reel_picture_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
}
} else {
+ LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
/* We don't have a picture asset of our own; hopefully we have one to reference */
BOOST_FOREACH (ReferencedReelAsset j, refs) {
shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
+ if (k) {
+ LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
+ }
if (k && j.period == _period) {
reel_picture_asset = k;
}
}
}
+ LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
+
+ DCPOMATIC_ASSERT (reel_picture_asset);
+ DCPOMATIC_ASSERT (reel_picture_asset->duration() == _period.duration().frames_round (_film->video_frame_rate ()));
reel->add (reel_picture_asset);
+ /* If we have a hash for this asset in the CPL, assume that it is correct */
+ if (reel_picture_asset->hash()) {
+ reel_picture_asset->asset_ref()->set_hash (reel_picture_asset->hash().get());
+ }
+
+ shared_ptr<dcp::ReelSoundAsset> reel_sound_asset;
+
if (_sound_asset) {
/* We have made a sound asset of our own. Put it into the reel */
- reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_asset, 0)));
+ reel_sound_asset.reset (new dcp::ReelSoundAsset (_sound_asset, 0));
} else {
/* We don't have a sound asset of our own; hopefully we have one to reference */
BOOST_FOREACH (ReferencedReelAsset j, refs) {
shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
if (k && j.period == _period) {
- reel->add (k);
+ reel_sound_asset = k;
+ /* If we have a hash for this asset in the CPL, assume that it is correct */
+ if (k->hash()) {
+ k->asset_ref()->set_hash (k->hash().get());
+ }
}
}
}
+ DCPOMATIC_ASSERT (reel_sound_asset);
+ DCPOMATIC_ASSERT (reel_sound_asset->duration() == _period.duration().frames_round (_film->video_frame_rate ()));
+ reel->add (reel_sound_asset);
+
+ shared_ptr<dcp::ReelSubtitleAsset> reel_subtitle_asset;
+
if (_subtitle_asset) {
boost::filesystem::path liberation_normal;
try {
- liberation_normal = shared_path () / "LiberationSans-Regular.ttf";
+ liberation_normal = shared_path() / "LiberationSans-Regular.ttf";
+ if (!boost::filesystem::exists (liberation_normal)) {
+ /* Hack for unit tests */
+ liberation_normal = shared_path() / "fonts" / "LiberationSans-Regular.ttf";
+ }
} catch (boost::filesystem::filesystem_error& e) {
- /* Hack: try the debian/ubuntu location if getting the shared path failed */
- liberation_normal = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf";
+
}
+ if (!boost::filesystem::exists(liberation_normal)) {
+ liberation_normal = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf";
+ }
/* Add all the fonts to the subtitle content */
BOOST_FOREACH (shared_ptr<Font> j, fonts) {
);
}
- reel->add (shared_ptr<dcp::ReelSubtitleAsset> (
- new dcp::ReelSubtitleAsset (
- _subtitle_asset,
- dcp::Fraction (_film->video_frame_rate(), 1),
- reel_picture_asset->intrinsic_duration (),
- 0
- )
- ));
+ reel_subtitle_asset.reset (
+ new dcp::ReelSubtitleAsset (
+ _subtitle_asset,
+ dcp::Fraction (_film->video_frame_rate(), 1),
+ reel_picture_asset->intrinsic_duration (),
+ 0
+ )
+ );
} else {
/* We don't have a subtitle asset of our own; hopefully we have one to reference */
BOOST_FOREACH (ReferencedReelAsset j, refs) {
shared_ptr<dcp::ReelSubtitleAsset> k = dynamic_pointer_cast<dcp::ReelSubtitleAsset> (j.asset);
if (k && j.period == _period) {
- reel->add (k);
+ reel_subtitle_asset = k;
+ /* If we have a hash for this asset in the CPL, assume that it is correct */
+ if (k->hash()) {
+ k->asset_ref()->set_hash (k->hash().get());
+ }
}
}
}
+ if (reel_subtitle_asset) {
+ DCPOMATIC_ASSERT (reel_subtitle_asset->duration() == _period.duration().frames_round (_film->video_frame_rate ()));
+ reel->add (reel_subtitle_asset);
+ }
+
return reel;
}
void
-ReelWriter::calculate_digests (shared_ptr<Job> job)
+ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
{
- job->sub (_("Computing image digest"));
if (_picture_asset) {
- _picture_asset->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _picture_asset->hash (set_progress);
}
if (_sound_asset) {
- job->sub (_("Computing audio digest"));
- _sound_asset->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _sound_asset->hash (set_progress);
}
}
_sound_asset_writer->write (audio->data(), audio->frames());
}
- ++_total_written_audio_frames;
+ _total_written_audio_frames += audio->frames ();
}
void
s->set_reel_number (1);
s->set_time_code_rate (_film->video_frame_rate ());
s->set_start_time (dcp::Time ());
+ if (_film->encrypted ()) {
+ s->set_key (_film->key ());
+ }
_subtitle_asset = s;
}
}
- for (list<dcp::SubtitleString>::const_iterator i = subs.text.begin(); i != subs.text.end(); ++i) {
- _subtitle_asset->add (*i);
+ BOOST_FOREACH (SubtitleString i, subs.text) {
+ i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
+ i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
+ _subtitle_asset->add (i);
}
}
+
+bool
+ReelWriter::existing_picture_frame_ok (FILE* asset_file, FILE* info_file) const
+{
+ LOG_GENERAL ("Checking existing picture frame %1", _first_nonexistant_frame);
+
+ /* Read the data from the info file; for 3D we just check the left
+ frames until we find a good one.
+ */
+ dcp::FrameInfo const info = read_frame_info (info_file, _first_nonexistant_frame, _film->three_d () ? EYES_LEFT : EYES_BOTH);
+
+ bool ok = true;
+
+ /* Read the data from the asset and hash it */
+ dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
+ Data data (info.size);
+ size_t const read = fread (data.data().get(), 1, data.size(), asset_file);
+ LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
+ if (read != static_cast<size_t> (data.size ())) {
+ LOG_GENERAL ("Existing frame %1 is incomplete", _first_nonexistant_frame);
+ ok = false;
+ } else {
+ Digester digester;
+ digester.add (data.data().get(), data.size());
+ LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
+ if (digester.get() != info.hash) {
+ LOG_GENERAL ("Existing frame %1 failed hash check", _first_nonexistant_frame);
+ ok = false;
+ }
+ }
+
+ return ok;
+}