diff --git a/CMakeLists.txt b/CMakeLists.txt
index d191757a..3ca96ed9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,6 +80,7 @@ option(DISABLE_BUNDLED_JSONCPP "Don't fall back to bundled JsonCpp" OFF)
option(ENABLE_IWYU "Enable 'Include What You Use' scanner (CMake 3.3+)" OFF)
option(ENABLE_PARALLEL_CTEST "Run CTest using multiple processors" ON)
+option(VERBOSE_TESTS "Run CTest with maximum verbosity" OFF)
option(ENABLE_COVERAGE "Scan test coverage using gcov and report" OFF)
option(ENABLE_DOCS "Build API documentation (requires Doxygen)" ON)
@@ -187,9 +188,12 @@ if(BUILD_TESTING)
ProcessorCount(CPU_COUNT)
if(CPU_COUNT GREATER 1)
add_feature_info("Parallel tests" TRUE "Unit tests can use ${CPU_COUNT} processors")
- set(CTEST_OPTIONS "-j${CPU_COUNT}")
+ list(APPEND CTEST_OPTIONS "-j${CPU_COUNT}")
endif()
endif()
+ if(VERBOSE_TESTS)
+ list(APPEND CTEST_OPTIONS "-VV")
+ endif()
add_subdirectory(tests)
endif()
add_feature_info("Unit tests" ${BUILD_TESTING} "Compile unit tests for library functions")
diff --git a/src/Clip.cpp b/src/Clip.cpp
index 4c0e9876..451b9073 100644
--- a/src/Clip.cpp
+++ b/src/Clip.cpp
@@ -374,15 +374,36 @@ std::shared_ptr Clip::GetFrame(int64_t frame_number)
// Get the original frame and pass it to GetFrame overload
std::shared_ptr original_frame = GetOrCreateFrame(frame_number);
- return GetFrame(original_frame, frame_number);
+ return GetFrame(original_frame, frame_number, NULL);
}
else
// Throw error if reader not initialized
throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
}
-// Use an existing openshot::Frame object and draw this Clip's frame onto it
+// Create an openshot::Frame object for a specific frame number of this reader.
std::shared_ptr Clip::GetFrame(std::shared_ptr background_frame, int64_t frame_number)
+{
+ // Check for open reader (or throw exception)
+ if (!is_open)
+ throw ReaderClosed("The Clip is closed. Call Open() before calling this method.");
+
+ if (reader)
+ {
+ // Adjust out of bounds frame number
+ frame_number = adjust_frame_number_minimum(frame_number);
+
+ // Get the original frame and pass it to GetFrame overload
+ std::shared_ptr original_frame = GetOrCreateFrame(frame_number);
+ return GetFrame(original_frame, frame_number, NULL);
+ }
+ else
+ // Throw error if reader not initialized
+ throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
+}
+
+// Use an existing openshot::Frame object and draw this Clip's frame onto it
+std::shared_ptr Clip::GetFrame(std::shared_ptr background_frame, int64_t frame_number, openshot::TimelineInfoStruct* options)
{
// Check for open reader (or throw exception)
if (!is_open)
@@ -406,9 +427,18 @@ std::shared_ptr Clip::GetFrame(std::shared_ptr backgroun
// TODO: Handle variable # of samples, since this resamples audio for different speeds (only when time curve is set)
get_time_mapped_frame(original_frame, new_frame_number);
- // Apply effects to the frame (if any)
+ // Apply local effects to the frame (if any)
apply_effects(original_frame);
+ // Apply global timeline effects (i.e. transitions & masks... if any)
+ if (timeline != NULL && options != NULL) {
+ if (options->is_top_clip) {
+ // Apply global timeline effects (only to top clip... if overlapping, pass in timeline frame number)
+ Timeline* timeline_instance = (Timeline*) timeline;
+ original_frame = timeline_instance->apply_effects(original_frame, background_frame->number, Layer());
+ }
+ }
+
// Apply keyframe / transforms
apply_keyframes(original_frame, background_frame->GetImage());
diff --git a/src/Clip.h b/src/Clip.h
index 6ea7fc93..661af53d 100644
--- a/src/Clip.h
+++ b/src/Clip.h
@@ -169,6 +169,7 @@ namespace openshot {
/// Reverse an audio buffer
void reverse_buffer(juce::AudioSampleBuffer* buffer);
+
public:
openshot::GravityType gravity; ///< The gravity of a clip determines where it snaps to its parent
openshot::ScaleType scale; ///< The scale determines how a clip should be resized to fit its parent
@@ -235,25 +236,39 @@ namespace openshot {
/// Look up an effect by ID
openshot::EffectBase* GetEffect(const std::string& id);
- /// @brief Get an openshot::Frame object for a specific frame number of this timeline. The image size and number
+ /// @brief Get an openshot::Frame object for a specific frame number of this clip. The image size and number
/// of samples match the source reader.
///
/// @returns A new openshot::Frame object
- /// @param frame_number The frame number (starting at 1) of the clip or effect on the timeline.
+ /// @param frame_number The frame number (starting at 1) of the clip
std::shared_ptr GetFrame(int64_t frame_number) override;
- /// @brief Get an openshot::Frame object for a specific frame number of this timeline. The image size and number
- /// of samples can be customized to match the Timeline, or any custom output. Extra samples will be moved to the
- /// next Frame. Missing samples will be moved from the next Frame.
+ /// @brief Get an openshot::Frame object for a specific frame number of this clip. The image size and number
+ /// of samples match the background_frame passed in and the timeline (if available).
///
/// A new openshot::Frame objects is returned, based on a copy from the source image, with all keyframes and clip effects
- /// rendered.
+ /// rendered/rasterized.
///
/// @returns The modified openshot::Frame object
/// @param background_frame The frame object to use as a background canvas (i.e. an existing Timeline openshot::Frame instance)
- /// @param frame_number The frame number (starting at 1) of the clip or effect on the timeline.
+ /// @param frame_number The frame number (starting at 1) of the clip. The image size and number
+ /// of samples match the background_frame passed in and the timeline (if available)
std::shared_ptr GetFrame(std::shared_ptr background_frame, int64_t frame_number);
+ /// @brief Get an openshot::Frame object for a specific frame number of this clip. The image size and number
+ /// of samples match the background_frame passed in and the timeline (if available).
+ ///
+ /// A new openshot::Frame objects is returned, based on a copy from the source image, with all keyframes and clip effects
+ /// rendered/rasterized.
+ ///
+ /// @returns The modified openshot::Frame object
+ /// @param background_frame The frame object to use as a background canvas (i.e. an existing Timeline openshot::Frame instance)
+ /// @param frame_number The frame number (starting at 1) of the clip on the timeline. The image size and number
+ /// of samples match the timeline.
+ /// @param options The openshot::TimelineInfoStruct pointer, with more details about this specific timeline clip,
+ /// such as, if it's a top clip. This info is used to apply global transitions and masks, if needed.
+ std::shared_ptr GetFrame(std::shared_ptr background_frame, int64_t frame_number, openshot::TimelineInfoStruct* options);
+
/// Open the internal reader
void Open() override;
diff --git a/src/Settings.cpp b/src/Settings.cpp
index cfbe2e2c..688eaae3 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -28,14 +28,14 @@
* along with OpenShot Library. If not, see .
*/
+#include // For std::getenv
+
#include "Settings.h"
-using namespace std;
using namespace openshot;
-
// Global reference to Settings
-Settings *Settings::m_pInstance = NULL;
+Settings *Settings::m_pInstance = nullptr;
// Create or Get an instance of the settings singleton
Settings *Settings::Instance()
@@ -53,6 +53,9 @@ Settings *Settings::Instance()
m_pInstance->HW_EN_DEVICE_SET = 0;
m_pInstance->PLAYBACK_AUDIO_DEVICE_NAME = "";
m_pInstance->DEBUG_TO_STDERR = false;
+ auto env_debug = std::getenv("LIBOPENSHOT_DEBUG");
+ if (env_debug != nullptr)
+ m_pInstance->DEBUG_TO_STDERR = true;
}
return m_pInstance;
diff --git a/src/Settings.h b/src/Settings.h
index 36ba2917..e21822a0 100644
--- a/src/Settings.h
+++ b/src/Settings.h
@@ -31,19 +31,7 @@
#ifndef OPENSHOT_SETTINGS_H
#define OPENSHOT_SETTINGS_H
-
-#include
-#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-
namespace openshot {
@@ -118,7 +106,7 @@ namespace openshot {
/// The current install path of OpenShot (needs to be set when using Timeline(path), since certain
/// paths depend on the location of OpenShot transitions and files)
std::string PATH_OPENSHOT_INSTALL = "";
-
+
/// Whether to dump ZeroMQ debug messages to stderr
bool DEBUG_TO_STDERR = false;
diff --git a/src/Timeline.cpp b/src/Timeline.cpp
index 51c90840..c2aea38d 100644
--- a/src/Timeline.cpp
+++ b/src/Timeline.cpp
@@ -572,7 +572,7 @@ std::shared_ptr Timeline::apply_effects(std::shared_ptr frame, int
}
// Get or generate a blank frame
-std::shared_ptr Timeline::GetOrCreateFrame(std::shared_ptr background_frame, Clip* clip, int64_t number)
+std::shared_ptr Timeline::GetOrCreateFrame(std::shared_ptr background_frame, Clip* clip, int64_t number, openshot::TimelineInfoStruct* options)
{
std::shared_ptr new_frame;
@@ -584,7 +584,7 @@ std::shared_ptr Timeline::GetOrCreateFrame(std::shared_ptr backgro
ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame);
// Attempt to get a frame (but this could fail if a reader has just been closed)
- new_frame = std::shared_ptr(clip->GetFrame(background_frame, number));
+ new_frame = std::shared_ptr(clip->GetFrame(background_frame, number, options));
// Return real frame
return new_frame;
@@ -603,29 +603,28 @@ std::shared_ptr Timeline::GetOrCreateFrame(std::shared_ptr backgro
}
// Process a new layer of video or audio
-void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, int64_t clip_frame_number, int64_t timeline_frame_number, bool is_top_clip, float max_volume)
+void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, int64_t clip_frame_number, bool is_top_clip, float max_volume)
{
- // Get the clip's frame, composited on top of the current timeline frame
+ // Create timeline options (with details about this current frame request)
+ TimelineInfoStruct* options = new TimelineInfoStruct();
+ options->is_top_clip = is_top_clip;
+
+ // Get the clip's frame, composited on top of the current timeline frame
std::shared_ptr source_frame;
- source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number);
+ source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, options);
+ delete options;
// No frame found... so bail
if (!source_frame)
return;
// Debug output
- ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer", "new_frame->number", new_frame->number, "clip_frame_number", clip_frame_number, "timeline_frame_number", timeline_frame_number);
-
- /* Apply effects to the source frame (if any). If multiple clips are overlapping, only process the
- * effects on the top clip. */
- if (is_top_clip) {
- source_frame = apply_effects(source_frame, timeline_frame_number, source_clip->Layer());
- }
+ ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer", "new_frame->number", new_frame->number, "clip_frame_number", clip_frame_number);
/* COPY AUDIO - with correct volume */
if (source_clip->Reader()->info.has_audio) {
// Debug output
- ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Copy Audio)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number, "timeline_frame_number", timeline_frame_number);
+ ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Copy Audio)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number);
if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0)
for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
@@ -678,7 +677,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in
}
else
// Debug output
- ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (No Audio Copied - Wrong # of Channels)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number, "timeline_frame_number", timeline_frame_number);
+ ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (No Audio Copied - Wrong # of Channels)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number);
}
// Debug output
@@ -893,7 +892,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame)
ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Calculate clip's frame #)", "clip->Position()", clip->Position(), "clip->Start()", clip->Start(), "info.fps.ToFloat()", info.fps.ToFloat(), "clip_frame_number", clip_frame_number);
// Add clip's frame as layer
- add_layer(new_frame, clip, clip_frame_number, requested_frame, is_top_clip, max_volume);
+ add_layer(new_frame, clip, clip_frame_number, is_top_clip, max_volume);
} else {
// Debug output
diff --git a/src/Timeline.h b/src/Timeline.h
index 630f93c8..3a696bfa 100644
--- a/src/Timeline.h
+++ b/src/Timeline.h
@@ -183,7 +183,7 @@ namespace openshot {
std::map> tracked_objects; ///< map of TrackedObjectBBoxes and their IDs
/// Process a new layer of video or audio
- void add_layer(std::shared_ptr new_frame, openshot::Clip* source_clip, int64_t clip_frame_number, int64_t timeline_frame_number, bool is_top_clip, float max_volume);
+ void add_layer(std::shared_ptr new_frame, openshot::Clip* source_clip, int64_t clip_frame_number, bool is_top_clip, float max_volume);
/// Apply a FrameMapper to a clip which matches the settings of this timeline
void apply_mapper_to_clip(openshot::Clip* clip);
@@ -206,10 +206,7 @@ namespace openshot {
std::vector find_intersecting_clips(int64_t requested_frame, int number_of_frames, bool include);
/// Get a clip's frame or generate a blank frame
- std::shared_ptr GetOrCreateFrame(std::shared_ptr background_frame, openshot::Clip* clip, int64_t number);
-
- /// Apply effects to the source frame (if any)
- std::shared_ptr apply_effects(std::shared_ptr frame, int64_t timeline_frame_number, int layer);
+ std::shared_ptr GetOrCreateFrame(std::shared_ptr background_frame, openshot::Clip* clip, int64_t number, openshot::TimelineInfoStruct* options);
/// Compare 2 floating point numbers for equality
bool isEqual(double a, double b);
@@ -268,6 +265,9 @@ namespace openshot {
/// @param effect Add an effect to the timeline. An effect can modify the audio or video of an openshot::Frame.
void AddEffect(openshot::EffectBase* effect);
+ /// Apply global/timeline effects to the source frame (if any)
+ std::shared_ptr apply_effects(std::shared_ptr frame, int64_t timeline_frame_number, int layer);
+
/// Apply the timeline's framerate and samplerate to all clips
void ApplyMapperToClips();
diff --git a/src/TimelineBase.h b/src/TimelineBase.h
index 39dd3a41..0fc70c74 100644
--- a/src/TimelineBase.h
+++ b/src/TimelineBase.h
@@ -31,7 +31,23 @@
#ifndef OPENSHOT_TIMELINE_BASE_H
#define OPENSHOT_TIMELINE_BASE_H
+#include
+
+
namespace openshot {
+ /**
+ * @brief This struct contains info about the current Timeline clip instance
+ *
+ * When the Timeline requests an openshot::Frame instance from a Clip, it passes
+ * this struct along, with some additional details from the Timeline, such as if this clip is
+ * above or below overlapping clips, etc... This info can help determine if a Clip should apply
+ * global effects from the Timeline, such as a global Transition/Mask effect.
+ */
+ struct TimelineInfoStruct
+ {
+ bool is_top_clip; ///< Is clip on top (if overlapping another clip)
+ };
+
/**
* @brief This class represents a timeline (used for building generic timeline implementations)
*/
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 6c5c6d51..f519856b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -24,6 +24,11 @@
# along with OpenShot Library. If not, see .
################################################################################
+# Allow spaces in test names
+if(POLICY CMP0110)
+ cmake_policy(SET CMP0110 NEW)
+endif()
+
# Test media path, used by unit tests for input data
file(TO_NATIVE_PATH "${PROJECT_SOURCE_DIR}/examples/" TEST_MEDIA_PATH)
@@ -103,6 +108,18 @@ foreach(tname ${OPENSHOT_TESTS})
list(APPEND CATCH2_TEST_TARGETS openshot-${tname}-test)
list(APPEND CATCH2_TEST_NAMES ${tname})
endforeach()
+# Add an additional special-case test, for an envvar-dependent setting
+add_test(NAME [=["Settings:Debug logging (enabled)"]=]
+ COMMAND
+ openshot-Settings-test "[environment]"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties([=["Settings:Debug logging (enabled)"]=]
+ PROPERTIES
+ LABELS Settings
+ ENVIRONMENT "LIBOPENSHOT_DEBUG=1"
+)
+
# Export target list for coverage use
set(UNIT_TEST_TARGETS ${CATCH2_TEST_TARGETS} PARENT_SCOPE)
set(UNIT_TEST_NAMES ${CATCH2_TEST_NAMES} PARENT_SCOPE)
diff --git a/tests/Settings.cpp b/tests/Settings.cpp
index e974ffd4..13239819 100644
--- a/tests/Settings.cpp
+++ b/tests/Settings.cpp
@@ -34,7 +34,7 @@
using namespace openshot;
-TEST_CASE( "Default_Constructor", "[libopenshot][settings]" )
+TEST_CASE( "Constructor", "[libopenshot][settings]" )
{
// Create an empty color
Settings *s = Settings::Instance();
@@ -43,7 +43,7 @@ TEST_CASE( "Default_Constructor", "[libopenshot][settings]" )
CHECK_FALSE(s->HIGH_QUALITY_SCALING);
}
-TEST_CASE( "Change_Settings", "[libopenshot][settings]" )
+TEST_CASE( "Change settings", "[libopenshot][settings]" )
{
// Create an empty color
Settings *s = Settings::Instance();
@@ -56,3 +56,12 @@ TEST_CASE( "Change_Settings", "[libopenshot][settings]" )
CHECK(Settings::Instance()->OMP_THREADS == 8);
CHECK(Settings::Instance()->HIGH_QUALITY_SCALING == true);
}
+
+TEST_CASE( "Debug logging", "[libopenshot][settings][environment]")
+{
+ // Check the environment
+ auto envvar = std::getenv("LIBOPENSHOT_DEBUG");
+ const auto is_enabled = bool(envvar != nullptr);
+
+ CHECK(Settings::Instance()->DEBUG_TO_STDERR == is_enabled);
+}