/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
-#include "playlist.h"
-#include "video_content.h"
-#include "text_content.h"
-#include "ffmpeg_decoder.h"
-#include "ffmpeg_content.h"
-#include "image_decoder.h"
+
#include "audio_content.h"
+#include "compose.hpp"
+#include "config.h"
#include "content_factory.h"
#include "dcp_content.h"
+#include "digester.h"
+#include "ffmpeg_content.h"
+#include "ffmpeg_decoder.h"
+#include "image_decoder.h"
#include "job.h"
-#include "config.h"
+#include "playlist.h"
+#include "text_content.h"
#include "util.h"
-#include "digester.h"
-#include "compose.hpp"
+#include "video_content.h"
#include <libcxml/cxml.h>
#include <libxml++/libxml++.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/foreach.hpp>
+#include <boost/bind/placeholders.hpp>
#include <iostream>
#include "i18n.h"
-using std::list;
+
using std::cout;
-using std::vector;
-using std::min;
+using std::dynamic_pointer_cast;
+using std::list;
using std::max;
-using std::string;
+using std::min;
using std::pair;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using std::weak_ptr;
using boost::optional;
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::dynamic_pointer_cast;
+using namespace dcpomatic;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
Playlist::Playlist ()
- : _sequence (true)
- , _sequencing (false)
{
}
+
Playlist::~Playlist ()
{
+ boost::mutex::scoped_lock lm (_mutex);
_content.clear ();
disconnect ();
}
+
void
Playlist::content_change (weak_ptr<const Film> weak_film, ChangeType type, weak_ptr<Content> content, int property, bool frequent)
{
- /* Make sure we only hear about atomic changes (e.g. a PENDING always with the DONE/CANCELLED)
- Ignore any DONE/CANCELLED that arrives without a PENDING.
- */
- if (_checker.send (type, property)) {
- return;
- }
-
- shared_ptr<const Film> film = weak_film.lock ();
+ auto film = weak_film.lock ();
DCPOMATIC_ASSERT (film);
- if (type == CHANGE_TYPE_DONE) {
+ if (type == ChangeType::DONE) {
if (
property == ContentProperty::TRIM_START ||
property == ContentProperty::TRIM_END ||
property == ContentProperty::TRIM_START ||
property == ContentProperty::TRIM_END) {
- ContentList old = _content;
- sort (_content.begin(), _content.end(), ContentSorter ());
- if (_content != old) {
- OrderChanged ();
+ bool changed = false;
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ ContentList old = _content;
+ sort (_content.begin(), _content.end(), ContentSorter ());
+ changed = _content != old;
+ }
+
+ if (changed) {
+ OrderChange ();
}
+
+ /* The length might have changed, and that's good enough for this signal */
+ LengthChange ();
}
}
ContentChange (type, content, property, frequent);
}
+
void
Playlist::maybe_sequence (shared_ptr<const Film> film)
{
_sequencing = true;
+ auto cont = content ();
+
/* Keep track of the content that we've set the position of so that we don't
do it twice.
*/
DCPTime next_left;
DCPTime next_right;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: cont) {
if (!i->video) {
continue;
}
- if (i->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
+ if (i->video->frame_type() == VideoFrameType::THREE_D_RIGHT) {
i->set_position (film, next_right);
next_right = i->end(film);
} else {
/* Captions */
DCPTime next;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: cont) {
if (i->text.empty() || find (placed.begin(), placed.end(), i) != placed.end()) {
continue;
}
_sequencing = false;
}
+
string
Playlist::video_identifier () const
{
string t;
- BOOST_FOREACH (shared_ptr<const Content> i, _content) {
+ for (auto i: content()) {
bool burn = false;
- BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
+ for (auto j: i->text) {
if (j->burn()) {
burn = true;
}
return digester.get ();
}
+
/** @param film Film that this Playlist is for.
* @param node <Playlist> node.
* @param version Metadata version number.
void
Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
{
- BOOST_FOREACH (cxml::NodePtr i, node->node_children ("Content")) {
- shared_ptr<Content> content = content_factory (i, version, notes);
+ boost::mutex::scoped_lock lm (_mutex);
+
+ for (auto i: node->node_children ("Content")) {
+ auto content = content_factory (i, version, notes);
/* See if this content should be nudged to start on a video frame */
- DCPTime const old_pos = content->position();
+ auto const old_pos = content->position();
content->set_position(film, old_pos);
if (old_pos != content->position()) {
string note = _("Your project contains video content that was not aligned to a frame boundary.");
}
/* ...or have a start trim which is an integer number of frames */
- ContentTime const old_trim = content->trim_start();
- content->set_trim_start(old_trim);
+ auto const old_trim = content->trim_start();
+ content->set_trim_start(film, old_trim);
if (old_trim != content->trim_start()) {
string note = _("Your project contains video content whose trim was not aligned to a frame boundary.");
note += " ";
reconnect (film);
}
+
/** @param node <Playlist> node.
* @param with_content_paths true to include <Path> nodes in <Content> nodes, false to omit them.
*/
void
Playlist::as_xml (xmlpp::Node* node, bool with_content_paths)
{
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: content()) {
i->as_xml (node->add_child ("Content"), with_content_paths);
}
}
+
void
Playlist::add (shared_ptr<const Film> film, shared_ptr<Content> c)
{
- Change (CHANGE_TYPE_PENDING);
- _content.push_back (c);
- sort (_content.begin(), _content.end(), ContentSorter ());
- reconnect (film);
- Change (CHANGE_TYPE_DONE);
+ Change (ChangeType::PENDING);
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _content.push_back (c);
+ sort (_content.begin(), _content.end(), ContentSorter ());
+ reconnect (film);
+ }
+
+ Change (ChangeType::DONE);
+
+ LengthChange ();
}
+
void
Playlist::remove (shared_ptr<Content> c)
{
- Change (CHANGE_TYPE_PENDING);
+ Change (ChangeType::PENDING);
- ContentList::iterator i = _content.begin ();
- while (i != _content.end() && *i != c) {
- ++i;
+ bool cancelled = false;
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ auto i = _content.begin ();
+ while (i != _content.end() && *i != c) {
+ ++i;
+ }
+
+ if (i != _content.end()) {
+ _content.erase (i);
+ } else {
+ cancelled = true;
+ }
}
- if (i != _content.end ()) {
- _content.erase (i);
- Change (CHANGE_TYPE_DONE);
+ if (cancelled) {
+ Change (ChangeType::CANCELLED);
} else {
- Change (CHANGE_TYPE_CANCELLED);
+ Change (ChangeType::DONE);
}
/* This won't change order, so it does not need a sort */
+
+ LengthChange ();
}
+
void
Playlist::remove (ContentList c)
{
- Change (CHANGE_TYPE_PENDING);
+ Change (ChangeType::PENDING);
- BOOST_FOREACH (shared_ptr<Content> i, c) {
- ContentList::iterator j = _content.begin ();
- while (j != _content.end() && *j != i) {
- ++j;
- }
+ {
+ boost::mutex::scoped_lock lm (_mutex);
- if (j != _content.end ()) {
- _content.erase (j);
+ for (auto i: c) {
+ auto j = _content.begin ();
+ while (j != _content.end() && *j != i) {
+ ++j;
+ }
+
+ if (j != _content.end ()) {
+ _content.erase (j);
+ }
}
}
+ Change (ChangeType::DONE);
+
/* This won't change order, so it does not need a sort */
- Change (CHANGE_TYPE_DONE);
+ LengthChange ();
}
+
class FrameRateCandidate
{
public:
int dcp;
};
+
/** @return the best frame rate from Config::_allowed_dcp_frame_rates for the content in this list */
int
Playlist::best_video_frame_rate () const
{
- list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
+ auto const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
/* Work out what rates we could manage, including those achieved by using skip / repeat */
list<FrameRateCandidate> candidates;
/* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
- BOOST_FOREACH (int i, allowed_dcp_frame_rates) {
+ for (auto i: allowed_dcp_frame_rates) {
candidates.push_back (FrameRateCandidate(i, i));
}
/* Then the skip/repeat ones */
- BOOST_FOREACH (int i, allowed_dcp_frame_rates) {
+ for (auto i: allowed_dcp_frame_rates) {
candidates.push_back (FrameRateCandidate (float(i) / 2, i));
candidates.push_back (FrameRateCandidate (float(i) * 2, i));
}
/* Pick the best one */
float error = std::numeric_limits<float>::max ();
optional<FrameRateCandidate> best;
- list<FrameRateCandidate>::iterator i = candidates.begin();
+ auto i = candidates.begin();
while (i != candidates.end()) {
float this_error = 0;
- BOOST_FOREACH (shared_ptr<Content> j, _content) {
+ for (auto j: content()) {
if (!j->video || !j->video_frame_rate()) {
continue;
}
return best->dcp;
}
+
/** @return length of the playlist from time 0 to the last thing on the playlist */
DCPTime
Playlist::length (shared_ptr<const Film> film) const
{
DCPTime len;
- BOOST_FOREACH (shared_ptr<const Content> i, _content) {
+ for (auto i: content()) {
len = max (len, i->end(film));
}
return len;
}
+
/** @return position of the first thing on the playlist, if it's not empty */
optional<DCPTime>
Playlist::start () const
{
- if (_content.empty ()) {
- return optional<DCPTime> ();
+ auto cont = content ();
+ if (cont.empty()) {
+ return {};
}
- DCPTime start = DCPTime::max ();
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ auto start = DCPTime::max ();
+ for (auto i: cont) {
start = min (start, i->position ());
}
return start;
}
+
+/** Must be called with a lock held on _mutex */
void
Playlist::disconnect ()
{
- for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
- i->disconnect ();
+ for (auto& i: _content_connections) {
+ i.disconnect ();
}
_content_connections.clear ();
}
+
+/** Must be called with a lock held on _mutex */
void
Playlist::reconnect (shared_ptr<const Film> film)
{
disconnect ();
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: _content) {
_content_connections.push_back (i->Change.connect(boost::bind(&Playlist::content_change, this, film, _1, _2, _3, _4)));
}
}
+
DCPTime
Playlist::video_end (shared_ptr<const Film> film) const
{
DCPTime end;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: content()) {
if (i->video) {
end = max (end, i->end(film));
}
return end;
}
+
DCPTime
Playlist::text_end (shared_ptr<const Film> film) const
{
DCPTime end;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: content()) {
if (!i->text.empty ()) {
end = max (end, i->end(film));
}
return end;
}
+
FrameRateChange
Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
{
- for (ContentList::const_reverse_iterator i = _content.rbegin(); i != _content.rend(); ++i) {
+ auto cont = content ();
+ for (ContentList::const_reverse_iterator i = cont.rbegin(); i != cont.rend(); ++i) {
if (!(*i)->video) {
continue;
}
return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
}
+
void
Playlist::set_sequence (bool s)
{
_sequence = s;
}
+
bool
ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
{
return a->digest() < b->digest();
}
+
/** @return content in ascending order of position */
ContentList
Playlist::content () const
{
+ boost::mutex::scoped_lock lm (_mutex);
return _content;
}
+
void
Playlist::repeat (shared_ptr<const Film> film, ContentList c, int n)
{
- pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
- BOOST_FOREACH (shared_ptr<Content> i, c) {
+ pair<DCPTime, DCPTime> range (DCPTime::max(), DCPTime());
+ for (auto i: c) {
range.first = min (range.first, i->position ());
range.second = max (range.second, i->position ());
range.first = min (range.first, i->end(film));
range.second = max (range.second, i->end(film));
}
- Change (CHANGE_TYPE_PENDING);
+ Change (ChangeType::PENDING);
- DCPTime pos = range.second;
- for (int i = 0; i < n; ++i) {
- BOOST_FOREACH (shared_ptr<Content> j, c) {
- shared_ptr<Content> copy = j->clone ();
- copy->set_position (film, pos + copy->position() - range.first);
- _content.push_back (copy);
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ DCPTime pos = range.second;
+ for (int i = 0; i < n; ++i) {
+ for (auto j: c) {
+ auto copy = j->clone ();
+ copy->set_position (film, pos + copy->position() - range.first);
+ _content.push_back (copy);
+ }
+ pos += range.second - range.first;
}
- pos += range.second - range.first;
- }
- sort (_content.begin(), _content.end(), ContentSorter ());
+ sort (_content.begin(), _content.end(), ContentSorter ());
+ reconnect (film);
+ }
- reconnect (film);
- Change (CHANGE_TYPE_DONE);
+ Change (ChangeType::DONE);
}
+
void
Playlist::move_earlier (shared_ptr<const Film> film, shared_ptr<Content> c)
{
- ContentList::iterator previous = _content.end ();
- ContentList::iterator i = _content.begin();
- while (i != _content.end() && *i != c) {
+ auto cont = content ();
+ auto previous = cont.end();
+ auto i = cont.begin();
+ while (i != cont.end() && *i != c) {
previous = i;
++i;
}
- DCPOMATIC_ASSERT (i != _content.end ());
- if (previous == _content.end ()) {
+ DCPOMATIC_ASSERT (i != cont.end());
+ if (previous == cont.end()) {
return;
}
- shared_ptr<Content> previous_c = *previous;
+ auto previous_c = *previous;
- DCPTime const p = previous_c->position ();
+ auto const p = previous_c->position ();
previous_c->set_position (film, p + c->length_after_trim(film));
c->set_position (film, p);
}
+
void
Playlist::move_later (shared_ptr<const Film> film, shared_ptr<Content> c)
{
- ContentList::iterator i = _content.begin();
- while (i != _content.end() && *i != c) {
+ auto cont = content ();
+ auto i = cont.begin();
+ while (i != cont.end() && *i != c) {
++i;
}
- DCPOMATIC_ASSERT (i != _content.end ());
+ DCPOMATIC_ASSERT (i != cont.end());
ContentList::iterator next = i;
++next;
- if (next == _content.end ()) {
+ if (next == cont.end()) {
return;
}
- shared_ptr<Content> next_c = *next;
+ auto next_c = *next;
next_c->set_position (film, c->position());
c->set_position (film, c->position() + next_c->length_after_trim(film));
}
+
int64_t
Playlist::required_disk_space (shared_ptr<const Film> film, int j2k_bandwidth, int audio_channels, int audio_frame_rate) const
{
int64_t video = uint64_t (j2k_bandwidth / 8) * length(film).seconds();
int64_t audio = uint64_t (audio_channels * audio_frame_rate * 3) * length(film).seconds();
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
- shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
+ for (auto i: content()) {
+ auto d = dynamic_pointer_cast<DCPContent> (i);
if (d) {
if (d->reference_video()) {
video -= uint64_t (j2k_bandwidth / 8) * d->length_after_trim(film).seconds();
return video + audio + 65536;
}
+
string
Playlist::content_summary (shared_ptr<const Film> film, DCPTimePeriod period) const
{
string best_summary;
int best_score = -1;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: content()) {
int score = 0;
- optional<DCPTimePeriod> const o = DCPTimePeriod(i->position(), i->end(film)).overlap (period);
+ auto const o = i->period(film).overlap(period);
if (o) {
score += 100 * o.get().duration().get() / period.duration().get();
}
return best_summary;
}
+
pair<double, double>
Playlist::speed_up_range (int dcp_video_frame_rate) const
{
pair<double, double> range (DBL_MAX, -DBL_MAX);
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ for (auto i: content()) {
if (!i->video) {
continue;
}