/*
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 "dcp_content_type.h"
#include "ratio.h"
#include "cross.h"
-#include "safe_stringstream.h"
#include "environment_info.h"
-#include "raw_convert.h"
#include "audio_processor.h"
-#include "md5_digester.h"
+#include "digester.h"
#include "compose.hpp"
#include "screen.h"
#include "audio_content.h"
#include <dcp/util.h>
#include <dcp/local_time.h>
#include <dcp/decrypted_kdm.h>
+#include <dcp/raw_convert.h>
#include <libxml++/libxml++.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
+#include <boost/regex.hpp>
#include <unistd.h>
#include <stdexcept>
#include <iostream>
using boost::dynamic_pointer_cast;
using boost::optional;
using boost::is_any_of;
+using dcp::raw_convert;
#define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
#define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
*
* 32 -> 33
* Changed <Period> to <Subtitle> in FFmpegSubtitleStream
+ * 33 -> 34
+ * Content only contains audio/subtitle-related tags if those things
+ * are present.
+ * 34 -> 35
+ * VideoFrameType in VideoContent is a string rather than an integer.
+ * 35 -> 36
+ * EffectColour rather than OutlineColour in Subtitle.
*/
-int const Film::current_state_version = 33;
+int const Film::current_state_version = 36;
/** Construct a Film object in a given directory.
*
, _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
, _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
, _video_frame_rate (24)
- , _audio_channels (6)
+ , _audio_channels (Config::instance()->default_dcp_audio_channels ())
, _three_d (false)
, _sequence (true)
, _interop (Config::instance()->default_interop ())
{
DCPOMATIC_ASSERT (container ());
- SafeStringStream s;
- s.imbue (std::locale::classic ());
-
- s << container()->id()
- << "_" << resolution_to_string (_resolution)
- << "_" << _playlist->video_identifier()
- << "_" << _video_frame_rate
- << "_" << j2k_bandwidth();
+ string s = container()->id()
+ + "_" + resolution_to_string (_resolution)
+ + "_" + _playlist->video_identifier()
+ + "_" + raw_convert<string>(_video_frame_rate)
+ + "_" + raw_convert<string>(j2k_bandwidth());
if (encrypted ()) {
- s << "_E";
+ s += "_E";
} else {
- s << "_P";
+ s += "_P";
}
if (_interop) {
- s << "_I";
+ s += "_I";
} else {
- s << "_S";
+ s += "_S";
}
if (_three_d) {
- s << "_3D";
+ s += "_3D";
}
- return s.str ();
+ return s;
}
/** @return The file to write video frame info to */
{
boost::filesystem::path p = dir ("analysis");
- MD5Digester digester;
+ Digester digester;
BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (i);
- if (!ac) {
+ if (!i->audio) {
continue;
}
- digester.add (ac->digest ());
- digester.add (ac->audio_mapping().digest ());
+ digester.add (i->digest ());
+ digester.add (i->audio->mapping().digest ());
if (playlist->content().size() != 1) {
/* Analyses should be considered equal regardless of gain
if they were made from just one piece of content. This
analysis at the plotting stage rather than having to
recompute it.
*/
- digester.add (ac->audio_gain ());
+ digester.add (i->audio->gain ());
}
}
if (_audio_processor) {
root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
}
- root->add_child("ReelType")->add_child_text (raw_convert<string> (_reel_type));
+ root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
_playlist->as_xml (root->add_child ("Playlist"));
}
} else {
BOOST_FOREACH (shared_ptr<Content> i, content ()) {
- shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (i);
- if (ac) {
- list<int> c = ac->audio_mapping().mapped_output_channels ();
+ if (i->audio) {
+ list<int> c = i->audio->mapping().mapped_output_channels ();
copy (c.begin(), c.end(), back_inserter (mapped));
}
}
string
Film::isdcf_name (bool if_created_now) const
{
- SafeStringStream d;
+ string d;
string raw_name = name ();
fixed_name = fixed_name.substr (0, 14);
}
- d << fixed_name;
+ d += fixed_name;
if (dcp_content_type()) {
- d << "_" << dcp_content_type()->isdcf_name();
- d << "-" << isdcf_metadata().content_version;
+ d += "_" + dcp_content_type()->isdcf_name();
+ d += "-" + raw_convert<string>(isdcf_metadata().content_version);
}
ISDCFMetadata const dm = isdcf_metadata ();
if (dm.temp_version) {
- d << "-Temp";
+ d += "-Temp";
}
if (dm.pre_release) {
- d << "-Pre";
+ d += "-Pre";
}
if (dm.red_band) {
- d << "-RedBand";
+ d += "-RedBand";
}
if (!dm.chain.empty ()) {
- d << "-" << dm.chain;
+ d += "-" + dm.chain;
}
if (three_d ()) {
- d << "-3D";
+ d += "-3D";
}
if (dm.two_d_version_of_three_d) {
- d << "-2D";
+ d += "-2D";
}
if (!dm.mastered_luminance.empty ()) {
- d << "-" << dm.mastered_luminance;
+ d += "-" + dm.mastered_luminance;
}
if (video_frame_rate() != 24) {
- d << "-" << video_frame_rate();
+ d += "-" + raw_convert<string>(video_frame_rate());
}
if (container()) {
- d << "_" << container()->isdcf_name();
+ d += "_" + container()->isdcf_name();
}
/* XXX: this uses the first bit of content only */
if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
Ratio const * content_ratio = 0;
BOOST_FOREACH (shared_ptr<Content> i, content ()) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (i);
- if (vc) {
+ if (i->video) {
/* Here's the first piece of video content */
- if (vc->scale().ratio ()) {
- content_ratio = vc->scale().ratio ();
+ if (i->video->scale().ratio ()) {
+ content_ratio = i->video->scale().ratio ();
} else {
- content_ratio = Ratio::from_ratio (vc->video_size().ratio ());
+ content_ratio = Ratio::from_ratio (i->video->size().ratio ());
}
break;
}
}
if (content_ratio && content_ratio != container()) {
- d << "-" << content_ratio->isdcf_name();
+ d += "-" + content_ratio->isdcf_name();
}
}
if (!dm.audio_language.empty ()) {
- d << "_" << dm.audio_language;
+ d += "_" + dm.audio_language;
if (!dm.subtitle_language.empty()) {
bool burnt_in = true;
BOOST_FOREACH (shared_ptr<Content> i, content ()) {
- shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (i);
- if (!sc) {
+ if (!i->subtitle) {
continue;
}
- if (sc->use_subtitles() && !sc->burn_subtitles()) {
+ if (i->subtitle->use() && !i->subtitle->burn()) {
burnt_in = false;
}
}
transform (language.begin(), language.end(), language.begin(), ::toupper);
}
- d << "-" << language;
+ d += "-" + language;
} else {
- d << "-XX";
+ d += "-XX";
}
}
if (!dm.territory.empty ()) {
- d << "_" << dm.territory;
+ d += "_" + dm.territory;
if (dm.rating.empty ()) {
- d << "-NR";
+ d += "-NR";
} else {
- d << "-" << dm.rating;
+ d += "-" + dm.rating;
}
}
}
if (non_lfe) {
- d << "_" << non_lfe << lfe;
+ d += String::compose("_%1%2", non_lfe, lfe);
}
/* XXX: HI/VI */
- d << "_" << resolution_to_string (_resolution);
+ d += "_" + resolution_to_string (_resolution);
if (!dm.studio.empty ()) {
- d << "_" << dm.studio;
+ d += "_" + dm.studio;
}
if (if_created_now) {
- d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
+ d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
} else {
- d << "_" << boost::gregorian::to_iso_string (_isdcf_date);
+ d += "_" + boost::gregorian::to_iso_string (_isdcf_date);
}
if (!dm.facility.empty ()) {
- d << "_" << dm.facility;
+ d += "_" + dm.facility;
}
if (_interop) {
- d << "_IOP";
+ d += "_IOP";
} else {
- d << "_SMPTE";
+ d += "_SMPTE";
}
if (three_d ()) {
- d << "-3D";
+ d += "-3D";
}
bool vf = false;
}
if (vf) {
- d << "_VF";
+ d += "_VF";
} else {
- d << "_OV";
+ d += "_OV";
}
- return d.str ();
+ return d;
}
/** @return name to give the DCP */
switch (p) {
case Film::CONTENT:
- set_video_frame_rate (_playlist->best_dcp_frame_rate ());
+ set_video_frame_rate (_playlist->best_video_frame_rate ());
break;
case Film::VIDEO_FRAME_RATE:
case Film::SEQUENCE:
p /= "j2c";
p /= video_identifier ();
- SafeStringStream s;
- s.width (8);
- s << setfill('0') << reel << "_" << frame;
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame);
+ string s (buffer);
if (eyes == EYES_LEFT) {
- s << ".L";
+ s += ".L";
} else if (eyes == EYES_RIGHT) {
- s << ".R";
+ s += ".R";
}
- s << ".j2c";
+ s += ".j2c";
if (tmp) {
- s << ".tmp";
+ s += ".tmp";
}
- p /= s.str();
+ p /= s;
return file (p);
}
try {
dcp::DCP dcp (*i);
dcp.read ();
+ DCPOMATIC_ASSERT (dcp.cpls().front()->file());
out.push_back (
CPLSummary (
i->path().leaf().string(),
dcp.cpls().front()->id(),
dcp.cpls().front()->annotation_text(),
- dcp.cpls().front()->file()
+ dcp.cpls().front()->file().get()
)
);
} catch (...) {
}
add_content (content);
- if (Config::instance()->automatic_audio_analysis() && dynamic_pointer_cast<AudioContent> (content)) {
+ if (Config::instance()->automatic_audio_analysis() && content->audio) {
shared_ptr<Playlist> playlist (new Playlist);
playlist->add (content);
boost::signals2::connection c;
Film::add_content (shared_ptr<Content> c)
{
/* Add {video,subtitle} content after any existing {video,subtitle} content */
- if (dynamic_pointer_cast<VideoContent> (c)) {
+ if (c->video) {
c->set_position (_playlist->video_end ());
- } else if (dynamic_pointer_cast<SubtitleContent> (c)) {
+ } else if (c->subtitle) {
c->set_position (_playlist->subtitle_end ());
}
int
Film::best_video_frame_rate () const
{
- return _playlist->best_dcp_frame_rate ();
+ return _playlist->best_video_frame_rate ();
}
FrameRateChange
{
_dirty = true;
- if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
- set_video_frame_rate (_playlist->best_dcp_frame_rate ());
- } else if (p == AudioContentProperty::AUDIO_STREAMS) {
+ if (p == ContentProperty::VIDEO_FRAME_RATE) {
+ set_video_frame_rate (_playlist->best_video_frame_rate ());
+ } else if (p == AudioContentProperty::STREAMS) {
signal_changed (NAME);
}
Film::audio_frame_rate () const
{
BOOST_FOREACH (shared_ptr<Content> i, content ()) {
- shared_ptr<AudioContent> a = dynamic_pointer_cast<AudioContent> (i);
- if (a && a->has_rate_above_48k ()) {
+ if (i->audio && i->audio->has_rate_above_48k ()) {
return 96000;
}
}
ContentList cl = content ();
BOOST_FOREACH (shared_ptr<Content>& c, cl) {
- shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c);
- if (sc) {
- languages.insert (sc->subtitle_language ());
+ if (c->subtitle) {
+ languages.insert (c->subtitle->language ());
}
}
/** Change the gains of the supplied AudioMapping to make it a default
* for this film. The defaults are guessed based on what processor (if any)
- * is in use and the number of input channels.
+ * is in use, the number of input channels and any filename supplied.
*/
void
-Film::make_audio_mapping_default (AudioMapping& mapping) const
+Film::make_audio_mapping_default (AudioMapping& mapping, optional<boost::filesystem::path> filename) const
{
+ static string const regex[] = {
+ ".*[\\._-]L[\\._-].*",
+ ".*[\\._-]R[\\._-].*",
+ ".*[\\._-]C[\\._-].*",
+ ".*[\\._-]Lfe[\\._-].*",
+ ".*[\\._-]Ls[\\._-].*",
+ ".*[\\._-]Rs[\\._-].*"
+ };
+
+ static int const regexes = sizeof(regex) / sizeof(*regex);
+
if (audio_processor ()) {
audio_processor()->make_audio_mapping_default (mapping);
} else {
mapping.make_zero ();
if (mapping.input_channels() == 1) {
- /* Mono -> Centre */
- mapping.set (0, static_cast<int> (dcp::CENTRE), 1);
+ bool guessed = false;
+
+ /* See if we can guess where this stream should go */
+ if (filename) {
+ for (int i = 0; i < regexes; ++i) {
+ boost::regex e (regex[i], boost::regex::icase);
+ if (boost::regex_match (filename->string(), e) && i < mapping.output_channels()) {
+ mapping.set (0, i, 1);
+ guessed = true;
+ }
+ }
+ }
+
+ if (!guessed) {
+ /* If we have no idea, just put it on centre */
+ mapping.set (0, static_cast<int> (dcp::CENTRE), 1);
+ }
} else {
/* 1:1 mapping */
for (int i = 0; i < min (mapping.input_channels(), mapping.output_channels()); ++i) {
case REELTYPE_BY_VIDEO_CONTENT:
{
optional<DCPTime> last_split;
- shared_ptr<VideoContent> last_video;
- ContentList cl = content ();
+ shared_ptr<Content> last_video;
BOOST_FOREACH (shared_ptr<Content> c, content ()) {
- shared_ptr<VideoContent> v = dynamic_pointer_cast<VideoContent> (c);
- if (v) {
- BOOST_FOREACH (DCPTime t, v->reel_split_points()) {
+ if (c->video) {
+ BOOST_FOREACH (DCPTime t, c->reel_split_points()) {
if (last_split) {
p.push_back (DCPTimePeriod (last_split.get(), t));
}
last_split = t;
}
- last_video = v;
+ last_video = c;
}
}
return p;
}
+
+string
+Film::content_summary (DCPTimePeriod period) const
+{
+ return _playlist->content_summary (period);
+}
+
+list<string>
+Film::fix_conflicting_settings ()
+{
+ list<string> notes;
+
+ list<boost::filesystem::path> was_referencing;
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
+ shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
+ if (d) {
+ list<string> reasons;
+ bool was = false;
+ if (!d->can_reference_video(reasons) && d->reference_video()) {
+ d->set_reference_video (false);
+ was = true;
+ }
+ if (!d->can_reference_audio(reasons) && d->reference_audio()) {
+ d->set_reference_audio (false);
+ was = true;
+ }
+ if (!d->can_reference_subtitle(reasons) && d->reference_subtitle()) {
+ d->set_reference_subtitle (false);
+ was = true;
+ }
+ if (was) {
+ was_referencing.push_back (d->path(0).parent_path().filename());
+ }
+ }
+ }
+
+ BOOST_FOREACH (boost::filesystem::path d, was_referencing) {
+ notes.push_back (String::compose (_("The DCP %1 was being referred to by this film. This not now possible because the reel sizes in the film no longer agree with those in the imported DCP.\n\nSetting the 'Reel type' to 'split by video content' will probably help.\n\nAfter doing that you would need to re-tick the appropriate 'refer to existing DCP' checkboxes."), d.string()));
+ }
+
+ return notes;
+}