#include "config.h"
#include "util.h"
#include "digester.h"
+#include "compose.hpp"
#include <libcxml/cxml.h>
#include <libxml++/libxml++.h>
#include <boost/shared_ptr.hpp>
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 ();
DCPOMATIC_ASSERT (film);
property == ContentProperty::TRIM_START ||
property == ContentProperty::TRIM_END) {
- ContentList old = _content;
- sort (_content.begin(), _content.end(), ContentSorter ());
- if (_content != old) {
+ bool changed = false;
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ ContentList old = _content;
+ sort (_content.begin(), _content.end(), ContentSorter ());
+ changed = _content != old;
+ }
+
+ if (changed) {
OrderChanged ();
}
}
_sequencing = true;
+ ContentList 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) {
+ BOOST_FOREACH (shared_ptr<Content> i, cont) {
if (!i->video) {
continue;
}
/* Captions */
DCPTime next;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> i, cont) {
if (i->text.empty() || find (placed.begin(), placed.end(), i) != placed.end()) {
continue;
}
{
string t;
- BOOST_FOREACH (shared_ptr<const Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<const Content> i, content()) {
bool burn = false;
BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
if (j->burn()) {
void
Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
{
+ boost::mutex::scoped_lock lm (_mutex);
+
BOOST_FOREACH (cxml::NodePtr i, node->node_children ("Content")) {
- _content.push_back (content_factory (film, i, version, notes));
+ shared_ptr<Content> 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();
+ 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.");
+ note += " ";
+ if (old_pos < content->position()) {
+ note += String::compose(
+ _("The file %1 has been moved %2 milliseconds later."),
+ content->path_summary(), DCPTime(content->position() - old_pos).seconds() * 1000
+ );
+ } else {
+ note += String::compose(
+ _("The file %1 has been moved %2 milliseconds earlier."),
+ content->path_summary(), DCPTime(content->position() - old_pos).seconds() * 1000
+ );
+ }
+ notes.push_back (note);
+ }
+
+ /* ...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);
+ if (old_trim != content->trim_start()) {
+ string note = _("Your project contains video content whose trim was not aligned to a frame boundary.");
+ note += " ";
+ if (old_trim < content->trim_start()) {
+ note += String::compose(
+ _("The file %1 has been trimmed by %2 milliseconds more."),
+ content->path_summary(), ContentTime(content->trim_start() - old_trim).seconds() * 1000
+ );
+ } else {
+ note += String::compose(
+ _("The file %1 has been trimmed by %2 milliseconds less."),
+ content->path_summary(), ContentTime(old_trim - content->trim_start()).seconds() * 1000
+ );
+ }
+ notes.push_back (note);
+ }
+
+ _content.push_back (content);
}
/* This shouldn't be necessary but better safe than sorry (there could be old files) */
void
Playlist::as_xml (xmlpp::Node* node, bool with_content_paths)
{
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
i->as_xml (node->add_child ("Content"), with_content_paths);
}
}
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);
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _content.push_back (c);
+ sort (_content.begin(), _content.end(), ContentSorter ());
+ reconnect (film);
+ }
+
Change (CHANGE_TYPE_DONE);
}
{
Change (CHANGE_TYPE_PENDING);
- ContentList::iterator i = _content.begin ();
- while (i != _content.end() && *i != c) {
- ++i;
+ bool cancelled = false;
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ ContentList::iterator 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);
- } else {
+ if (cancelled) {
Change (CHANGE_TYPE_CANCELLED);
+ } else {
+ Change (CHANGE_TYPE_DONE);
}
/* This won't change order, so it does not need a sort */
{
Change (CHANGE_TYPE_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);
+
+ BOOST_FOREACH (shared_ptr<Content> i, c) {
+ ContentList::iterator j = _content.begin ();
+ while (j != _content.end() && *j != i) {
+ ++j;
+ }
- if (j != _content.end ()) {
- _content.erase (j);
+ if (j != _content.end ()) {
+ _content.erase (j);
+ }
}
}
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<FrameRateCandidate> candidates;
/* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
- for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
- candidates.push_back (FrameRateCandidate (*i, *i));
+ BOOST_FOREACH (int i, allowed_dcp_frame_rates) {
+ candidates.push_back (FrameRateCandidate(i, i));
}
/* Then the skip/repeat ones */
- for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
- candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
- candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
+ BOOST_FOREACH (int 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 */
while (i != candidates.end()) {
float this_error = 0;
- BOOST_FOREACH (shared_ptr<Content> j, _content) {
+ BOOST_FOREACH (shared_ptr<Content> j, content()) {
if (!j->video || !j->video_frame_rate()) {
continue;
}
Playlist::length (shared_ptr<const Film> film) const
{
DCPTime len;
- BOOST_FOREACH (shared_ptr<const Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<const Content> i, content()) {
len = max (len, i->end(film));
}
optional<DCPTime>
Playlist::start () const
{
- if (_content.empty ()) {
+ ContentList cont = content ();
+ if (cont.empty()) {
return optional<DCPTime> ();
}
DCPTime start = DCPTime::max ();
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> 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 ();
+ BOOST_FOREACH (boost::signals2::connection& 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)
{
Playlist::video_end (shared_ptr<const Film> film) const
{
DCPTime end;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
if (i->video) {
end = max (end, i->end(film));
}
Playlist::text_end (shared_ptr<const Film> film) const
{
DCPTime end;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
if (!i->text.empty ()) {
end = max (end, i->end(film));
}
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) {
+ ContentList cont = content ();
+ for (ContentList::const_reverse_iterator i = cont.rbegin(); i != cont.rend(); ++i) {
if (!(*i)->video) {
continue;
}
ContentList
Playlist::content () const
{
+ boost::mutex::scoped_lock lm (_mutex);
return _content;
}
Change (CHANGE_TYPE_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 (film);
- 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) {
+ 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);
+ }
+ 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);
}
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) {
+ ContentList cont = content ();
+ ContentList::iterator previous = cont.end();
+ ContentList::iterator 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;
}
void
Playlist::move_later (shared_ptr<const Film> film, shared_ptr<Content> c)
{
- ContentList::iterator i = _content.begin();
- while (i != _content.end() && *i != c) {
+ ContentList cont = content ();
+ ContentList::iterator 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;
}
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) {
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
if (d) {
if (d->reference_video()) {
{
string best_summary;
int best_score = -1;
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
int score = 0;
optional<DCPTimePeriod> const o = DCPTimePeriod(i->position(), i->end(film)).overlap (period);
if (o) {
{
pair<double, double> range (DBL_MAX, -DBL_MAX);
- BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
if (!i->video) {
continue;
}