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
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ For more information, please visit <http://www.openshot.org/>.
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules")

################ PROJECT VERSION ####################
set(PROJECT_VERSION_FULL "0.5.0")
set(PROJECT_SO_VERSION 28)
set(PROJECT_VERSION_FULL "0.6.0")
set(PROJECT_SO_VERSION 29)

# Remove the dash and anything following, to get the #.#.# version for project()
STRING(REGEX REPLACE "\-.*$" "" VERSION_NUM "${PROJECT_VERSION_FULL}")
Expand Down
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ target_include_directories(openshot
# Find JUCE-based openshot Audio libraries
if(NOT TARGET OpenShot::Audio)
# Only load if necessary (not for integrated builds)
find_package(OpenShotAudio 0.4.0 REQUIRED)
find_package(OpenShotAudio 0.6.0 REQUIRED)
endif()
target_link_libraries(openshot PUBLIC OpenShot::Audio)

Expand Down
22 changes: 22 additions & 0 deletions src/FFmpegReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2827,6 +2827,28 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) {
}
}

// If both streams have advanced past this frame but the decoder never
// produced image data for it, reuse the most recent non-future image.
// This avoids stalling indefinitely on sparse/missing decoded frames.
if (!f->has_image_data && is_video_ready && is_audio_ready) {
std::shared_ptr<Frame> previous_frame_instance = final_cache.GetFrame(f->number - 1);
if (previous_frame_instance && previous_frame_instance->has_image_data) {
f->AddImage(std::make_shared<QImage>(previous_frame_instance->GetImage()->copy()));
}
if (!f->has_image_data
&& last_final_video_frame
&& last_final_video_frame->has_image_data
&& last_final_video_frame->number <= f->number) {
f->AddImage(std::make_shared<QImage>(last_final_video_frame->GetImage()->copy()));
}
if (!f->has_image_data
&& last_video_frame
&& last_video_frame->has_image_data
&& last_video_frame->number <= f->number) {
f->AddImage(std::make_shared<QImage>(last_video_frame->GetImage()->copy()));
}
}

// Do not finalize non-EOF video frames without decoded image data.
// This prevents repeated previous-frame fallbacks being cached as real frames.
if (!f->has_image_data) {
Expand Down
50 changes: 50 additions & 0 deletions tests/FFmpegReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

#include "openshot_catch.h"

#define private public
#include "FFmpegReader.h"
#undef private
#include "Exceptions.h"
#include "Frame.h"
#include "Timeline.h"
Expand Down Expand Up @@ -528,6 +530,54 @@ TEST_CASE( "Attached_Picture_Audio_Does_Not_Stall_Early_Frames", "[libopenshot][
std::remove(fixture_path.str().c_str());
}

TEST_CASE( "Missing_Image_Frame_Finalizes_Using_Previous_Image", "[libopenshot][ffmpegreader]" )
{
FFmpegReader r("synthetic-missing-image", DurationStrategy::VideoPreferred, false);

r.info.has_video = true;
r.info.has_audio = true;
r.info.has_single_image = false;
r.info.width = 320;
r.info.height = 240;
r.info.fps = Fraction(30, 1);
r.info.sample_rate = 48000;
r.info.channels = 2;
r.info.channel_layout = LAYOUT_STEREO;
r.info.video_length = 120;
r.info.video_timebase = Fraction(1, 30);
r.info.audio_timebase = Fraction(1, 48000);

r.pts_offset_seconds = 0.0;
r.last_frame = 58;
r.video_pts_seconds = 2.233333;
r.audio_pts_seconds = 3.100000;
r.packet_status.video_eof = false;
r.packet_status.audio_eof = false;
r.packet_status.packets_eof = false;
r.packet_status.end_of_file = false;

const int samples_per_frame = Frame::GetSamplesPerFrame(
58, r.info.fps, r.info.sample_rate, r.info.channels);
auto previous = std::make_shared<Frame>(
58, r.info.width, r.info.height, "#112233", samples_per_frame, r.info.channels);
previous->AddColor(r.info.width, r.info.height, "#112233");
r.final_cache.Add(previous);
r.last_final_video_frame = previous;

auto missing = r.CreateFrame(59);
r.working_cache.Add(missing);
REQUIRE(missing != nullptr);
REQUIRE_FALSE(missing->has_image_data);

r.CheckWorkingFrames(59);

auto finalized = r.final_cache.GetFrame(59);
REQUIRE(finalized != nullptr);
CHECK(finalized->has_image_data);
CHECK(finalized->CheckPixel(0, 0, 17, 34, 51, 255, 0));
CHECK(r.final_cache.GetFrame(58) != nullptr);
}

TEST_CASE( "HardwareDecodeSuccessful_IsFalse_WhenHardwareDecodeIsDisabled", "[libopenshot][ffmpegreader][hardware]" )
{
HardwareDecoderSettingsGuard guard;
Expand Down
Loading