Skip to content

Commit 959947a

Browse files
committed
Adding GetMinTime/GetMinFrame functions to timeline, to find the "start" position of a timeline, if it's not 0.0.
1 parent 118810f commit 959947a

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

src/Timeline.cpp

+45-3
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,19 @@ int64_t Timeline::GetMaxFrame() {
472472
return std::round(max_time * fps);
473473
}
474474

475+
// Compute the start time of the first timeline clip
476+
double Timeline::GetMinTime() {
477+
// Return cached min_time variable (threadsafe)
478+
return min_time;
479+
}
480+
481+
// Compute the first frame# based on the first clip position
482+
int64_t Timeline::GetMinFrame() {
483+
double fps = info.fps.ToDouble();
484+
auto min_time = GetMinTime();
485+
return std::round(min_time * fps);
486+
}
487+
475488
// Apply a FrameMapper to a clip which matches the settings of this timeline
476489
void Timeline::apply_mapper_to_clip(Clip* clip)
477490
{
@@ -755,22 +768,51 @@ void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect)
755768
"open_clips.size()", open_clips.size());
756769
}
757770

758-
// Calculate the max duration (in seconds) of the timeline, based on all the clips, and cache the value
771+
// Calculate the max and min duration (in seconds) of the timeline, based on all the clips, and cache the value
759772
void Timeline::calculate_max_duration() {
760773
double last_clip = 0.0;
761774
double last_effect = 0.0;
775+
double first_clip = std::numeric_limits<double>::max();
776+
double first_effect = std::numeric_limits<double>::max();
762777

778+
// Find the last and first clip
763779
if (!clips.empty()) {
780+
// Find the clip with the maximum end frame
764781
const auto max_clip = std::max_element(
765-
clips.begin(), clips.end(), CompareClipEndFrames());
782+
clips.begin(), clips.end(), CompareClipEndFrames());
766783
last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
784+
785+
// Find the clip with the minimum start position (ignoring layer)
786+
const auto min_clip = std::min_element(
787+
clips.begin(), clips.end(), [](const openshot::Clip* lhs, const openshot::Clip* rhs) {
788+
return lhs->Position() < rhs->Position();
789+
});
790+
first_clip = (*min_clip)->Position();
767791
}
792+
793+
// Find the last and first effect
768794
if (!effects.empty()) {
795+
// Find the effect with the maximum end frame
769796
const auto max_effect = std::max_element(
770-
effects.begin(), effects.end(), CompareEffectEndFrames());
797+
effects.begin(), effects.end(), CompareEffectEndFrames());
771798
last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
799+
800+
// Find the effect with the minimum start position
801+
const auto min_effect = std::min_element(
802+
effects.begin(), effects.end(), [](const openshot::EffectBase* lhs, const openshot::EffectBase* rhs) {
803+
return lhs->Position() < rhs->Position();
804+
});
805+
first_effect = (*min_effect)->Position();
772806
}
807+
808+
// Calculate the max and min time
773809
max_time = std::max(last_clip, last_effect);
810+
min_time = std::min(first_clip, first_effect);
811+
812+
// If no clips or effects exist, set min_time to 0
813+
if (clips.empty() && effects.empty()) {
814+
min_time = 0.0;
815+
}
774816
}
775817

776818
// Sort clips by position on the timeline

