Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/framework/mlt.vers
Original file line number Diff line number Diff line change
Expand Up @@ -705,3 +705,8 @@ MLT_7.42.0 {
mlt_frame_push_image_with_fx_cut;
mlt_producer_pass_frame_properties;
} MLT_7.40.0;

MLT_7.44.0 {
global:
mlt_properties_revision;
} MLT_7.42.0;
64 changes: 58 additions & 6 deletions src/framework/mlt_playlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,54 @@ struct playlist_entry_s
mlt_position producer_length;
mlt_event event;
int preservation_hack;
unsigned int revision;
int is_blank;
};

/** Determine if a playlist entry is a blank.
*
* Whether the producer of an entry is a blank does not change, so it is
* determined once when the entry is created. An entry whose producer has been
* closed by autoclose counts as a blank, as mlt_producer_is_blank() does for NULL.
* \private \memberof mlt_playlist_s
* \param entry a playlist entry
* \return true if the entry is a blank
*/

static inline int entry_is_blank(playlist_entry *entry)
{
return entry->producer == NULL || entry->is_blank;
}

/** Determine if the producer of a playlist entry may have changed.
*
* The producer's points are only read again when one of its properties has
* changed since the last time this was checked for the entry.
* \private \memberof mlt_playlist_s
* \param entry a playlist entry with a producer
* \return true if the producer may have changed
*/

static int entry_producer_changed(playlist_entry *entry)
{
unsigned int revision = mlt_properties_revision(MLT_PRODUCER_PROPERTIES(entry->producer));
int changed = revision != entry->revision;
Comment thread
eseifert marked this conversation as resolved.
Resolved
entry->revision = revision;
return changed;
}

/** Make the next refresh compare an entry with the points of its producer.
*
* \private \memberof mlt_playlist_s
* \param entry a playlist entry
*/

static void entry_force_refresh(playlist_entry *entry)
{
if (entry->producer)
entry->revision = mlt_properties_revision(MLT_PRODUCER_PROPERTIES(entry->producer)) - 1;
}

/* Forward declarations
*/

