/*
- Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
#include "text_content.h"
#include "ffmpeg_content.h"
#include "dcp_content.h"
-#include "screen_kdm.h"
+#include "kdm_with_metadata.h"
#include "cinema.h"
#include "change_signaller.h"
#include "check_content_change_job.h"
+#include "ffmpeg_subtitle_stream.h"
+#include "font.h"
#include <libcxml/cxml.h>
#include <dcp/cpl.h>
#include <dcp/certificate_chain.h>
set_isdcf_date_today ();
_playlist_change_connection = _playlist->Change.connect (bind (&Film::playlist_change, this, _1));
- _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
+ _playlist_order_changed_connection = _playlist->OrderChange.connect (bind (&Film::playlist_order_changed, this));
_playlist_content_change_connection = _playlist->ContentChange.connect (bind (&Film::playlist_content_change, this, _1, _2, _3, _4));
+ _playlist_length_change_connection = _playlist->LengthChange.connect (bind(&Film::playlist_length_change, this));
if (dir) {
/* Make state.directory a complete path without ..s (where possible)
boost::filesystem::path result;
for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
if (*i == "..") {
- if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
+ boost::system::error_code ec;
+ if (boost::filesystem::is_symlink(result, ec) || result.filename() == "..") {
result /= *i;
} else {
result = result.parent_path ();
return p;
}
+
+boost::filesystem::path
+Film::subtitle_analysis_path (shared_ptr<const Content> content) const
+{
+ boost::filesystem::path p = dir ("analysis");
+
+ Digester digester;
+ digester.add (content->digest());
+
+ if (!content->text.empty()) {
+ shared_ptr<TextContent> tc = content->text.front();
+ digester.add (tc->x_scale());
+ digester.add (tc->y_scale());
+ BOOST_FOREACH (shared_ptr<dcpomatic::Font> i, tc->fonts()) {
+ digester.add (i->id());
+ }
+ if (tc->effect()) {
+ digester.add (tc->effect().get());
+ }
+ digester.add (tc->line_spacing());
+ digester.add (tc->outline_width());
+ }
+
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent>(content);
+ if (fc) {
+ digester.add (fc->subtitle_stream()->identifier());
+ }
+
+ p /= digester.get ();
+ return p;
+}
+
+
/** Add suitable Jobs to the JobManager to create a DCP for this Film.
* @param gui true if this is being called from a GUI tool.
* @param check true to check the content in the project for changes before making the DCP.
throw runtime_error (_("You must add some content to the DCP before creating it"));
}
+ if (length() == DCPTime()) {
+ throw runtime_error (_("The DCP is empty, perhaps because all the content has zero length."));
+ }
+
if (dcp_content_type() == 0) {
throw MissingSettingError (_("content type"));
}
BOOST_FOREACH (dcp::Rating i, _ratings) {
i.as_xml (root->add_child("Rating"));
}
+ root->add_child("ContentVersion")->add_child_text(_content_version);
_playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
return doc;
path = file (metadata_file);
}
+ if (!boost::filesystem::exists(*path)) {
+ throw FileNotFoundError(*path);
+ }
+
cxml::Document f ("Metadata");
f.read_file (path.get ());
_ratings.push_back (dcp::Rating(i));
}
+ _content_version = f.optional_string_child("ContentVersion").get_value_or("");
+
list<string> notes;
_playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
_playlist->move_later (shared_from_this(), c);
}
-/** @return length of the film from time 0 to the last thing on the playlist */
+/** @return length of the film from time 0 to the last thing on the playlist,
+ * with a minimum length of 1 second.
+ */
DCPTime
Film::length () const
{
- return _playlist->length(shared_from_this()).ceil(video_frame_rate());
+ return max(DCPTime::from_seconds(1), _playlist->length(shared_from_this()).ceil(video_frame_rate()));
}
int
}
}
+void
+Film::playlist_length_change ()
+{
+ LengthChange ();
+}
+
void
Film::playlist_change (ChangeType type)
{
).encrypt (signer, recipient, trusted_devices, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio);
}
-/** @param screens Screens to make KDMs for.
- * @param cpl_file Path to CPL to make KDMs for.
- * @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema.
- * @param until KDM to time expressed as a local time in the time zone of the Screen's Cinema.
- * @param formulation KDM formulation to use.
- * @param disable_forensic_marking_picture true to disable forensic marking of picture.
- * @param disable_forensic_marking_audio if not set, don't disable forensic marking of audio. If set to 0,
- * disable all forensic marking; if set above 0, disable forensic marking above that channel.
- */
-list<shared_ptr<ScreenKDM> >
-Film::make_kdms (
- list<shared_ptr<Screen> > screens,
- boost::filesystem::path cpl_file,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until,
- dcp::Formulation formulation,
- bool disable_forensic_marking_picture,
- optional<int> disable_forensic_marking_audio
- ) const
-{
- list<shared_ptr<ScreenKDM> > kdms;
-
- BOOST_FOREACH (shared_ptr<Screen> i, screens) {
- if (i->recipient) {
- dcp::EncryptedKDM const kdm = make_kdm (
- i->recipient.get(),
- i->trusted_device_thumbprints(),
- cpl_file,
- dcp::LocalTime (from, i->cinema ? i->cinema->utc_offset_hour() : 0, i->cinema ? i->cinema->utc_offset_minute() : 0),
- dcp::LocalTime (until, i->cinema ? i->cinema->utc_offset_hour() : 0, i->cinema ? i->cinema->utc_offset_minute() : 0),
- formulation,
- disable_forensic_marking_picture,
- disable_forensic_marking_audio
- );
-
- kdms.push_back (shared_ptr<ScreenKDM>(new DCPScreenKDM(i, kdm)));
- }
- }
-
- return kdms;
-}
/** @return The approximate disk space required to encode a DCP of this film with the
* current settings, in bytes.
break;
case REELTYPE_BY_VIDEO_CONTENT:
{
- optional<DCPTime> last_split;
- shared_ptr<Content> last_video;
- BOOST_FOREACH (shared_ptr<Content> c, content ()) {
+ /* Collect all reel boundaries */
+ list<DCPTime> split_points;
+ split_points.push_back (DCPTime());
+ split_points.push_back (len);
+ BOOST_FOREACH (shared_ptr<Content> c, content()) {
if (c->video) {
BOOST_FOREACH (DCPTime t, c->reel_split_points(shared_from_this())) {
- if (last_split) {
- p.push_back (DCPTimePeriod (last_split.get(), t));
- }
- last_split = t;
+ split_points.push_back (t);
}
- last_video = c;
+ split_points.push_back (c->end(shared_from_this()));
}
}
- DCPTime video_end = last_video ? last_video->end(shared_from_this()) : DCPTime(0);
- if (last_split) {
- /* Definitely go from the last split to the end of the video content */
- p.push_back (DCPTimePeriod (last_split.get(), video_end));
- }
-
- if (video_end < len) {
- /* And maybe go after that as well if there is any non-video hanging over the end */
- p.push_back (DCPTimePeriod (video_end, len));
+ split_points.sort ();
+ split_points.unique ();
+
+ /* Make them into periods, coalescing any that are less than 1 second long */
+ optional<DCPTime> last;
+ BOOST_FOREACH (DCPTime t, split_points) {
+ if (last && (t - *last) >= DCPTime::from_seconds(1)) {
+ /* Period from *last to t is long enough; use it and start a new one */
+ p.push_back (DCPTimePeriod(*last, t));
+ last = t;
+ } else if (!last) {
+ /* That was the first time, so start a new period */
+ last = t;
+ }
}
break;
}
case REELTYPE_BY_LENGTH:
{
DCPTime current;
- /* Integer-divide reel length by the size of one frame to give the number of frames per reel */
- Frame const reel_in_frames = _reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8);
+ /* Integer-divide reel length by the size of one frame to give the number of frames per reel,
+ * making sure we don't go less than 1s long.
+ */
+ Frame const reel_in_frames = max(_reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8), static_cast<Frame>(video_frame_rate()));
while (current < len) {
DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ()));
p.push_back (DCPTimePeriod (current, end));
_markers.erase (type);
}
+
+void
+Film::clear_markers ()
+{
+ ChangeSignaller<Film> ch (this, MARKERS);
+ _markers.clear ();
+}
+
+
void
Film::set_ratings (vector<dcp::Rating> r)
{
_ratings = r;
}
+void
+Film::set_content_version (string v)
+{
+ ChangeSignaller<Film> ch (this, CONTENT_VERSION);
+ _content_version = v;
+}
+
optional<DCPTime>
Film::marker (dcp::Marker type) const
{