src/Timeline.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ namespace openshot {
160160
bool managed_cache; ///< Does this timeline instance manage the cache object
161161
std::string path; ///< Optional path of loaded UTF-8 OpenShot JSON project file
162162
int max_concurrent_frames; ///< Max concurrent frames to process at one time
163-
double max_time; ///> The max duration (in seconds) of the timeline, based on all the clips
163+
double max_time; ///> The max duration (in seconds) of the timeline, based on the furthest clip (right edge)
164+
double min_time; ///> The min duration (in seconds) of the timeline, based on the position of the first clip (left edge)
164165

165166
std::map<std::string, std::shared_ptr<openshot::TrackedObjectBase>> tracked_objects; ///< map of TrackedObjectBBoxes and their IDs
166167

@@ -286,6 +287,11 @@ namespace openshot {
286287
/// Look up the end frame number of the latest element on the timeline
287288
int64_t GetMaxFrame();
288289

290+
/// Look up the position/start time of the first timeline element
291+
double GetMinTime();
292+
/// Look up the start frame number of the first element on the timeline
293+
int64_t GetMinFrame();
294+
289295
/// Close the timeline reader (and any resources it was consuming)
290296
void Close() override;
291297

tests/Timeline.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,67 @@ TEST_CASE( "GetMaxFrame and GetMaxTime", "[libopenshot][timeline]" )
661661
CHECK(t.GetMaxTime() == Approx(20.0).margin(0.001));
662662
}
663663

664+
TEST_CASE( "GetMinFrame and GetMinTime", "[libopenshot][timeline]" )
665+
{
666+
// Create a timeline
667+
Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO);
668+
669+
std::stringstream path1;
670+
path1 << TEST_MEDIA_PATH << "interlaced.png";
671+
Clip clip1(path1.str());
672+
clip1.Id("C1");
673+
clip1.Layer(1);
674+
clip1.Position(50); // Start at 50 seconds
675+
clip1.End(45); // Ends at 95 seconds
676+
t.AddClip(&clip1);
677+
678+
CHECK(t.GetMinTime() == Approx(50.0).margin(0.001));
679+
CHECK(t.GetMinFrame() == 50 * 30);
680+
681+
Clip clip2(path1.str());
682+
clip2.Id("C2");
683+
clip2.Layer(2);
684+
clip2.Position(0); // Start at 0 seconds
685+
clip2.End(55); // Ends at 55 seconds
686+
t.AddClip(&clip2);
687+
688+
CHECK(t.GetMinTime() == Approx(0.0).margin(0.001));
689+
CHECK(t.GetMinFrame() == 0);
690+
691+
clip1.Position(80); // Move clip1 to start at 80 seconds
692+
clip2.Position(100); // Move clip2 to start at 100 seconds
693+
CHECK(t.GetMinTime() == Approx(80.0).margin(0.001));
694+
CHECK(t.GetMinFrame() == 80 * 30);
695+
696+
clip2.Position(20); // Adjust clip2 to start at 20 seconds
697+
CHECK(t.GetMinTime() == Approx(20.0).margin(0.001));
698+
CHECK(t.GetMinFrame() == 20 * 30);
699+
700+
clip2.End(35); // Adjust clip2 to end at 35 seconds
701+
CHECK(t.GetMinTime() == Approx(20.0).margin(0.001));
702+
CHECK(t.GetMinFrame() == 20 * 30);
703+
704+
t.RemoveClip(&clip1);
705+
CHECK(t.GetMinTime() == Approx(20.0).margin(0.001));
706+
CHECK(t.GetMinFrame() == 20 * 30);
707+
708+
// Update Clip's basic properties with JSON Diff
709+
std::stringstream json_change1;
710+
json_change1 << "[{\"type\":\"update\",\"key\":[\"clips\",{\"id\":\"C2\"}],\"value\":{\"id\":\"C2\",\"layer\":4000000,\"position\":5.0,\"start\":0,\"end\":10},\"partial\":false}]";
711+
t.ApplyJsonDiff(json_change1.str());
712+
713+
CHECK(t.GetMinTime() == Approx(5.0).margin(0.001));
714+
CHECK(t.GetMinFrame() == 5 * 30);
715+
716+
// Insert NEW Clip with JSON Diff
717+
std::stringstream json_change2;
718+
json_change2 << "[{\"type\":\"insert\",\"key\":[\"clips\"],\"value\":{\"id\":\"C3\",\"layer\":4000000,\"position\":10.0,\"start\":0,\"end\":10,\"reader\":{\"acodec\":\"\",\"audio_bit_rate\":0,\"audio_stream_index\":-1,\"audio_timebase\":{\"den\":1,\"num\":1},\"channel_layout\":4,\"channels\":0,\"display_ratio\":{\"den\":1,\"num\":1},\"duration\":3600.0,\"file_size\":\"160000\",\"fps\":{\"den\":1,\"num\":30},\"has_audio\":false,\"has_single_image\":true,\"has_video\":true,\"height\":200,\"interlaced_frame\":false,\"metadata\":{},\"path\":\"" << path1.str() << "\",\"pixel_format\":-1,\"pixel_ratio\":{\"den\":1,\"num\":1},\"sample_rate\":0,\"top_field_first\":true,\"type\":\"QtImageReader\",\"vcodec\":\"\",\"video_bit_rate\":0,\"video_length\":\"108000\",\"video_stream_index\":-1,\"video_timebase\":{\"den\":30,\"num\":1},\"width\":200}},\"partial\":false}]";
719+
t.ApplyJsonDiff(json_change2.str());
720+
721+
CHECK(t.GetMinTime() == Approx(5.0).margin(0.001));
722+
CHECK(t.GetMinFrame() == 5 * 30);
723+
}
724+
664725
TEST_CASE( "Multi-threaded Timeline GetFrame", "[libopenshot][timeline]" )
665726
{
666727
Timeline *t = new Timeline(1280, 720, Fraction(24, 1), 48000, 2, LAYOUT_STEREO);

0 commit comments

Comments
 (0)