Expand Down Expand Up @@ -187,7 +233,7 @@ static int mlt_playlist_virtual_refresh(mlt_playlist self)
for (i = 0; i < self->count; i++) {
// Get the producer
mlt_producer producer = self->list[i]->producer;
if (producer) {
if (producer && entry_producer_changed(self->list[i])) {
int current_length = mlt_producer_get_playtime(producer);

// Check if the length of the producer has changed
Expand Down Expand Up @@ -257,9 +303,10 @@ static int mlt_playlist_virtual_append(mlt_playlist self,
mlt_producer producer = NULL;
mlt_properties properties = NULL;
mlt_properties parent = NULL;
int is_blank = mlt_producer_is_blank(source);

// If we have a cut, then use the in/out points from the cut
if (mlt_producer_is_blank(source)) {
if (is_blank) {
mlt_position length = out - in + 1;
mlt_producer blank = blank_producer(self);

Expand Down Expand Up @@ -334,7 +381,9 @@ static int mlt_playlist_virtual_append(mlt_playlist self,
self->list[self->count]->frame_out = out;
self->list[self->count]->frame_count = out - in + 1;
self->list[self->count]->repeat = 1;
self->list[self->count]->is_blank = is_blank;
self->list[self->count]->producer_length = mlt_producer_get_playtime(producer);
entry_force_refresh(self->list[self->count]);
self->list[self->count]->event = mlt_events_listen(parent,
self,
"producer-changed",
Expand Down Expand Up @@ -519,6 +568,7 @@ static mlt_producer mlt_playlist_virtual_set_out(mlt_playlist self)
// Update the frame_count for the changed clip (hmmm)
self->list[i]->frame_out = position;
self->list[i]->frame_count = self->list[i]->frame_out - self->list[i]->frame_in + 1;
entry_force_refresh(self->list[i]);

// Refresh the playlist
mlt_playlist_virtual_refresh(self);
Expand Down Expand Up @@ -1707,15 +1757,15 @@ void mlt_playlist_consolidate_blanks(mlt_playlist self, int keep_length)
playlist_entry *left = self->list[i - 1];
playlist_entry *right = self->list[i];

if (mlt_producer_is_blank(left->producer) && mlt_producer_is_blank(right->producer)) {
if (entry_is_blank(left) && entry_is_blank(right)) {
mlt_playlist_resize_clip(self, i - 1, 0, left->frame_count + right->frame_count - 1);
mlt_playlist_remove(self, i--);
}
}

if (!keep_length && self->count > 0) {
playlist_entry *last = self->list[self->count - 1];
if (mlt_producer_is_blank(last->producer))
if (entry_is_blank(last))
mlt_playlist_remove(self, self->count - 1);
}

Expand All @@ -1734,7 +1784,7 @@ void mlt_playlist_consolidate_blanks(mlt_playlist self, int keep_length)

int mlt_playlist_is_blank(mlt_playlist self, int clip)
{
return self == NULL || mlt_producer_is_blank(mlt_playlist_get_clip(self, clip));
return self == NULL || clip < 0 || clip >= self->count || entry_is_blank(self->list[clip]);
}

/** Determine if the specified position is a blank.
Expand All @@ -1747,7 +1797,9 @@ int mlt_playlist_is_blank(mlt_playlist self, int clip)

int mlt_playlist_is_blank_at(mlt_playlist self, mlt_position position)
{
return self == NULL || mlt_producer_is_blank(mlt_playlist_get_clip_at(self, position));
int index = 0, total = 0;
return self == NULL || mlt_playlist_locate(self, &position, &index, &total) == NULL
|| entry_is_blank(self->list[index]);
}

/** Replace the specified clip with a blank and return the clip.
Expand Down
25 changes: 25 additions & 0 deletions src/framework/mlt_properties.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <locale.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -66,6 +67,7 @@ typedef struct
mlt_properties *children_properties;
char **children_names;
int children_count;
atomic_uint revision;
} property_list;

/* Memory leak checks */
Expand Down Expand Up @@ -485,6 +487,26 @@ int mlt_properties_ref_count(mlt_properties self)
return 0;
}

/** Get the revision of a properties list.
*
* The revision changes whenever a property is set, cleared or renamed, so
* comparing it with a previously saved value tells whether any property may
* have changed since then. The value itself has no meaning and wraps around.
*
* \public \memberof mlt_properties_s
* \param self a properties list
* \return the current revision
*/

unsigned int mlt_properties_revision(mlt_properties self)
{
if (self != NULL) {
property_list *list = self->local;
return atomic_load(&list->revision);
}
return 0;
}

/** Set a properties list to be a mirror copy of another.
*
* Note that this does not copy all existing properties. Rather, you must
Expand Down Expand Up @@ -736,6 +758,7 @@ static mlt_property mlt_properties_fetch(mlt_properties self, const char *name)

static void fire_property_changed(mlt_properties self, const char *name)
{
atomic_fetch_add(&((property_list *) self->local)->revision, 1);
mlt_events_fire(self, "property-changed", mlt_event_data_from_string(name));
}

Expand Down Expand Up @@ -1419,6 +1442,8 @@ int mlt_properties_rename(mlt_properties self, const char *source, const char *d
}
}
}
if (found)
atomic_fetch_add(&list->revision, 1);
mlt_properties_unlock(self);
}
return value != NULL;
Expand Down
3 changes: 2 additions & 1 deletion src/framework/mlt_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* \brief Properties class declaration
* \see mlt_properties_s
*
* Copyright (C) 2003-2022 Meltytech, LLC
* Copyright (C) 2003-2026 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -56,6 +56,7 @@ MLT_EXPORT int mlt_properties_preset(mlt_properties self, const char *name);
MLT_EXPORT int mlt_properties_inc_ref(mlt_properties self);
MLT_EXPORT int mlt_properties_dec_ref(mlt_properties self);
MLT_EXPORT int mlt_properties_ref_count(mlt_properties self);
MLT_EXPORT unsigned int mlt_properties_revision(mlt_properties self);
Comment thread
eseifert marked this conversation as resolved.
Resolved
MLT_EXPORT void mlt_properties_mirror(mlt_properties self, mlt_properties that);
MLT_EXPORT int mlt_properties_inherit(mlt_properties self, mlt_properties that);
MLT_EXPORT int mlt_properties_copy(mlt_properties self, mlt_properties that, const char *prefix);
Expand Down
91 changes: 91 additions & 0 deletions src/tests/test_playlist/test_playlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,97 @@ private Q_SLOTS:
QCOMPARE(frame->get_int("consumer.dummy"), 1);
delete frame;
}

void RefreshDetectsCutChange()
{
Producer p(profile, "color", "red");
p.set("length", 100);
p.set_in_and_out(0, 99);
Playlist pl(profile);
for (int i = 0; i < 4; i++)
pl.append(p, 0, 9);
QCOMPARE(pl.get_playtime(), 40);

// A cut does not notify the playlist when its points change.
Producer *cut = pl.get_clip(0);
QVERIFY(cut != nullptr);
cut->set_in_and_out(0, 4);
delete cut;
QCOMPARE(pl.clip_length(0), 10);

// The next edit refreshes the playlist from the producers.
pl.blank(9);
QCOMPARE(pl.clip_length(0), 5);
QCOMPARE(pl.clip_start(1), 5);
QCOMPARE(pl.get_playtime(), 45);

cut = pl.get_clip(2);
QVERIFY(cut != nullptr);
cut->set_in_and_out(2, 4);
delete cut;
QCOMPARE(pl.resize_clip(1, 0, 19), 0);
QCOMPARE(pl.clip_length(1), 20);
QCOMPARE(pl.clip_length(2), 3);
QCOMPARE(pl.clip_start(3), 28);
QCOMPARE(pl.get_playtime(), 48);

// So does a change of the parent producer.
cut = pl.get_clip(3);
QVERIFY(cut != nullptr);
cut->set_in_and_out(0, 0);
delete cut;
p.set("out", 98);
QCOMPARE(pl.clip_length(3), 1);
QCOMPARE(pl.get_playtime(), 39);
}

void BlankStatus()
{
Producer p(profile, "color", "red");
p.set("length", 100);
p.set_in_and_out(0, 99);
Playlist pl(profile);
pl.blank(4);
pl.append(p, 0, 9);
pl.blank(4);
pl.blank(2);
pl.append(p, 0, 9);
pl.blank(6);
QCOMPARE(pl.count(), 6);
QVERIFY(pl.is_blank(0));
QVERIFY(!pl.is_blank(1));
QVERIFY(pl.is_blank(2));
QVERIFY(pl.is_blank(3));
QVERIFY(!pl.is_blank(4));
QVERIFY(pl.is_blank(5));
QVERIFY(pl.is_blank(-1));
QVERIFY(pl.is_blank(6));
QVERIFY(pl.is_blank_at(0));
QVERIFY(!pl.is_blank_at(5));
QVERIFY(pl.is_blank_at(15));
QVERIFY(pl.is_blank_at(1000));

pl.consolidate_blanks(0);
QCOMPARE(pl.count(), 4);
QVERIFY(pl.is_blank(0));
QVERIFY(!pl.is_blank(1));
QVERIFY(pl.is_blank(2));
QCOMPARE(pl.clip_length(2), 8);
QVERIFY(!pl.is_blank(3));
QCOMPARE(pl.get_playtime(), 33);

// A clip replaced by a blank is merged with its neighbours.
Producer *clip = pl.replace_with_blank(3);
QVERIFY(clip != nullptr);
delete clip;
QVERIFY(pl.is_blank(3));
pl.consolidate_blanks(1);
QCOMPARE(pl.count(), 3);
QCOMPARE(pl.clip_length(2), 18);
pl.consolidate_blanks(0);
QCOMPARE(pl.count(), 2);
QCOMPARE(pl.get_playtime(), 15);
}
};

QTEST_APPLESS_MAIN(TestPlaylist)
Expand Down
39 changes: 39 additions & 0 deletions src/tests/test_properties/test_properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,45 @@ private Q_SLOTS:
QCOMPARE(p.get_int("foo"), 123);
QCOMPARE(p.get_double("foo"), 123.4);
}

void RevisionChangesOnModification()
{
Properties p;
mlt_properties props = p.get_properties();
unsigned int revision = mlt_properties_revision(props);
p.set("foo", "bar");
QVERIFY(mlt_properties_revision(props) != revision);
revision = mlt_properties_revision(props);
p.set("foo", 10);
QVERIFY(mlt_properties_revision(props) != revision);
revision = mlt_properties_revision(props);
p.anim_set("anim", 1.0, 5);
QVERIFY(mlt_properties_revision(props) != revision);
revision = mlt_properties_revision(props);
p.set("data", &revision, 0);
QVERIFY(mlt_properties_revision(props) != revision);
revision = mlt_properties_revision(props);
p.clear("foo");
QVERIFY(mlt_properties_revision(props) != revision);
revision = mlt_properties_revision(props);
QCOMPARE(mlt_properties_rename(props, "anim", "renamed"), 0);
QVERIFY(mlt_properties_revision(props) != revision);
}

void RevisionUnchangedOnRead()
{
Properties p;
p.set("foo", "123");
p.anim_set("anim", 1.0, 5);
mlt_properties props = p.get_properties();
const unsigned int revision = mlt_properties_revision(props);
QCOMPARE(p.get_int("foo"), 123);
QCOMPARE(p.get("foo"), "123");
QCOMPARE(p.anim_get_double("anim", 5), 1.0);
QCOMPARE(p.get("missing"), nullptr);
QCOMPARE(mlt_properties_rename(props, "missing", "other"), 0);
QCOMPARE(mlt_properties_revision(props), revision);
}
};

QTEST_APPLESS_MAIN(TestProperties)
Expand Down
Loading