diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7535c2ea5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI Build +on: [push, pull_request] +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04, ubuntu-20.04] + compiler: [gcc, clang] + env: + CC: ${{ matrix.compiler }} + steps: + - uses: actions/checkout@v2 + + - uses: haya14busa/action-cond@v1 + id: coverage + with: + cond: ${{ matrix.compiler == 'clang' }} + if_true: "-DENABLE_COVERAGE:BOOL=1" + if_false: "-DENABLE_COVERAGE:BOOL=0" + + - name: Install dependencies + shell: bash + run: | + sudo add-apt-repository ppa:openshot.developers/libopenshot-daily + sudo apt update + sudo apt install cmake swig doxygen graphviz curl lcov + sudo apt install libopenshot-audio-dev + sudo apt install qtbase5-dev qtbase5-dev-tools + sudo apt install libfdk-aac-dev libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libavfilter-dev libswscale-dev libpostproc-dev libswresample-dev + sudo apt install libzmq3-dev libmagick++-dev libunittest++-dev + + - name: Build libopenshot + shell: bash + run: | + mkdir build + pushd build + cmake -B . -S .. -DCMAKE_INSTALL_PREFIX:PATH="dist" -DCMAKE_BUILD_TYPE="Debug" "${{ steps.coverage.outputs.value }}" + cmake --build . -- VERBOSE=1 + popd + + - name: Test libopenshot + shell: bash + run: | + pushd build + cmake --build . --target os_test -- VERBOSE=1 + popd + + - name: Install libopenshot + shell: bash + run: | + pushd build + cmake --build . --target install -- VERBOSE=1 + popd + + - uses: codecov/codecov-action@v1 + if: ${{ matrix.compiler == 'clang' }} + with: + file: build/coverage.info + diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml new file mode 100644 index 000000000..e5703a5fe --- /dev/null +++ b/.github/workflows/label-merge-conflicts.yml @@ -0,0 +1,26 @@ + +name: Label merge conflicts + +# Controls when the action will run. Triggers the workflow on push to repo branches +# (It shouldn't run on pull requests, as it won't have the right credentials to +# edit labels on other PRs.) +on: push + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: eps1lon/actions-label-merge-conflict@v2.0.1 + with: + # Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }} + repoToken: ${{ secrets.GITHUB_TOKEN }} + # Name of the label which indicates that the branch is dirty + dirtyLabel: 'conflicts' + # Number of seconds after which the action runs again if the mergable state is unknown. + retryAfter: 60 + # Number of times the action retries calculating the mergable state + retryMax: 5 + # String. Comment to add when the pull request is conflicting. Supports markdown. + commentOnDirty: 'Merge conflicts have been detected on this PR, please resolve.' + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8ee17e4f4..3ce3829ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,7 +46,7 @@ mac-builder: - unzip artifacts.zip - export LIBOPENSHOT_AUDIO_DIR=$CI_PROJECT_DIR/build/install-x64 - mkdir -p build; cd build; - - cmake -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++" -DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++" -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_PREFIX_PATH=/usr/local/qt5.15.X/qt5.15/5.15.0/clang_64/ -DPYTHON_INCLUDE_DIR=/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m -DPYTHON_LIBRARY=/Library/Frameworks/Python.framework/Versions/3.6/lib/libpython3.6.dylib -DPYTHON_MODULE_PATH=python -DPython_FRAMEWORKS=/Library/Frameworks/Python.framework/ -D"CMAKE_BUILD_TYPE:STRING=Release" -D"CMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk" -D"CMAKE_OSX_DEPLOYMENT_TARGET=10.9" -D"CMAKE_INSTALL_RPATH_USE_LINK_PATH=1" -D"ENABLE_RUBY=0" ../ + - cmake -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++" -DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++" -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -D"CMAKE_BUILD_TYPE:STRING=Release" -D"CMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk" -D"CMAKE_OSX_DEPLOYMENT_TARGET=10.9" -DCMAKE_PREFIX_PATH=/usr/local/qt5.15.X/qt5.15/5.15.0/clang_64/ -D"CMAKE_INSTALL_RPATH_USE_LINK_PATH=1" -D"ENABLE_RUBY=0" ../ - make - make install - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ab338bdde..000000000 --- a/.travis.yml +++ /dev/null @@ -1,122 +0,0 @@ -language: cpp -compiler: gcc -os: linux -dist: xenial - -# This section uses a rather esoteric (and tricky!) feature of YAML, -# &aliases and *anchors, to build package lists out of sublists without -# repeating their contents. Basically, '&name' creates an alias for the -# given data, which can then be referenced using the anchor '*name'. -addons: - apt: - packages: &p_common # Packages common to all Ubuntu builds - - cmake - - libopenshot-audio-dev - - libmagick++-dev - - libunittest++-dev - - libzmq3-dev - - qtbase5-dev - - qtmultimedia5-dev - - libfdk-aac-dev - - libavcodec-dev - - libavformat-dev - - libavdevice-dev - - libavutil-dev - - libavfilter-dev - - libswscale-dev - - libpostproc-dev - - libswresample-dev - - swig - - doxygen - - graphviz - - curl - -jobs: - include: - - name: "Coverage + FFmpeg 3.4 GCC (Ubuntu 18.04 Bionic)" - env: - - BUILD_VERSION=coverage_ffmpeg34 - - CMAKE_EXTRA_ARGS="-DENABLE_COVERAGE=1" - - TEST_TARGET=coverage - os: linux - dist: bionic - addons: - apt: - sources: - - sourceline: 'ppa:openshot.developers/libopenshot-daily' - packages: - - *p_common - - qt5-default - - libavresample-dev - - libjsoncpp-dev - - lcov - - binutils-common # For c++filt - - - name: "FFmpeg 4 GCC (Ubuntu 20.04 Focal)" - env: - - BUILD_VERSION=ffmpeg4 - - CMAKE_EXTRA_ARGS="" - - TEST_TARGET=test - os: linux - dist: focal - addons: - apt: - sources: - - sourceline: 'ppa:openshot.developers/libopenshot-daily' - packages: - - *p_common - - qt5-default - - libjsoncpp-dev - - libavcodec58 - - libavformat58 - - libavdevice58 - - libavutil56 - - libavfilter7 - - libswscale5 - - libpostproc55 - - libswresample3 - - - name: "FFmpeg 3.4 Clang (Ubuntu 18.04 Bionic)" - env: - - BUILD_VERSION=clang_ffmpeg34 - - CMAKE_EXTRA_ARGS="" - - TEST_TARGET=test - os: linux - dist: bionic - compiler: clang - addons: - apt: - sources: - - sourceline: 'ppa:openshot.developers/libopenshot-daily' - packages: - - *p_common - - qt5-default - - libavresample-dev - - libomp-dev - - - name: "FFmpeg 2 GCC (Ubuntu 16.04 Xenial)" - env: - - BUILD_VERSION=ffmpeg2 - - CMAKE_EXTRA_ARGS="" - - TEST_TARGET="os_test" - os: linux - dist: xenial - addons: - apt: - sources: - - sourceline: 'ppa:openshot.developers/libopenshot-daily' - - sourceline: 'ppa:beineri/opt-qt-5.10.0-xenial' - packages: - - *p_common - - libavresample-dev - -script: - - mkdir -p build; cd build; - - cmake -DCMAKE_INSTALL_PREFIX:PATH="$TRAVIS_OS_NAME-x64" -DCMAKE_BUILD_TYPE:STRING="Debug" ${CMAKE_EXTRA_ARGS} ../ - - make VERBOSE=1 - - make ${TEST_TARGET} - - make install - - cd .. - -after_success: - - if [ "x$TEST_TARGET" = "xcoverage" ]; then bash <(curl -s https://codecov.io/bash) -f build/coverage.info || echo "Codecov did not collect coverage reports"; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index ec608257d..294639bf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,14 +180,40 @@ if (ENABLE_COVERAGE) NAME coverage LCOV_ARGS "--no-external" EXECUTABLE openshot-test - DEPENDENCIES openshot-test - EXCLUDE "bindings" "examples" "${CMAKE_CURRENT_BINARY_DIR}/bindings" + DEPENDENCIES openshot openshot-test + EXCLUDE + "bindings/*" + "examples/*" + "${CMAKE_CURRENT_BINARY_DIR}/bindings/*" + "${CMAKE_CURRENT_BINARY_DIR}/src/*_autogen/*" ) - message("Generate coverage report with 'make coverage'") + if(NOT TARGET os_test) + add_custom_target(os_test) + add_dependencies(os_test coverage) + endif() +endif() + +# Also hook up 'test' as an alias for the 'os_test' target, if possible +# This requires CMake 3.11+, where the CMP0037 policy +# configured to 'NEW' mode will not reserve target names +# unless the corresponding feature is actually used +if (POLICY CMP0037) + cmake_policy(SET CMP0037 NEW) +endif() +if(TARGET os_test) + if (CMAKE_VERSION VERSION_GREATER 3.11) + message(STATUS "Cmake 3.11+ detected, enabling 'test' target") + add_custom_target(test) + add_dependencies(test os_test) + set(TEST_TARGET_NAME "test") + else() + set(TEST_TARGET_NAME "os_test") + endif() + add_feature_info("Testrunner" ENABLE_TESTS "Run unit tests with 'make ${TEST_TARGET_NAME}'") endif() ########### PRINT FEATURE SUMMARY ############## feature_summary(WHAT ALL - INCLUDE_QUIET_PACKAGES - FATAL_ON_MISSING_REQUIRED_PACKAGES - DESCRIPTION "Displaying feature summary\n\nBuild configuration:") + INCLUDE_QUIET_PACKAGES + FATAL_ON_MISSING_REQUIRED_PACKAGES + DESCRIPTION "Displaying feature summary\n\nBuild configuration:") diff --git a/bindings/python/openshot.i b/bindings/python/openshot.i index c204b4070..99e9cab4d 100644 --- a/bindings/python/openshot.i +++ b/bindings/python/openshot.i @@ -232,6 +232,7 @@ %include "effects/Bars.h" %include "effects/Blur.h" %include "effects/Brightness.h" +%include "effects/Caption.h" %include "effects/ChromaKey.h" %include "effects/ColorShift.h" %include "effects/Crop.h" diff --git a/bindings/ruby/openshot.i b/bindings/ruby/openshot.i index e7a6fa7b7..fe47f1bce 100644 --- a/bindings/ruby/openshot.i +++ b/bindings/ruby/openshot.i @@ -206,6 +206,7 @@ %include "effects/Bars.h" %include "effects/Blur.h" %include "effects/Brightness.h" +%include "effects/Caption.h" %include "effects/ChromaKey.h" %include "effects/ColorShift.h" %include "effects/Crop.h" diff --git a/codecov.yml b/codecov.yml index e00103e78..e5c073f63 100644 --- a/codecov.yml +++ b/codecov.yml @@ -7,11 +7,10 @@ coverage: base: pr # Only post a status to pull requests informational: true # Don't block PRs based on coverage stats (yet?) ignore: - - "/examples" - - "/bindings" - - "/thirdparty/jsoncpp" - - "/doc" - - "/cmake" - - "/*.md" + - "examples" - "bindings" + - "thirdparty/jsoncpp" + - "doc" + - "cmake" + - "*.md" - "src/openshot_autogen" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cfb73feca..faa23d251 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -174,7 +174,10 @@ target_include_directories(openshot ################# LIBOPENSHOT-AUDIO ################### # Find JUCE-based openshot Audio libraries -find_package(OpenShotAudio 0.2.0 REQUIRED) +if(NOT TARGET OpenShot::Audio) + # Only load if necessary (not for integrated builds) + find_package(OpenShotAudio 0.2.0 REQUIRED) +endif() target_link_libraries(openshot PUBLIC OpenShot::Audio) ### diff --git a/src/Clip.h b/src/Clip.h index 0b6d7ee8e..e2521532a 100644 --- a/src/Clip.h +++ b/src/Clip.h @@ -204,7 +204,7 @@ namespace openshot { void AddEffect(openshot::EffectBase* effect); /// Close the internal reader - void Close(); + void Close() override; /// Return the list of effects on the timeline std::list Effects() { return effects; }; @@ -217,7 +217,7 @@ namespace openshot { /// /// @returns A new openshot::Frame object /// @param frame_number The frame number (starting at 1) of the clip or effect on the timeline. - std::shared_ptr GetFrame(int64_t frame_number); + 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 diff --git a/src/ImageReader.h b/src/ImageReader.h index aa96272fd..2fcb963aa 100644 --- a/src/ImageReader.h +++ b/src/ImageReader.h @@ -48,6 +48,8 @@ namespace openshot { + // Forward decls + class CacheBase; /** * @brief This class uses the ImageMagick++ libraries, to open image files, and return @@ -90,7 +92,7 @@ namespace openshot void Close() override; /// Get the cache object used by this reader (always returns NULL for this object) - CacheMemory* GetCache() override { return NULL; }; + CacheBase* GetCache() override { return NULL; }; /// Get an openshot::Frame object for a specific frame number of this reader. All numbers /// return the same Frame, since they all share the same image data. diff --git a/src/QtHtmlReader.h b/src/QtHtmlReader.h index 3d426afb0..cd5a2750a 100644 --- a/src/QtHtmlReader.h +++ b/src/QtHtmlReader.h @@ -49,6 +49,8 @@ class QImage; namespace openshot { + // Forward decls + class CacheBase; /** * @brief This class uses Qt libraries, to create frames with rendered HTML, and return @@ -115,7 +117,7 @@ namespace openshot void Close() override; /// Get the cache object used by this reader (always returns NULL for this object) - openshot::CacheMemory* GetCache() override { return NULL; }; + CacheBase* GetCache() override { return NULL; }; /// Get an openshot::Frame object for a specific frame number of this reader. All numbers /// return the same Frame, since they all share the same image data. diff --git a/src/QtImageReader.h b/src/QtImageReader.h index 5162b8f45..b61ca3af3 100644 --- a/src/QtImageReader.h +++ b/src/QtImageReader.h @@ -42,6 +42,8 @@ namespace openshot { + // Forward decl + class CacheBase; /** * @brief This class uses the Qt library, to open image files, and return @@ -88,7 +90,7 @@ namespace openshot void Close() override; /// Get the cache object used by this reader (always returns NULL for this object) - CacheMemory* GetCache() override { return NULL; }; + CacheBase* GetCache() override { return NULL; }; /// Get an openshot::Frame object for a specific frame number of this reader. All numbers /// return the same Frame, since they all share the same image data. diff --git a/src/QtTextReader.h b/src/QtTextReader.h index 2fd203ad4..d66681f31 100644 --- a/src/QtTextReader.h +++ b/src/QtTextReader.h @@ -49,6 +49,8 @@ class QImage; namespace openshot { + // Forward decls + class CacheBase; /** * @brief This class uses Qt libraries, to create frames with "Text", and return @@ -126,7 +128,7 @@ namespace openshot void Close() override; /// Get the cache object used by this reader (always returns NULL for this object) - openshot::CacheMemory* GetCache() override { return NULL; }; + CacheBase* GetCache() override { return NULL; }; /// Get an openshot::Frame object for a specific frame number of this reader. All numbers /// return the same Frame, since they all share the same image data. diff --git a/src/Timeline.cpp b/src/Timeline.cpp index abc02b6f8..d7377a280 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -289,7 +289,7 @@ void Timeline::RemoveClip(Clip* clip) } // Look up a clip -openshot::ClipBase* Timeline::GetClip(const std::string& id) +openshot::Clip* Timeline::GetClip(const std::string& id) { // Find the matching clip (if any) for (const auto& clip : clips) { diff --git a/src/Timeline.h b/src/Timeline.h index 1d156153b..e12cf3ee5 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -265,7 +265,7 @@ namespace openshot { std::list Clips() { return clips; }; /// Look up a single clip by ID - openshot::ClipBase* GetClip(const std::string& id); + openshot::Clip* GetClip(const std::string& id); /// Look up a clip effect by ID openshot::EffectBase* GetClipEffect(const std::string& id); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ad1d3b9b5..f480b6d30 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -125,21 +125,9 @@ target_link_libraries(openshot-test ) ##### RUNNING TESTS (make os_test / make test) ##### -# Hook up the 'make os_test' target to the 'openshot-test' executable -add_custom_target(os_test COMMAND openshot-test) - -# Also hook up 'make test', if possible -# This requires CMake 3.11+, where the CMP0037 policy -# configured to 'NEW' mode will not reserve target names -# unless the corresponding feature is actually used -if (POLICY CMP0037) - cmake_policy(SET CMP0037 NEW) +# Hook up the 'make os_test' target to the 'openshot-test' executable, +# if we aren't defining it as the coverage target +if(NOT ENABLE_COVERAGE) + add_custom_target(os_test COMMAND openshot-test) endif() -if (CMAKE_VERSION VERSION_GREATER 3.11) - message(STATUS "Cmake 3.11+ detected, enabling 'test' target") - add_custom_target(test COMMAND openshot-test) - set(TEST_TARGET_NAME "test") -else() - set(TEST_TARGET_NAME "os_test") -endif() -add_feature_info("Testrunner" ENABLE_TESTS "Run unit tests with 'make ${TEST_TARGET_NAME}'") + diff --git a/tests/Color_Tests.cpp b/tests/Color_Tests.cpp index 81d6070eb..388ac5efa 100644 --- a/tests/Color_Tests.cpp +++ b/tests/Color_Tests.cpp @@ -33,23 +33,37 @@ #define DONT_SET_USING_JUCE_NAMESPACE 1 #include "OpenShot.h" -using namespace std; -using namespace openshot; +SUITE(Color) { -TEST(Color_Default_Constructor) +TEST(Default_Constructor) { // Create an empty color - Color c1; + openshot::Color c1; CHECK_CLOSE(0.0f, c1.red.GetValue(0), 0.00001); CHECK_CLOSE(0.0f, c1.green.GetValue(0), 0.00001); CHECK_CLOSE(0.0f, c1.blue.GetValue(0), 0.00001); } -TEST(Color_Animate_Colors) +TEST(Keyframe_constructor) +{ + std::vector kfs{0, 0, 0, 0}; + int64_t i(0); + for (auto& kf : kfs) { + kf.AddPoint(100, ++i * 20); + } + auto c = openshot::Color(kfs[0], kfs[1], kfs[2], kfs[3]); + + CHECK_CLOSE(20, c.red.GetLong(100), 0.01); + CHECK_CLOSE(40, c.green.GetLong(100), 0.01); + CHECK_CLOSE(60, c.blue.GetLong(100), 0.01); + CHECK_CLOSE(80, c.alpha.GetLong(100), 0.01); +} + +TEST(Animate_Colors) { // Create an empty color - Color c1; + openshot::Color c1; // Set starting color (on frame 0) c1.red.AddPoint(1, 0); @@ -67,7 +81,7 @@ TEST(Color_Animate_Colors) CHECK_CLOSE(160, c1.blue.GetLong(500), 0.01); } -TEST(Color_HEX_Value) +TEST(HEX_Value) { // Color openshot::Color c; @@ -84,7 +98,7 @@ TEST(Color_HEX_Value) } -TEST(Color_HEX_Constructor) +TEST(HEX_Constructor) { // Color openshot::Color c("#4586db"); @@ -97,7 +111,7 @@ TEST(Color_HEX_Constructor) CHECK_EQUAL("#ffffff", c.GetColorHex(100)); } -TEST(Color_Distance) +TEST(Distance) { // Color openshot::Color c1("#040a0c"); @@ -105,11 +119,11 @@ TEST(Color_Distance) openshot::Color c3("#000000"); openshot::Color c4("#ffffff"); - CHECK_CLOSE(19.0f, Color::GetDistance(c1.red.GetInt(1), c1.blue.GetInt(1), c1.green.GetInt(1), c2.red.GetInt(1), c2.blue.GetInt(1), c2.green.GetInt(1)), 0.001); - CHECK_CLOSE(764.0f, Color::GetDistance(c3.red.GetInt(1), c3.blue.GetInt(1), c3.green.GetInt(1), c4.red.GetInt(1), c4.blue.GetInt(1), c4.green.GetInt(1)), 0.001); + CHECK_CLOSE(19.0f, openshot::Color::GetDistance(c1.red.GetInt(1), c1.blue.GetInt(1), c1.green.GetInt(1), c2.red.GetInt(1), c2.blue.GetInt(1), c2.green.GetInt(1)), 0.001); + CHECK_CLOSE(764.0f, openshot::Color::GetDistance(c3.red.GetInt(1), c3.blue.GetInt(1), c3.green.GetInt(1), c4.red.GetInt(1), c4.blue.GetInt(1), c4.green.GetInt(1)), 0.001); } -TEST(Color_RGBA_Constructor) +TEST(RGBA_Constructor) { // Color openshot::Color c(69, 134, 219, 255); @@ -126,3 +140,41 @@ TEST(Color_RGBA_Constructor) CHECK_EQUAL("#4586db", c1.GetColorHex(1)); CHECK_EQUAL(128, c1.alpha.GetInt(1)); } + +TEST(Json) +{ + openshot::Color c(128, 128, 128, 0); + openshot::Color c1; + c1.red.AddPoint(1, 128); + c1.green.AddPoint(1, 128); + c1.blue.AddPoint(1, 128); + c1.alpha.AddPoint(1, 0); + // Check that JSON produced is identical + auto j = c.Json(); + auto j1 = c1.Json(); + CHECK_EQUAL(j, j1); + // Check Json::Value representation + auto jv = c.JsonValue(); + auto jv_string = jv.toStyledString(); + CHECK_EQUAL(jv_string, j1); +} + +TEST(SetJson) { + const std::string json_input = R"json( + { + "red": { "Points": [ { "co": { "X": 1.0, "Y": 0.0 }, "interpolation": 0 } ] }, + "green": { "Points": [ { "co": { "X": 1.0, "Y": 128.0 }, "interpolation": 0 } ] }, + "blue": { "Points": [ { "co": { "X": 1.0, "Y": 64.0 }, "interpolation": 0 } ] }, + "alpha": { "Points": [ { "co": { "X": 1.0, "Y": 192.0 }, "interpolation": 0 } ] } + } + )json"; + openshot::Color c; + CHECK_THROW(c.SetJson("}{"), openshot::InvalidJSON); + c.SetJson(json_input); + CHECK_CLOSE(0, c.red.GetLong(10), 0.01); + CHECK_CLOSE(128, c.green.GetLong(10), 0.01); + CHECK_CLOSE(64, c.blue.GetLong(10), 0.01); + CHECK_CLOSE(192, c.alpha.GetLong(10), 0.01); +} + +} // SUITE diff --git a/tests/FrameMapper_Tests.cpp b/tests/FrameMapper_Tests.cpp index 45e07e3a1..d586c30c2 100644 --- a/tests/FrameMapper_Tests.cpp +++ b/tests/FrameMapper_Tests.cpp @@ -31,33 +31,45 @@ #include "UnitTest++.h" // Prevent name clashes with juce::UnitTest #define DONT_SET_USING_JUCE_NAMESPACE 1 -#include "OpenShot.h" +#include "CacheMemory.h" +#include "Clip.h" +#include "DummyReader.h" +#include "FFmpegReader.h" +#include "Fraction.h" +#include "Frame.h" +#include "FrameMapper.h" +#include "Timeline.h" using namespace std; using namespace openshot; -TEST(FrameMapper_Get_Valid_Frame) +SUITE(FrameMapper) { + +TEST(NoOp_GetMappedFrame) { // Create a reader DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); - // Create mapping between 24 fps and 29.97 fps using classic pulldown - FrameMapper mapping(&r, Fraction(30000, 1001), PULLDOWN_CLASSIC, 22000, 2, LAYOUT_STEREO); + // Create mapping between 24 fps and 24 fps without pulldown + FrameMapper mapping(&r, Fraction(24, 1), PULLDOWN_NONE, 22000, 2, LAYOUT_STEREO); + CHECK_EQUAL("FrameMapper", mapping.Name()); - try - { - // Should find this frame - MappedFrame f = mapping.GetMappedFrame(125); - CHECK(true); // success - } - catch (OutOfBoundsFrame &e) - { - // Unexpected failure to find frame - CHECK(false); - } + // Should find this frame + MappedFrame f = mapping.GetMappedFrame(100); + CHECK_EQUAL(100, f.Odd.Frame); + CHECK_EQUAL(100, f.Even.Frame); + + // Should return end frame + f = mapping.GetMappedFrame(150); + CHECK_EQUAL(120, f.Odd.Frame); + CHECK_EQUAL(120, f.Even.Frame); + + mapping.Close(); + mapping.Reader(nullptr); + CHECK_THROW(mapping.Reader(), ReaderClosed); } -TEST(FrameMapper_Invalid_Frame_Too_Small) +TEST(Invalid_Frame_Too_Small) { // Create a reader DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); @@ -70,7 +82,7 @@ TEST(FrameMapper_Invalid_Frame_Too_Small) } -TEST(FrameMapper_24_fps_to_30_fps_Pulldown_Classic) +TEST(24_fps_to_30_fps_Pulldown_Classic) { // Create a reader DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); @@ -87,7 +99,7 @@ TEST(FrameMapper_24_fps_to_30_fps_Pulldown_Classic) CHECK_EQUAL(3, frame3.Even.Frame); } -TEST(FrameMapper_24_fps_to_30_fps_Pulldown_Advanced) +TEST(24_fps_to_30_fps_Pulldown_Advanced) { // Create a reader DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); @@ -107,7 +119,7 @@ TEST(FrameMapper_24_fps_to_30_fps_Pulldown_Advanced) CHECK_EQUAL(3, frame4.Even.Frame); } -TEST(FrameMapper_24_fps_to_30_fps_Pulldown_None) +TEST(24_fps_to_30_fps_Pulldown_None) { // Create a reader DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); @@ -124,7 +136,7 @@ TEST(FrameMapper_24_fps_to_30_fps_Pulldown_None) CHECK_EQUAL(4, frame5.Even.Frame); } -TEST(FrameMapper_30_fps_to_24_fps_Pulldown_Classic) +TEST(30_fps_to_24_fps_Pulldown_Classic) { // Create a reader DummyReader r(Fraction(30, 1), 720, 480, 22000, 2, 5.0); @@ -144,7 +156,7 @@ TEST(FrameMapper_30_fps_to_24_fps_Pulldown_Classic) CHECK_EQUAL(6, frame5.Even.Frame); } -TEST(FrameMapper_30_fps_to_24_fps_Pulldown_Advanced) +TEST(30_fps_to_24_fps_Pulldown_Advanced) { // Create a reader DummyReader r(Fraction(30, 1), 720, 480, 22000, 2, 5.0); @@ -164,7 +176,7 @@ TEST(FrameMapper_30_fps_to_24_fps_Pulldown_Advanced) CHECK_EQUAL(5, frame4.Even.Frame); } -TEST(FrameMapper_30_fps_to_24_fps_Pulldown_None) +TEST(30_fps_to_24_fps_Pulldown_None) { // Create a reader DummyReader r(Fraction(30, 1), 720, 480, 22000, 2, 5.0); @@ -181,7 +193,7 @@ TEST(FrameMapper_30_fps_to_24_fps_Pulldown_None) CHECK_EQUAL(6, frame5.Even.Frame); } -TEST(FrameMapper_resample_audio_48000_to_41000) +TEST(resample_audio_48000_to_41000) { // Create a reader: 24 fps, 2 channels, 48000 sample rate stringstream path; @@ -211,7 +223,7 @@ TEST(FrameMapper_resample_audio_48000_to_41000) map.Close(); } -TEST (FrameMapper_resample_audio_mapper) { +TEST(resample_audio_mapper) { // This test verifies that audio data can be resampled on FrameMapper // instances, even on frame rates that do not divide evenly, and that no audio data is misplaced // or duplicated. We verify this by creating a SIN wave, add those data points to a DummyReader, @@ -350,7 +362,7 @@ TEST (FrameMapper_resample_audio_mapper) { r.Close(); } -TEST (FrameMapper_redistribute_samples_per_frame) { +TEST(redistribute_samples_per_frame) { // This test verifies that audio data is correctly aligned on // FrameMapper instances. We do this by creating 2 Clips based on the same parent reader // (i.e. same exact audio sample data). We use a Timeline to overlap these clips @@ -471,4 +483,19 @@ TEST (FrameMapper_redistribute_samples_per_frame) { // Clean up cache.Clear(); r.Close(); -} \ No newline at end of file +} + +TEST(Json) +{ + DummyReader r(Fraction(30,1), 1280, 720, 48000, 2, 5.0); + FrameMapper map(&r, Fraction(30, 1), PULLDOWN_NONE, 48000, 2, LAYOUT_STEREO); + + // Read JSON config & write it back again + const std::string map_config = map.Json(); + map.SetJson(map_config); + + CHECK_EQUAL(48000, map.info.sample_rate); + CHECK_EQUAL(30, map.info.fps.num); +} + +} // SUITE diff --git a/tests/ImageWriter_Tests.cpp b/tests/ImageWriter_Tests.cpp index d74907bf3..e4e500ddf 100644 --- a/tests/ImageWriter_Tests.cpp +++ b/tests/ImageWriter_Tests.cpp @@ -85,7 +85,7 @@ TEST(Gif) // Basic Reader state queries CHECK_EQUAL("ImageReader", r1.Name()); - CacheMemory* c = r1.GetCache(); + CacheBase* c = r1.GetCache(); CHECK_EQUAL(true, c == nullptr); CHECK_EQUAL(false, r1.IsOpen()); diff --git a/tests/Point_Tests.cpp b/tests/Point_Tests.cpp index 3e63778d8..cce783377 100644 --- a/tests/Point_Tests.cpp +++ b/tests/Point_Tests.cpp @@ -33,86 +33,141 @@ #define DONT_SET_USING_JUCE_NAMESPACE 1 #include "OpenShot.h" -using namespace std; -using namespace openshot; +SUITE(POINT) { -TEST(Point_Default_Constructor) +TEST(Default_Constructor) +{ + openshot::Point p; + + // Default values + CHECK_EQUAL(1, p.co.X); + CHECK_EQUAL(0, p.co.Y); + CHECK_EQUAL(0.5, p.handle_left.X); + CHECK_EQUAL(1.0, p.handle_left.Y); + CHECK_EQUAL(0.5, p.handle_right.X); + CHECK_EQUAL(0.0, p.handle_right.Y); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p.interpolation); + CHECK_EQUAL(openshot::HandleType::AUTO, p.handle_type); +} +TEST(XY_Constructor) { // Create a point with X and Y values openshot::Point p1(2,9); CHECK_EQUAL(2, p1.co.X); CHECK_EQUAL(9, p1.co.Y); - CHECK_EQUAL(BEZIER, p1.interpolation); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p1.interpolation); } -TEST(Point_Constructor_With_Coordinate) +TEST(Constructor_With_Coordinate) { // Create a point with a coordinate - Coordinate c1(3,7); + openshot::Coordinate c1(3,7); openshot::Point p1(c1); CHECK_EQUAL(3, p1.co.X); CHECK_EQUAL(7, p1.co.Y); - CHECK_EQUAL(BEZIER, p1.interpolation); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p1.interpolation); } -TEST(Point_Constructor_With_Coordinate_And_LINEAR_Interpolation) +TEST(Constructor_With_Coordinate_And_LINEAR_Interpolation) { // Create a point with a coordinate and interpolation - Coordinate c1(3,9); - InterpolationType interp = LINEAR; + openshot::Coordinate c1(3,9); + auto interp = openshot::InterpolationType::LINEAR; openshot::Point p1(c1, interp); CHECK_EQUAL(3, c1.X); CHECK_EQUAL(9, c1.Y); - CHECK_EQUAL(LINEAR, p1.interpolation); + CHECK_EQUAL(openshot::InterpolationType::LINEAR, p1.interpolation); } -TEST(Point_Constructor_With_Coordinate_And_BEZIER_Interpolation) +TEST(Constructor_With_Coordinate_And_BEZIER_Interpolation) { // Create a point with a coordinate and interpolation - Coordinate c1(3,9); - InterpolationType interp = BEZIER; + openshot::Coordinate c1(3,9); + auto interp = openshot::InterpolationType::BEZIER; openshot::Point p1(c1, interp); CHECK_EQUAL(3, p1.co.X); CHECK_EQUAL(9, p1.co.Y); - CHECK_EQUAL(BEZIER, p1.interpolation); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p1.interpolation); } -TEST(Point_Constructor_With_Coordinate_And_CONSTANT_Interpolation) +TEST(Constructor_With_Coordinate_And_CONSTANT_Interpolation) { // Create a point with a coordinate and interpolation - Coordinate c1(2,8); - InterpolationType interp = CONSTANT; + openshot::Coordinate c1(2,8); + auto interp = openshot::InterpolationType::CONSTANT; openshot::Point p1(c1, interp); CHECK_EQUAL(2, p1.co.X); CHECK_EQUAL(8, p1.co.Y); - CHECK_EQUAL(CONSTANT, p1.interpolation); + CHECK_EQUAL(openshot::InterpolationType::CONSTANT, p1.interpolation); } -TEST(Point_Constructor_With_Coordinate_And_BEZIER_And_AUTO_Handle) +TEST(Constructor_With_Coordinate_And_BEZIER_And_AUTO_Handle) { // Create a point with a coordinate and interpolation - Coordinate c1(3,9); - openshot::Point p1(c1, BEZIER, AUTO); + openshot::Coordinate c1(3,9); + openshot::Point p1(c1, + openshot::InterpolationType::BEZIER, + openshot::HandleType::AUTO); CHECK_EQUAL(3, p1.co.X); CHECK_EQUAL(9, p1.co.Y); - CHECK_EQUAL(BEZIER, p1.interpolation); - CHECK_EQUAL(AUTO, p1.handle_type); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p1.interpolation); + CHECK_EQUAL(openshot::HandleType::AUTO, p1.handle_type); } -TEST(Point_Constructor_With_Coordinate_And_BEZIER_And_MANUAL_Handle) +TEST(Constructor_With_Coordinate_And_BEZIER_And_MANUAL_Handle) { // Create a point with a coordinate and interpolation - Coordinate c1(3,9); - openshot::Point p1(c1, BEZIER, MANUAL); + openshot::Coordinate c1(3,9); + openshot::Point p1(c1, + openshot::InterpolationType::BEZIER, + openshot::HandleType::MANUAL); CHECK_EQUAL(3, p1.co.X); CHECK_EQUAL(9, p1.co.Y); - CHECK_EQUAL(BEZIER, p1.interpolation); - CHECK_EQUAL(MANUAL, p1.handle_type); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p1.interpolation); + CHECK_EQUAL(openshot::HandleType::MANUAL, p1.handle_type); +} + +TEST(Json) +{ + openshot::Point p1; + openshot::Point p2(1, 0); + auto json1 = p1.Json(); + auto json2 = p2.JsonValue(); + auto json_string2 = json2.toStyledString(); + CHECK_EQUAL(json1, json_string2); } + +TEST(SetJson) +{ + openshot::Point p1; + std::stringstream json_stream; + json_stream << R"json( + { + "co": { "X": 1.0, "Y": 0.0 }, + "handle_left": { "X": 2.0, "Y": 3.0 }, + "handle_right": { "X": 4.0, "Y": -2.0 }, + "handle_type": )json"; + json_stream << static_cast(openshot::HandleType::MANUAL) << ","; + json_stream << R"json( + "interpolation": )json"; + json_stream << static_cast(openshot::InterpolationType::CONSTANT); + json_stream << R"json( + } + )json"; + p1.SetJson(json_stream.str()); + CHECK_EQUAL(2.0, p1.handle_left.X); + CHECK_EQUAL(3.0, p1.handle_left.Y); + CHECK_EQUAL(4.0, p1.handle_right.X); + CHECK_EQUAL(-2.0, p1.handle_right.Y); + CHECK_EQUAL(openshot::HandleType::MANUAL, p1.handle_type); + CHECK_EQUAL(openshot::InterpolationType::CONSTANT, p1.interpolation); +} + +} // SUITE diff --git a/tests/Timeline_Tests.cpp b/tests/Timeline_Tests.cpp index 536b21334..0956366f3 100644 --- a/tests/Timeline_Tests.cpp +++ b/tests/Timeline_Tests.cpp @@ -437,20 +437,26 @@ TEST(GetClip_by_id) std::string clip2_id("CLIP00002"); clip2.Id(clip2_id); clip2.Layer(2); + clip2.Waveform(true); t.AddClip(&clip1); t.AddClip(&clip2); - auto matched = t.GetClip(clip1_id); + // We explicitly want to get returned a Clip*, here + Clip* matched = t.GetClip(clip1_id); CHECK_EQUAL(clip1_id, matched->Id()); CHECK_EQUAL(1, matched->Layer()); - auto matched2 = t.GetClip(clip2_id); + Clip* matched2 = t.GetClip(clip2_id); CHECK_EQUAL(clip2_id, matched2->Id()); CHECK_EQUAL(false, matched2->Layer() < 2); - auto matched3 = t.GetClip("BAD_ID"); + Clip* matched3 = t.GetClip("BAD_ID"); CHECK_EQUAL(true, matched3 == nullptr); + + // Ensure we can access the Clip API interfaces after lookup + CHECK_EQUAL(false, matched->Waveform()); + CHECK_EQUAL(true, matched2->Waveform()); } TEST(GetClipEffect_by_id)