From 8bc4db00d31a240914f6695e0df4915a74ef31ab Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 7 Apr 2021 22:49:12 -0400 Subject: [PATCH] Duplicate *_Tests.cpp history in cppunittest/ history. --- tests/cppunittest/CVObjectDetection_Tests.cpp | 140 ++++ tests/cppunittest/CVStabilizer_Tests.cpp | 142 ++++ tests/cppunittest/CVTracker_Tests.cpp | 151 +++++ tests/cppunittest/Cache_Tests.cpp | 470 +++++++++++++ tests/cppunittest/Clip_Tests.cpp | 264 ++++++++ tests/cppunittest/Color_Tests.cpp | 186 ++++++ tests/cppunittest/Coordinate_Tests.cpp | 99 +++ tests/cppunittest/DummyReader_Tests.cpp | 192 ++++++ tests/cppunittest/FFmpegReader_Tests.cpp | 281 ++++++++ tests/cppunittest/FFmpegWriter_Tests.cpp | 135 ++++ tests/cppunittest/Fraction_Tests.cpp | 156 +++++ tests/cppunittest/FrameMapper_Tests.cpp | 501 ++++++++++++++ tests/cppunittest/Frame_Tests.cpp | 183 ++++++ tests/cppunittest/ImageWriter_Tests.cpp | 122 ++++ tests/cppunittest/KeyFrame_Tests.cpp | 513 +++++++++++++++ tests/cppunittest/Point_Tests.cpp | 186 ++++++ tests/cppunittest/QtImageReader_Tests.cpp | 107 +++ tests/cppunittest/ReaderBase_Tests.cpp | 93 +++ tests/cppunittest/Settings_Tests.cpp | 60 ++ tests/cppunittest/Timeline_Tests.cpp | 620 ++++++++++++++++++ tests/{ => cppunittest}/tests.cpp | 0 21 files changed, 4601 insertions(+) create mode 100644 tests/cppunittest/CVObjectDetection_Tests.cpp create mode 100644 tests/cppunittest/CVStabilizer_Tests.cpp create mode 100644 tests/cppunittest/CVTracker_Tests.cpp create mode 100644 tests/cppunittest/Cache_Tests.cpp create mode 100644 tests/cppunittest/Clip_Tests.cpp create mode 100644 tests/cppunittest/Color_Tests.cpp create mode 100644 tests/cppunittest/Coordinate_Tests.cpp create mode 100644 tests/cppunittest/DummyReader_Tests.cpp create mode 100644 tests/cppunittest/FFmpegReader_Tests.cpp create mode 100644 tests/cppunittest/FFmpegWriter_Tests.cpp create mode 100644 tests/cppunittest/Fraction_Tests.cpp create mode 100644 tests/cppunittest/FrameMapper_Tests.cpp create mode 100644 tests/cppunittest/Frame_Tests.cpp create mode 100644 tests/cppunittest/ImageWriter_Tests.cpp create mode 100644 tests/cppunittest/KeyFrame_Tests.cpp create mode 100644 tests/cppunittest/Point_Tests.cpp create mode 100644 tests/cppunittest/QtImageReader_Tests.cpp create mode 100644 tests/cppunittest/ReaderBase_Tests.cpp create mode 100644 tests/cppunittest/Settings_Tests.cpp create mode 100644 tests/cppunittest/Timeline_Tests.cpp rename tests/{ => cppunittest}/tests.cpp (100%) diff --git a/tests/cppunittest/CVObjectDetection_Tests.cpp b/tests/cppunittest/CVObjectDetection_Tests.cpp new file mode 100644 index 000000000..e6c997a00 --- /dev/null +++ b/tests/cppunittest/CVObjectDetection_Tests.cpp @@ -0,0 +1,140 @@ +/** + * @file + * @brief Unit tests for openshot::Frame + * @author Jonathan Thomas + * @author FeRD (Frank Dana) + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Clip.h" +#include "CVObjectDetection.h" +#include "ProcessingController.h" +#include "Json.h" + +using namespace openshot; + +std::string effectInfo =(" {\"protobuf_data_path\": \"objdetector.data\", " + " \"processing_device\": \"GPU\", " + " \"model_configuration\": \"~/yolo/yolov3.cfg\", " + " \"model_weights\": \"~/yolo/yolov3.weights\", " + " \"classes_file\": \"~/yolo/obj.names\"} "); + +SUITE(CVObjectDetection_Tests) +{ + + // Just for the stabilizer constructor, it won't be used + ProcessingController processingController; + + TEST(DetectObject_Video) + { + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "run.mp4"; + + // Open clip + openshot::Clip c1(path.str()); + c1.Open(); + + //TODO remove hardcoded path + CVObjectDetection objectDetector(effectInfo, processingController); + + objectDetector.detectObjectsClip(c1, 0, 20, true); + + CVDetectionData dd = objectDetector.GetDetectionData(20); + + float x1 = dd.boxes.at(20).x; + float y1 = dd.boxes.at(20).y; + float x2 = x1 + dd.boxes.at(20).width; + float y2 = y1 + dd.boxes.at(20).height; + float confidence = dd.confidences.at(20); + int classId = dd.classIds.at(20); + + CHECK_EQUAL((int) (x1 * 720), 106); + CHECK_EQUAL((int) (y1 * 400), 21); + CHECK_EQUAL((int) (x2 * 720), 628); + CHECK_EQUAL((int) (y2 * 400), 429); + CHECK_EQUAL((int) (confidence * 1000), 554); + CHECK_EQUAL(classId, 0); + + } + + + TEST(SaveLoad_Protobuf) + { + + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "run.mp4"; + + // Open clip + openshot::Clip c1(path.str()); + c1.Open(); + + //TODO remove hardcoded path + CVObjectDetection objectDetector_1(effectInfo ,processingController); + + objectDetector_1.detectObjectsClip(c1, 0, 20, true); + + CVDetectionData dd_1 = objectDetector_1.GetDetectionData(20); + + float x1_1 = dd_1.boxes.at(20).x; + float y1_1 = dd_1.boxes.at(20).y; + float x2_1 = x1_1 + dd_1.boxes.at(20).width; + float y2_1 = y1_1 + dd_1.boxes.at(20).height; + float confidence_1 = dd_1.confidences.at(20); + int classId_1 = dd_1.classIds.at(20); + + objectDetector_1.SaveObjDetectedData(); + + CVObjectDetection objectDetector_2(effectInfo, processingController); + + objectDetector_2._LoadObjDetectdData(); + + CVDetectionData dd_2 = objectDetector_2.GetDetectionData(20); + + float x1_2 = dd_2.boxes.at(20).x; + float y1_2 = dd_2.boxes.at(20).y; + float x2_2 = x1_2 + dd_2.boxes.at(20).width; + float y2_2 = y1_2 + dd_2.boxes.at(20).height; + float confidence_2 = dd_2.confidences.at(20); + int classId_2 = dd_2.classIds.at(20); + + CHECK_EQUAL((int) (x1_1 * 720), (int) (x1_2 * 720)); + CHECK_EQUAL((int) (y1_1 * 400), (int) (y1_2 * 400)); + CHECK_EQUAL((int) (x2_1 * 720), (int) (x2_2 * 720)); + CHECK_EQUAL((int) (y2_1 * 400), (int) (y2_2 * 400)); + CHECK_EQUAL((int) (confidence_1 * 1000), (int) (confidence_2 * 1000)); + CHECK_EQUAL(classId_1, classId_2); + + } + +} // SUITE(Frame_Tests) diff --git a/tests/cppunittest/CVStabilizer_Tests.cpp b/tests/cppunittest/CVStabilizer_Tests.cpp new file mode 100644 index 000000000..4d1f9305c --- /dev/null +++ b/tests/cppunittest/CVStabilizer_Tests.cpp @@ -0,0 +1,142 @@ +/** + * @file + * @brief Unit tests for openshot::Frame + * @author Jonathan Thomas + * @author FeRD (Frank Dana) + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Clip.h" +#include "CVStabilization.h" // for TransformParam, CamTrajectory, CVStabilization +#include "ProcessingController.h" + +using namespace openshot; + +SUITE(CVStabilizer_Tests) +{ + + // Just for the stabilizer constructor, it won't be used + ProcessingController processingController; + + TEST(Stabilize_Video) + { + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "test.avi"; + + // Open clip + openshot::Clip c1(path.str()); + c1.Open(); + + std::string json_data = R"proto( + { + "protobuf_data_path": "stabilizer.data", + "smoothing-window": 30 + } )proto"; + + // Create stabilizer + CVStabilization stabilizer(json_data, processingController); + + // Stabilize clip for frames 0-21 + stabilizer.stabilizeClip(c1, 0, 21, true); + + // Get stabilized data + TransformParam tp = stabilizer.GetTransformParamData(20); + CamTrajectory ct = stabilizer.GetCamTrajectoryTrackedData(20); + + // // Compare if stabilized data is equal to pre-tested ones + int dx = tp.dx*1000; + int dy = tp.dy*1000; + int da = tp.da*1000; + int x = ct.x*1000; + int y = ct.y*1000; + int a = ct.a*1000; + + CHECK_EQUAL((int) (58), dx); + CHECK_EQUAL((int) (-88), dy); + CHECK_EQUAL((int) (7), da); + CHECK_EQUAL((int) (0), x); + CHECK_EQUAL((int) (-1), y); + CHECK_EQUAL((int) (0), a); + } + + + TEST(SaveLoad_Protobuf) + { + + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "test.avi"; + + // Open clip + openshot::Clip c1(path.str()); + c1.Open(); + + std::string json_data = R"proto( + { + "protobuf_data_path": "stabilizer.data", + "smoothing-window": 30 + } )proto"; + + // Create first stabilizer + CVStabilization stabilizer_1(json_data, processingController); + + // Stabilize clip for frames 0-20 + stabilizer_1.stabilizeClip(c1, 0, 20+1, true); + + // Get stabilized data + TransformParam tp_1 = stabilizer_1.GetTransformParamData(20); + CamTrajectory ct_1 = stabilizer_1.GetCamTrajectoryTrackedData(20); + + // Save stabilized data + stabilizer_1.SaveStabilizedData(); + + // Create second stabilizer + CVStabilization stabilizer_2(json_data, processingController); + + // Load stabilized data from first stabilizer protobuf data + stabilizer_2._LoadStabilizedData(); + + // Get stabilized data + TransformParam tp_2 = stabilizer_2.GetTransformParamData(20); + CamTrajectory ct_2 = stabilizer_2.GetCamTrajectoryTrackedData(20); + + // Compare first stabilizer data with second stabilizer data + CHECK_EQUAL((int) (tp_1.dx * 10000), (int) (tp_2.dx *10000)); + CHECK_EQUAL((int) (tp_1.dy * 10000), (int) (tp_2.dy * 10000)); + CHECK_EQUAL((int) (tp_1.da * 10000), (int) (tp_2.da * 10000)); + CHECK_EQUAL((int) (ct_1.x * 10000), (int) (ct_2.x * 10000)); + CHECK_EQUAL((int) (ct_1.y * 10000), (int) (ct_2.y * 10000)); + CHECK_EQUAL((int) (ct_1.a * 10000), (int) (ct_2.a * 10000)); + } + +} // SUITE(Frame_Tests) diff --git a/tests/cppunittest/CVTracker_Tests.cpp b/tests/cppunittest/CVTracker_Tests.cpp new file mode 100644 index 000000000..3229d1ca9 --- /dev/null +++ b/tests/cppunittest/CVTracker_Tests.cpp @@ -0,0 +1,151 @@ +/** + * @file + * @brief Unit tests for openshot::Frame + * @author Jonathan Thomas + * @author FeRD (Frank Dana) + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Clip.h" +#include "CVTracker.h" // for FrameData, CVTracker +#include "ProcessingController.h" + +using namespace openshot; + +SUITE(CVTracker_Tests) +{ + + // Just for the tracker constructor, it won't be used + ProcessingController processingController; + + TEST(Track_Video) + { + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "test.avi"; + + // Open clip + openshot::Clip c1(path.str()); + c1.Open(); + + std::string json_data = R"proto( + { + "protobuf_data_path": "kcf_tracker.data", + "tracker-type": "KCF", + "region": {"x": 294, "y": 102, "width": 180, "height": 166, "first-frame": 0} + } )proto"; + + // Create tracker + CVTracker kcfTracker(json_data, processingController); + + // Track clip for frames 0-20 + kcfTracker.trackClip(c1, 0, 20, true); + // Get tracked data + FrameData fd = kcfTracker.GetTrackedData(20); + float x = fd.x1; + float y = fd.y1; + float width = fd.x2 - x; + float height = fd.y2 - y; + + // Compare if tracked data is equal to pre-tested ones + CHECK_EQUAL(259, (int)(x * 640)); + CHECK_EQUAL(131, (int)(y * 360)); + CHECK_EQUAL(180, (int)(width * 640)); + CHECK_EQUAL(166, (int)(height * 360)); + } + + + TEST(SaveLoad_Protobuf) + { + + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "test.avi"; + + // Open clip + openshot::Clip c1(path.str()); + c1.Open(); + + std::string json_data = R"proto( + { + "protobuf_data_path": "kcf_tracker.data", + "tracker-type": "KCF", + "region": {"x": 294, "y": 102, "width": 180, "height": 166, "first-frame": 0} + } )proto"; + + + // Create first tracker + CVTracker kcfTracker_1(json_data, processingController); + + // Track clip for frames 0-20 + kcfTracker_1.trackClip(c1, 0, 20, true); + + // Get tracked data + FrameData fd_1 = kcfTracker_1.GetTrackedData(20); + + float x_1 = fd_1.x1; + float y_1 = fd_1.y1; + float width_1 = fd_1.x2 - x_1; + float height_1 = fd_1.y2 - y_1; + + // Save tracked data + kcfTracker_1.SaveTrackedData(); + + std::string proto_data_1 = R"proto( + { + "protobuf_data_path": "kcf_tracker.data", + "tracker_type": "", + "region": {"x": -1, "y": -1, "width": -1, "height": -1, "first-frame": 0} + } )proto"; + + // Create second tracker + CVTracker kcfTracker_2(proto_data_1, processingController); + + // Load tracked data from first tracker protobuf data + kcfTracker_2._LoadTrackedData(); + + // Get tracked data + FrameData fd_2 = kcfTracker_2.GetTrackedData(20); + + float x_2 = fd_2.x1; + float y_2 = fd_2.y1; + float width_2 = fd_2.x2 - x_2; + float height_2 = fd_2.y2 - y_2; + + // Compare first tracker data with second tracker data + CHECK_EQUAL((int)(x_1 * 640), (int)(x_2 * 640)); + CHECK_EQUAL((int)(y_1 * 360), (int)(y_2 * 360)); + CHECK_EQUAL((int)(width_1 * 640), (int)(width_2 * 640)); + CHECK_EQUAL((int)(height_1 * 360), (int)(height_2 * 360)); + } + +} // SUITE(Frame_Tests) diff --git a/tests/cppunittest/Cache_Tests.cpp b/tests/cppunittest/Cache_Tests.cpp new file mode 100644 index 000000000..1220cf14b --- /dev/null +++ b/tests/cppunittest/Cache_Tests.cpp @@ -0,0 +1,470 @@ +/** + * @file + * @brief Unit tests for openshot::Cache + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "CacheDisk.h" +#include "CacheMemory.h" +#include "Json.h" + +#include + +using namespace openshot; + +TEST(Cache_Default_Constructor) +{ + // Create cache object + CacheMemory c; + + // Loop 50 times + for (int i = 0; i < 50; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + c.Add(f); + } + + CHECK_EQUAL(50, c.Count()); // Cache should have all frames, with no limit + CHECK_EQUAL(0, c.GetMaxBytes()); // Max frames should default to 0 +} + +TEST(Cache_Max_Bytes_Constructor) +{ + // Create cache object (with a max of 5 previous items) + CacheMemory c(250 * 1024); + + // Loop 20 times + for (int i = 30; i > 0; i--) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame(i, 320, 240, "#000000")); + f->AddColor(320, 240, "#000000"); + c.Add(f); + } + + // Cache should have all 20 + CHECK_EQUAL(20, c.Count()); + + // Add 10 frames again + for (int i = 10; i > 0; i--) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame(i, 320, 240, "#000000")); + f->AddColor(320, 240, "#000000"); + c.Add(f); + } + + // Count should be 20, since we're more frames than can be cached. + CHECK_EQUAL(20, c.Count()); + + // Check which items the cache kept + CHECK_EQUAL(true, c.GetFrame(1) != NULL); + CHECK_EQUAL(true, c.GetFrame(10) != NULL); + CHECK_EQUAL(true, c.GetFrame(11) != NULL); + CHECK_EQUAL(true, c.GetFrame(19) != NULL); + CHECK_EQUAL(true, c.GetFrame(20) != NULL); + CHECK_EQUAL(false, c.GetFrame(21) != NULL); + CHECK_EQUAL(false, c.GetFrame(30) != NULL); +} + +TEST(Cache_Clear) +{ + // Create cache object + CacheMemory c(250 * 1024); + + // Loop 10 times + for (int i = 0; i < 10; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + c.Add(f); + } + + // Cache should only have 10 items + CHECK_EQUAL(10, c.Count()); + + // Clear Cache + c.Clear(); + + // Cache should now have 0 items + CHECK_EQUAL(0, c.Count()); +} + +TEST(Cache_Add_Duplicate_Frames) +{ + // Create cache object + CacheMemory c(250 * 1024); + + // Loop 10 times + for (int i = 0; i < 10; i++) + { + // Add blank frame to the cache (each frame is #1) + std::shared_ptr f(new Frame()); + c.Add(f); + } + + // Cache should only have 1 items (since all frames were frame #1) + CHECK_EQUAL(1, c.Count()); +} + +TEST(Cache_Check_If_Frame_Exists) +{ + // Create cache object + CacheMemory c(250 * 1024); + + // Loop 5 times + for (int i = 1; i < 6; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + c.Add(f); + } + + // Check if certain frames exists (only 1-5 exist) + CHECK_EQUAL(false, c.GetFrame(0) != NULL); + CHECK_EQUAL(true, c.GetFrame(1) != NULL); + CHECK_EQUAL(true, c.GetFrame(2) != NULL); + CHECK_EQUAL(true, c.GetFrame(3) != NULL); + CHECK_EQUAL(true, c.GetFrame(4) != NULL); + CHECK_EQUAL(true, c.GetFrame(5) != NULL); + CHECK_EQUAL(false, c.GetFrame(6) != NULL); +} + +TEST(Cache_GetFrame) +{ + // Create cache object + CacheMemory c(250 * 1024); + + // Create 3 frames + Frame *red = new Frame(1, 300, 300, "red"); + Frame *blue = new Frame(2, 400, 400, "blue"); + Frame *green = new Frame(3, 500, 500, "green"); + + // Add frames to cache + c.Add(std::shared_ptr(red)); + c.Add(std::shared_ptr(blue)); + c.Add(std::shared_ptr(green)); + + // Get frames + CHECK_EQUAL(true, c.GetFrame(0) == NULL); + CHECK_EQUAL(true, c.GetFrame(4) == NULL); + + // Check if certain frames exists (only 1-5 exist) + CHECK_EQUAL(1, c.GetFrame(1)->number); + CHECK_EQUAL(2, c.GetFrame(2)->number); + CHECK_EQUAL(3, c.GetFrame(3)->number); +} + +TEST(Cache_GetSmallest) +{ + // Create cache object (with a max of 10 items) + CacheMemory c(250 * 1024); + + // Create 3 frames + Frame *red = new Frame(1, 300, 300, "red"); + Frame *blue = new Frame(2, 400, 400, "blue"); + Frame *green = new Frame(3, 500, 500, "green"); + + // Add frames to cache + c.Add(std::shared_ptr(red)); + c.Add(std::shared_ptr(blue)); + c.Add(std::shared_ptr(green)); + + // Check if frame 1 is the front + CHECK_EQUAL(1, c.GetSmallestFrame()->number); + + // Check if frame 1 is STILL the front + CHECK_EQUAL(1, c.GetSmallestFrame()->number); + + // Erase frame 1 + c.Remove(1); + + // Check if frame 2 is the front + CHECK_EQUAL(2, c.GetSmallestFrame()->number); +} + +TEST(Cache_Remove) +{ + // Create cache object (with a max of 10 items) + CacheMemory c(250 * 1024); + + // Create 3 frames + Frame *red = new Frame(1, 300, 300, "red"); + Frame *blue = new Frame(2, 400, 400, "blue"); + Frame *green = new Frame(3, 500, 500, "green"); + + // Add frames to cache + c.Add(std::shared_ptr(red)); + c.Add(std::shared_ptr(blue)); + c.Add(std::shared_ptr(green)); + + // Check if count is 3 + CHECK_EQUAL(3, c.Count()); + + // Check if frame 2 exists + CHECK_EQUAL(true, c.GetFrame(2) != NULL); + + // Remove frame 2 + c.Remove(2); + + // Check if frame 2 exists + CHECK_EQUAL(false, c.GetFrame(2) != NULL); + + // Check if count is 2 + CHECK_EQUAL(2, c.Count()); + + // Remove frame 1 + c.Remove(1); + + // Check if frame 1 exists + CHECK_EQUAL(false, c.GetFrame(1) != NULL); + + // Check if count is 1 + CHECK_EQUAL(1, c.Count()); +} + +TEST(Cache_Set_Max_Bytes) +{ + // Create cache object + CacheMemory c; + + // Loop 20 times + for (int i = 0; i < 20; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + c.Add(f); + } + + CHECK_EQUAL(0, c.GetMaxBytes()); // Cache defaults max frames to -1, unlimited frames + + // Set max frames + c.SetMaxBytes(8 * 1024); + CHECK_EQUAL(8 * 1024, c.GetMaxBytes()); + + // Set max frames + c.SetMaxBytes(4 * 1024); + CHECK_EQUAL(4 * 1024, c.GetMaxBytes()); +} + +TEST(Cache_Multiple_Remove) +{ + // Create cache object (using platform /temp/ directory) + CacheMemory c; + + // Add frames to disk cache + for (int i = 1; i <= 20; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + // Add some picture data + f->AddColor(1280, 720, "Blue"); + f->ResizeAudio(2, 500, 44100, LAYOUT_STEREO); + f->AddAudioSilence(500); + c.Add(f); + } + + // Should have 20 frames + CHECK_EQUAL(20, c.Count()); + + // Remove all 20 frames + c.Remove(1, 20); + + // Should have 20 frames + CHECK_EQUAL(0, c.Count()); +} + +TEST(CacheDisk_Set_Max_Bytes) +{ + // Create cache object (using platform /temp/ directory) + CacheDisk c("", "PPM", 1.0, 0.25); + + // Add frames to disk cache + for (int i = 0; i < 20; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + // Add some picture data + f->AddColor(1280, 720, "Blue"); + f->ResizeAudio(2, 500, 44100, LAYOUT_STEREO); + f->AddAudioSilence(500); + c.Add(f); + } + + CHECK_EQUAL(0, c.GetMaxBytes()); // Cache defaults max frames to -1, unlimited frames + + // Set max frames + c.SetMaxBytes(8 * 1024); + CHECK_EQUAL(8 * 1024, c.GetMaxBytes()); + + // Set max frames + c.SetMaxBytes(4 * 1024); + CHECK_EQUAL(4 * 1024, c.GetMaxBytes()); + + // Read frames from disk cache + std::shared_ptr f = c.GetFrame(5); + CHECK_EQUAL(320, f->GetWidth()); + CHECK_EQUAL(180, f->GetHeight()); + CHECK_EQUAL(2, f->GetAudioChannelsCount()); + CHECK_EQUAL(500, f->GetAudioSamplesCount()); + CHECK_EQUAL(LAYOUT_STEREO, f->ChannelsLayout()); + CHECK_EQUAL(44100, f->SampleRate()); + + // Check count of cache + CHECK_EQUAL(20, c.Count()); + + // Clear cache + c.Clear(); + + // Check count of cache + CHECK_EQUAL(0, c.Count()); + + // Delete cache directory + QDir path = QDir::tempPath() + QString("/preview-cache/"); + path.removeRecursively(); +} + +TEST(CacheDisk_Multiple_Remove) +{ + // Create cache object (using platform /temp/ directory) + CacheDisk c("", "PPM", 1.0, 0.25); + + // Add frames to disk cache + for (int i = 1; i <= 20; i++) + { + // Add blank frame to the cache + std::shared_ptr f(new Frame()); + f->number = i; + // Add some picture data + f->AddColor(1280, 720, "Blue"); + f->ResizeAudio(2, 500, 44100, LAYOUT_STEREO); + f->AddAudioSilence(500); + c.Add(f); + } + + // Should have 20 frames + CHECK_EQUAL(20, c.Count()); + + // Remove all 20 frames + c.Remove(1, 20); + + // Should have 20 frames + CHECK_EQUAL(0, c.Count()); + + // Delete cache directory + QDir path = QDir::tempPath() + QString("/preview-cache/"); + path.removeRecursively(); +} + +TEST(CacheDisk_JSON) +{ + // Create cache object (using platform /temp/ directory) + CacheDisk c("", "PPM", 1.0, 0.25); + + // Add some frames (out of order) + std::shared_ptr f3(new Frame(3, 1280, 720, "Blue", 500, 2)); + c.Add(f3); + CHECK_EQUAL(1, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("1", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f1(new Frame(1, 1280, 720, "Blue", 500, 2)); + c.Add(f1); + CHECK_EQUAL(2, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("2", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f2(new Frame(2, 1280, 720, "Blue", 500, 2)); + c.Add(f2); + CHECK_EQUAL(1, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("3", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f5(new Frame(5, 1280, 720, "Blue", 500, 2)); + c.Add(f5); + CHECK_EQUAL(2, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("4", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f4(new Frame(4, 1280, 720, "Blue", 500, 2)); + c.Add(f4); + CHECK_EQUAL(1, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("5", c.JsonValue()["version"].asString()); + + // Delete cache directory + QDir path = QDir::tempPath() + QString("/preview-cache/"); + path.removeRecursively(); +} + +TEST(CacheMemory_JSON) +{ + // Create memory cache object + CacheMemory c; + + // Add some frames (out of order) + std::shared_ptr f3(new Frame(3, 1280, 720, "Blue", 500, 2)); + c.Add(f3); + CHECK_EQUAL(1, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("1", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f1(new Frame(1, 1280, 720, "Blue", 500, 2)); + c.Add(f1); + CHECK_EQUAL(2, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("2", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f2(new Frame(2, 1280, 720, "Blue", 500, 2)); + c.Add(f2); + CHECK_EQUAL(1, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("3", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f5(new Frame(5, 1280, 720, "Blue", 500, 2)); + c.Add(f5); + CHECK_EQUAL(2, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("4", c.JsonValue()["version"].asString()); + + // Add some frames (out of order) + std::shared_ptr f4(new Frame(4, 1280, 720, "Blue", 500, 2)); + c.Add(f4); + CHECK_EQUAL(1, (int)c.JsonValue()["ranges"].size()); + CHECK_EQUAL("5", c.JsonValue()["version"].asString()); + +} diff --git a/tests/cppunittest/Clip_Tests.cpp b/tests/cppunittest/Clip_Tests.cpp new file mode 100644 index 000000000..5d8ab43ce --- /dev/null +++ b/tests/cppunittest/Clip_Tests.cpp @@ -0,0 +1,264 @@ +/** + * @file + * @brief Unit tests for openshot::Clip + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" + +// Work around older versions of UnitTest++ without REQUIRE +#ifndef REQUIRE + #define REQUIRE +#endif + +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Clip.h" +#include "Frame.h" +#include "Fraction.h" +#include "Timeline.h" +#include "Json.h" + +using namespace openshot; + +SUITE(Clip) +{ + +TEST(Default_Constructor) +{ + // Create a empty clip + Clip c1; + + // Check basic settings + CHECK_EQUAL(ANCHOR_CANVAS, c1.anchor); + CHECK_EQUAL(GRAVITY_CENTER, c1.gravity); + CHECK_EQUAL(SCALE_FIT, c1.scale); + CHECK_EQUAL(0, c1.Layer()); + CHECK_CLOSE(0.0f, c1.Position(), 0.00001); + CHECK_CLOSE(0.0f, c1.Start(), 0.00001); + CHECK_CLOSE(0.0f, c1.End(), 0.00001); +} + +TEST(Clip_Constructor) +{ + // Create a empty clip + std::stringstream path; + path << TEST_MEDIA_PATH << "piano.wav"; + Clip c1(path.str()); + c1.Open(); + + // Check basic settings + CHECK_EQUAL(ANCHOR_CANVAS, c1.anchor); + CHECK_EQUAL(GRAVITY_CENTER, c1.gravity); + CHECK_EQUAL(SCALE_FIT, c1.scale); + CHECK_EQUAL(0, c1.Layer()); + CHECK_CLOSE(0.0f, c1.Position(), 0.00001); + CHECK_CLOSE(0.0f, c1.Start(), 0.00001); + CHECK_CLOSE(4.39937f, c1.End(), 0.00001); +} + +TEST(Basic_Gettings_and_Setters) +{ + // Create a empty clip + Clip c1; + + // Check basic settings + CHECK_THROW(c1.Open(), ReaderClosed); + CHECK_EQUAL(ANCHOR_CANVAS, c1.anchor); + CHECK_EQUAL(GRAVITY_CENTER, c1.gravity); + CHECK_EQUAL(SCALE_FIT, c1.scale); + CHECK_EQUAL(0, c1.Layer()); + CHECK_CLOSE(0.0f, c1.Position(), 0.00001); + CHECK_CLOSE(0.0f, c1.Start(), 0.00001); + CHECK_CLOSE(0.0f, c1.End(), 0.00001); + + // Change some properties + c1.Layer(1); + c1.Position(5.0); + c1.Start(3.5); + c1.End(10.5); + + CHECK_EQUAL(1, c1.Layer()); + CHECK_CLOSE(5.0f, c1.Position(), 0.00001); + CHECK_CLOSE(3.5f, c1.Start(), 0.00001); + CHECK_CLOSE(10.5f, c1.End(), 0.00001); +} + +TEST(Properties) +{ + // Create a empty clip + Clip c1; + + // Change some properties + c1.Layer(1); + c1.Position(5.0); + c1.Start(3.5); + c1.End(10.5); + c1.alpha.AddPoint(1, 1.0); + c1.alpha.AddPoint(500, 0.0); + + // Get properties JSON string at frame 1 + std::string properties = c1.PropertiesJSON(1); + + // Parse JSON string into JSON objects + Json::Value root; + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + std::string errors; + bool success = reader->parse( + properties.c_str(), + properties.c_str() + properties.size(), + &root, &errors ); + CHECK_EQUAL(true, success); + + // Check for specific things + CHECK_CLOSE(1.0f, root["alpha"]["value"].asDouble(), 0.01); + CHECK_EQUAL(true, root["alpha"]["keyframe"].asBool()); + + // Get properties JSON string at frame 250 + properties = c1.PropertiesJSON(250); + + // Parse JSON string into JSON objects + root.clear(); + success = reader->parse( + properties.c_str(), + properties.c_str() + properties.size(), + &root, &errors ); + REQUIRE CHECK_EQUAL(true, success); + + // Check for specific things + CHECK_CLOSE(0.5f, root["alpha"]["value"].asDouble(), 0.01); + CHECK_EQUAL(false, root["alpha"]["keyframe"].asBool()); + + // Get properties JSON string at frame 250 (again) + properties = c1.PropertiesJSON(250); + + // Parse JSON string into JSON objects + root.clear(); + success = reader->parse( + properties.c_str(), + properties.c_str() + properties.size(), + &root, &errors ); + REQUIRE CHECK_EQUAL(true, success); + + // Check for specific things + CHECK_EQUAL(false, root["alpha"]["keyframe"].asBool()); + + // Get properties JSON string at frame 500 + properties = c1.PropertiesJSON(500); + + // Parse JSON string into JSON objects + root.clear(); + success = reader->parse( + properties.c_str(), + properties.c_str() + properties.size(), + &root, &errors ); + REQUIRE CHECK_EQUAL(true, success); + + // Check for specific things + CHECK_CLOSE(0.0f, root["alpha"]["value"].asDouble(), 0.00001); + CHECK_EQUAL(true, root["alpha"]["keyframe"].asBool()); + + // Free up the reader we allocated + delete reader; +} + +TEST(Effects) +{ + // Load clip with video + std::stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + Clip c10(path.str()); + c10.Open(); + + Negate n; + c10.AddEffect(&n); + + // Get frame 1 + std::shared_ptr f = c10.GetFrame(500); + + // Get the image data + const unsigned char* pixels = f->GetPixels(10); + int pixel_index = 112 * 4; // pixel 112 (4 bytes per pixel) + + // Check image properties on scanline 10, pixel 112 + CHECK_EQUAL(255, (int)pixels[pixel_index]); + CHECK_EQUAL(255, (int)pixels[pixel_index + 1]); + CHECK_EQUAL(255, (int)pixels[pixel_index + 2]); + CHECK_EQUAL(255, (int)pixels[pixel_index + 3]); + + // Check the # of Effects + CHECK_EQUAL(1, (int)c10.Effects().size()); + + + // Add a 2nd negate effect + Negate n1; + c10.AddEffect(&n1); + + // Get frame 1 + f = c10.GetFrame(500); + + // Get the image data + pixels = f->GetPixels(10); + pixel_index = 112 * 4; // pixel 112 (4 bytes per pixel) + + // Check image properties on scanline 10, pixel 112 + CHECK_EQUAL(0, (int)pixels[pixel_index]); + CHECK_EQUAL(0, (int)pixels[pixel_index + 1]); + CHECK_EQUAL(0, (int)pixels[pixel_index + 2]); + CHECK_EQUAL(255, (int)pixels[pixel_index + 3]); + + // Check the # of Effects + CHECK_EQUAL(2, (int)c10.Effects().size()); +} + +TEST(Verify_Parent_Timeline) +{ + Timeline t1(640, 480, Fraction(30,1), 44100, 2, LAYOUT_STEREO); + + // Load clip with video + std::stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + Clip c1(path.str()); + c1.Open(); + + // Check size of frame image + CHECK_EQUAL(c1.GetFrame(1)->GetImage()->width(), 1280); + CHECK_EQUAL(c1.GetFrame(1)->GetImage()->height(), 720); + + // Add clip to timeline + t1.AddClip(&c1); + + // Check size of frame image (with an associated timeline) + CHECK_EQUAL(c1.GetFrame(1)->GetImage()->width(), 640); + CHECK_EQUAL(c1.GetFrame(1)->GetImage()->height(), 360); +} + +} // SUITE diff --git a/tests/cppunittest/Color_Tests.cpp b/tests/cppunittest/Color_Tests.cpp new file mode 100644 index 000000000..8b110f9e1 --- /dev/null +++ b/tests/cppunittest/Color_Tests.cpp @@ -0,0 +1,186 @@ +/** + * @file + * @brief Unit tests for openshot::Color + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Color.h" +#include "Exceptions.h" +#include "KeyFrame.h" +#include "Json.h" + +SUITE(Color) { + +TEST(Default_Constructor) +{ + // Create an empty color + 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(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 + openshot::Color c1; + + // Set starting color (on frame 0) + c1.red.AddPoint(1, 0); + c1.green.AddPoint(1, 120); + c1.blue.AddPoint(1, 255); + + // Set ending color (on frame 1000) + c1.red.AddPoint(1000, 0); + c1.green.AddPoint(1000, 255); + c1.blue.AddPoint(1000, 65); + + // Check the color at frame 500 + CHECK_CLOSE(0, c1.red.GetLong(500), 0.01); + CHECK_CLOSE(187, c1.green.GetLong(500), 0.01); + CHECK_CLOSE(160, c1.blue.GetLong(500), 0.01); +} + +TEST(HEX_Value) +{ + // Color + openshot::Color c; + c.red = openshot::Keyframe(0); + c.red.AddPoint(100, 255); + c.green = openshot::Keyframe(0); + c.green.AddPoint(100, 255); + c.blue = openshot::Keyframe(0); + c.blue.AddPoint(100, 255); + + CHECK_EQUAL("#000000", c.GetColorHex(1)); + CHECK_EQUAL("#7d7d7d", c.GetColorHex(50)); + CHECK_EQUAL("#ffffff", c.GetColorHex(100)); + +} + +TEST(HEX_Constructor) +{ + // Color + openshot::Color c("#4586db"); + c.red.AddPoint(100, 255); + c.green.AddPoint(100, 255); + c.blue.AddPoint(100, 255); + + CHECK_EQUAL("#4586db", c.GetColorHex(1)); + CHECK_EQUAL("#a0c1ed", c.GetColorHex(50)); + CHECK_EQUAL("#ffffff", c.GetColorHex(100)); +} + +TEST(Distance) +{ + // Color + openshot::Color c1("#040a0c"); + openshot::Color c2("#0c0c04"); + openshot::Color c3("#000000"); + openshot::Color c4("#ffffff"); + + 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(RGBA_Constructor) +{ + // Color + openshot::Color c(69, 134, 219, 255); + c.red.AddPoint(100, 255); + c.green.AddPoint(100, 255); + c.blue.AddPoint(100, 255); + + CHECK_EQUAL("#4586db", c.GetColorHex(1)); + CHECK_EQUAL("#a0c1ed", c.GetColorHex(50)); + CHECK_EQUAL("#ffffff", c.GetColorHex(100)); + + // Color with alpha + openshot::Color c1(69, 134, 219, 128); + 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/cppunittest/Coordinate_Tests.cpp b/tests/cppunittest/Coordinate_Tests.cpp new file mode 100644 index 000000000..7dd5886c1 --- /dev/null +++ b/tests/cppunittest/Coordinate_Tests.cpp @@ -0,0 +1,99 @@ +/** + * @file + * @brief Unit tests for openshot::Coordinate + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Coordinate.h" +#include "Exceptions.h" + +using namespace openshot; + +SUITE(Coordinate) +{ + +TEST(Default_Constructor) +{ + // Create an empty coordinate + Coordinate c1; + + CHECK_CLOSE(0.0f, c1.X, 0.00001); + CHECK_CLOSE(0.0f, c1.Y, 0.00001); +} + +TEST(X_Y_Constructor) +{ + // Create an empty coordinate + Coordinate c1(2,8); + + CHECK_CLOSE(2.0f, c1.X, 0.00001); + CHECK_CLOSE(8.0f, c1.Y, 0.00001); +} + +TEST(Pair_Constructor) +{ + Coordinate c1(std::pair(12, 10)); + CHECK_CLOSE(12.0f, c1.X, 0.00001); + CHECK_CLOSE(10.0f, c1.Y, 0.00001); +} + +TEST(Json) +{ + openshot::Coordinate c(100, 200); + openshot::Coordinate c1; + c1.X = 100; + c1.Y = 200; + // 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) { + // Construct our input Json representation + const std::string json_input = R"json( + { + "X": 100.0, + "Y": 50.0 + } + )json"; + openshot::Coordinate c; + CHECK_THROW(c.SetJson("}{"), openshot::InvalidJSON); + // Check that values set via SetJson() are correct + c.SetJson(json_input); + CHECK_CLOSE(100.0, c.X, 0.01); + CHECK_CLOSE(50.0, c.Y, 0.01); +} + +} // SUITE diff --git a/tests/cppunittest/DummyReader_Tests.cpp b/tests/cppunittest/DummyReader_Tests.cpp new file mode 100644 index 000000000..806bd281e --- /dev/null +++ b/tests/cppunittest/DummyReader_Tests.cpp @@ -0,0 +1,192 @@ +/** + * @file + * @brief Unit tests for openshot::DummyReader + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 + +#include "DummyReader.h" +#include "Exceptions.h" +#include "CacheMemory.h" +#include "Fraction.h" +#include "Frame.h" + +SUITE (DummyReader) { + +TEST (Default_Constructor) { + openshot::DummyReader r; + + CHECK_EQUAL(false, r.IsOpen()); + CHECK_THROW(r.GetFrame(1), openshot::ReaderClosed); + + r.Open(); + + // Default values + CHECK_EQUAL(1280, r.info.width); + CHECK_EQUAL(768, r.info.height); + CHECK_EQUAL(24, r.info.fps.num); + CHECK_EQUAL(1, r.info.fps.den); + CHECK_EQUAL(44100, r.info.sample_rate); + CHECK_EQUAL(2, r.info.channels); + CHECK_EQUAL(30.0, r.info.duration); + + CHECK_EQUAL("DummyReader", r.Name()); + + auto cache = r.GetCache(); + CHECK_EQUAL(true, cache == nullptr); +} + +TEST (Constructor) { + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 48000, 2, 60.0); + r.Open(); + + // Check values + CHECK_EQUAL(1920, r.info.width); + CHECK_EQUAL(1080, r.info.height); + CHECK_EQUAL(30, r.info.fps.num); + CHECK_EQUAL(1, r.info.fps.den); + CHECK_EQUAL(48000, r.info.sample_rate); + CHECK_EQUAL(2, r.info.channels); + CHECK_EQUAL(60.0, r.info.duration); +} + +TEST (Blank_Frame) { + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + r.Open(); + + // Get a blank frame (because we have not passed a Cache object (full of Frame objects) to the constructor + // Check values + CHECK_EQUAL(1, r.GetFrame(1)->number); + CHECK_EQUAL(1, r.GetFrame(1)->GetPixels(700)[700] == 0); // black pixel + CHECK_EQUAL(1, r.GetFrame(1)->GetPixels(701)[701] == 0); // black pixel +} + +TEST (Fake_Frame) { + + // Create cache object to hold test frames + openshot::CacheMemory cache; + + // Let's create some test frames + for (int64_t frame_number = 1; frame_number <= 30; frame_number++) { + // Create blank frame (with specific frame #, samples, and channels) + // Sample count should be 44100 / 30 fps = 1470 samples per frame + int sample_count = 1470; + auto f = std::make_shared(frame_number, sample_count, 2); + + // Create test samples with incrementing value + float *audio_buffer = new float[sample_count]; + for (int64_t sample_number = 0; sample_number < sample_count; sample_number++) { + // Generate an incrementing audio sample value (just as an example) + audio_buffer[sample_number] = float(frame_number) + (float(sample_number) / float(sample_count)); + } + + // Add custom audio samples to Frame (bool replaceSamples, int destChannel, int destStartSample, const float* source, + f->AddAudio(true, 0, 0, audio_buffer, sample_count, 1.0); // add channel 1 + f->AddAudio(true, 1, 0, audio_buffer, sample_count, 1.0); // add channel 2 + + // Add test frame to dummy reader + cache.Add(f); + } + + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0, &cache); + r.Open(); + + // Verify our artificial audio sample data is correct + CHECK_EQUAL(1, r.GetFrame(1)->number); + CHECK_EQUAL(1, r.GetFrame(1)->GetAudioSamples(0)[0]); + CHECK_CLOSE(1.00068033, r.GetFrame(1)->GetAudioSamples(0)[1], 0.00001); + CHECK_CLOSE(1.00136054, r.GetFrame(1)->GetAudioSamples(0)[2], 0.00001); + CHECK_EQUAL(2, r.GetFrame(2)->GetAudioSamples(0)[0]); + CHECK_CLOSE(2.00068033, r.GetFrame(2)->GetAudioSamples(0)[1], 0.00001); + CHECK_CLOSE(2.00136054, r.GetFrame(2)->GetAudioSamples(0)[2], 0.00001); + + // Clean up + cache.Clear(); + r.Close(); +} + +TEST (Invalid_Fake_Frame) { + // Create fake frames (with specific frame #, samples, and channels) + auto f1 = std::make_shared(1, 1470, 2); + auto f2 = std::make_shared(2, 1470, 2); + + // Add test frames to cache object + openshot::CacheMemory cache; + cache.Add(f1); + cache.Add(f2); + + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0, &cache); + r.Open(); + + // Verify exception + CHECK_EQUAL(1, r.GetFrame(1)->number); + CHECK_EQUAL(2, r.GetFrame(2)->number); + CHECK_THROW(r.GetFrame(3)->number, openshot::InvalidFile); + + // Clean up + cache.Clear(); + r.Close(); +} + + +TEST(Json) +{ + openshot::DummyReader r1; + openshot::DummyReader r2(openshot::Fraction(24, 1), 1280, 768, 44100, 2, 30.0); + auto json1 = r1.Json(); + auto json2 = r2.JsonValue(); + auto json_string2 = json2.toStyledString(); + CHECK_EQUAL(json1, json_string2); +} + +TEST(SetJson) +{ + openshot::DummyReader r1; + std::stringstream json_stream; + json_stream << R"json( + { + "width": 1920, + "height": 1080, + "fps": { "num": 15, "den": 1 }, + "duration": 15.0 + } + )json"; + + r1.SetJson(json_stream.str()); + CHECK_EQUAL(1920, r1.info.width); + CHECK_EQUAL(1080, r1.info.height); + CHECK_EQUAL(15, r1.info.fps.num); + CHECK_EQUAL(1, r1.info.fps.den); + CHECK_EQUAL(15.0, r1.info.duration); +} + +} // SUITE diff --git a/tests/cppunittest/FFmpegReader_Tests.cpp b/tests/cppunittest/FFmpegReader_Tests.cpp new file mode 100644 index 000000000..f5dc44350 --- /dev/null +++ b/tests/cppunittest/FFmpegReader_Tests.cpp @@ -0,0 +1,281 @@ +/** + * @file + * @brief Unit tests for openshot::FFmpegReader + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "FFmpegReader.h" +#include "Exceptions.h" +#include "Frame.h" +#include "Timeline.h" +#include "Json.h" + +using namespace std; +using namespace openshot; + +SUITE(FFmpegReader) +{ + +TEST(Invalid_Path) +{ + // Check invalid path + CHECK_THROW(FFmpegReader(""), InvalidFile); +} + +TEST(GetFrame_Before_Opening) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "piano.wav"; + FFmpegReader r(path.str()); + + // Check invalid path + CHECK_THROW(r.GetFrame(1), ReaderClosed); +} + +TEST(Check_Audio_File) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "piano.wav"; + FFmpegReader r(path.str()); + r.Open(); + + // Get frame 1 + std::shared_ptr f = r.GetFrame(1); + + // Get the number of channels and samples + float *samples = f->GetAudioSamples(0); + + // Check audio properties + CHECK_EQUAL(2, f->GetAudioChannelsCount()); + CHECK_EQUAL(332, f->GetAudioSamplesCount()); + + // Check actual sample values (to be sure the waveform is correct) + CHECK_CLOSE(0.0f, samples[0], 0.00001); + CHECK_CLOSE(0.0f, samples[50], 0.00001); + CHECK_CLOSE(0.0f, samples[100], 0.00001); + CHECK_CLOSE(0.0f, samples[200], 0.00001); + CHECK_CLOSE(0.16406f, samples[230], 0.00001); + CHECK_CLOSE(-0.06250f, samples[300], 0.00001); + + // Close reader + r.Close(); +} + +TEST(Check_Video_File) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "test.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + // Get frame 1 + std::shared_ptr f = r.GetFrame(1); + + // Get the image data + const unsigned char* pixels = f->GetPixels(10); + int pixel_index = 112 * 4; // pixel 112 (4 bytes per pixel) + + // Check image properties on scanline 10, pixel 112 + CHECK_CLOSE(21, (int)pixels[pixel_index], 5); + CHECK_CLOSE(191, (int)pixels[pixel_index + 1], 5); + CHECK_CLOSE(0, (int)pixels[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)pixels[pixel_index + 3], 5); + + // Check pixel function + CHECK_EQUAL(true, f->CheckPixel(10, 112, 21, 191, 0, 255, 5)); + CHECK_EQUAL(false, f->CheckPixel(10, 112, 0, 0, 0, 0, 5)); + + // Get frame 1 + f = r.GetFrame(2); + + // Get the next frame + pixels = f->GetPixels(10); + pixel_index = 112 * 4; // pixel 112 (4 bytes per pixel) + + // Check image properties on scanline 10, pixel 112 + CHECK_CLOSE(0, (int)pixels[pixel_index], 5); + CHECK_CLOSE(96, (int)pixels[pixel_index + 1], 5); + CHECK_CLOSE(188, (int)pixels[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)pixels[pixel_index + 3], 5); + + // Check pixel function + CHECK_EQUAL(true, f->CheckPixel(10, 112, 0, 96, 188, 255, 5)); + CHECK_EQUAL(false, f->CheckPixel(10, 112, 0, 0, 0, 0, 5)); + + // Close reader + r.Close(); +} + +TEST(Seek) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + // Get frame + std::shared_ptr f = r.GetFrame(1); + CHECK_EQUAL(1, f->number); + + // Get frame + f = r.GetFrame(300); + CHECK_EQUAL(300, f->number); + + // Get frame + f = r.GetFrame(301); + CHECK_EQUAL(301, f->number); + + // Get frame + f = r.GetFrame(315); + CHECK_EQUAL(315, f->number); + + // Get frame + f = r.GetFrame(275); + CHECK_EQUAL(275, f->number); + + // Get frame + f = r.GetFrame(270); + CHECK_EQUAL(270, f->number); + + // Get frame + f = r.GetFrame(500); + CHECK_EQUAL(500, f->number); + + // Get frame + f = r.GetFrame(100); + CHECK_EQUAL(100, f->number); + + // Get frame + f = r.GetFrame(600); + CHECK_EQUAL(600, f->number); + + // Get frame + f = r.GetFrame(1); + CHECK_EQUAL(1, f->number); + + // Get frame + f = r.GetFrame(700); + CHECK_EQUAL(700, f->number); + + // Close reader + r.Close(); + +} + +TEST(Frame_Rate) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + // Verify detected frame rate + openshot::Fraction rate = r.info.fps; + CHECK_EQUAL(24, rate.num); + CHECK_EQUAL(1, rate.den); + + r.Close(); +} + +TEST(Multiple_Open_and_Close) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + // Get frame that requires a seek + std::shared_ptr f = r.GetFrame(1200); + CHECK_EQUAL(1200, f->number); + + // Close and Re-open the reader + r.Close(); + r.Open(); + + // Get frame + f = r.GetFrame(1); + CHECK_EQUAL(1, f->number); + f = r.GetFrame(250); + CHECK_EQUAL(250, f->number); + + // Close and Re-open the reader + r.Close(); + r.Open(); + + // Get frame + f = r.GetFrame(750); + CHECK_EQUAL(750, f->number); + f = r.GetFrame(1000); + CHECK_EQUAL(1000, f->number); + + // Close reader + r.Close(); +} + +TEST(Verify_Parent_Timeline) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + // Check size of frame image + CHECK_EQUAL(r.GetFrame(1)->GetImage()->width(), 1280); + CHECK_EQUAL(r.GetFrame(1)->GetImage()->height(), 720); + r.GetFrame(1)->GetImage()->save("reader-1.png", "PNG"); + + // Create a Clip associated with this reader + Clip c1(&r); + c1.Open(); + + // Check size of frame image (should still be the same) + CHECK_EQUAL(r.GetFrame(1)->GetImage()->width(), 1280); + CHECK_EQUAL(r.GetFrame(1)->GetImage()->height(), 720); + + // Create Timeline + Timeline t1(640, 480, Fraction(30,1), 44100, 2, LAYOUT_STEREO); + t1.AddClip(&c1); + + // Check size of frame image (it should now match the parent timeline) + CHECK_EQUAL(r.GetFrame(1)->GetImage()->width(), 640); + CHECK_EQUAL(r.GetFrame(1)->GetImage()->height(), 360); +} + +} // SUITE(FFmpegReader) diff --git a/tests/cppunittest/FFmpegWriter_Tests.cpp b/tests/cppunittest/FFmpegWriter_Tests.cpp new file mode 100644 index 000000000..0160ac929 --- /dev/null +++ b/tests/cppunittest/FFmpegWriter_Tests.cpp @@ -0,0 +1,135 @@ +/** + * @file + * @brief Unit tests for openshot::FFmpegWriter + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "FFmpegWriter.h" +#include "Exceptions.h" +#include "FFmpegReader.h" +#include "Fraction.h" +#include "Frame.h" + +using namespace std; +using namespace openshot; + +SUITE(FFMpegWriter) { +TEST(Webm) +{ + // Reader + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + /* WRITER ---------------- */ + FFmpegWriter w("output1.webm"); + + // Set options + w.SetAudioOptions(true, "libvorbis", 44100, 2, LAYOUT_STEREO, 188000); + w.SetVideoOptions(true, "libvpx", Fraction(24,1), 1280, 720, Fraction(1,1), false, false, 30000000); + + // Open writer + w.Open(); + + // Write some frames + w.WriteFrame(&r, 24, 50); + + // Close writer & reader + w.Close(); + r.Close(); + + FFmpegReader r1("output1.webm"); + r1.Open(); + + // Verify various settings on new MP4 + CHECK_EQUAL(2, r1.GetFrame(1)->GetAudioChannelsCount()); + CHECK_EQUAL(24, r1.info.fps.num); + CHECK_EQUAL(1, r1.info.fps.den); + + // Get a specific frame + std::shared_ptr f = r1.GetFrame(8); + + // Get the image data for row 500 + const unsigned char* pixels = f->GetPixels(500); + int pixel_index = 112 * 4; // pixel 112 (4 bytes per pixel) + + // Check image properties on scanline 10, pixel 112 + CHECK_CLOSE(23, (int)pixels[pixel_index], 5); + CHECK_CLOSE(23, (int)pixels[pixel_index + 1], 5); + CHECK_CLOSE(23, (int)pixels[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)pixels[pixel_index + 3], 5); +} + +TEST(Options_Overloads) +{ + // Reader + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + r.Open(); + + /* WRITER ---------------- */ + FFmpegWriter w("output1.mp4"); + + // Set options + w.SetAudioOptions("aac", 48000, 192000); + w.SetVideoOptions("libx264", 1280, 720, Fraction(30,1), 5000000); + + // Open writer + w.Open(); + + // Write some frames + w.WriteFrame(&r, 24, 50); + + // Close writer & reader + w.Close(); + r.Close(); + + FFmpegReader r1("output1.mp4"); + r1.Open(); + + // Verify implied settings + CHECK_EQUAL(true, r1.info.has_audio); + CHECK_EQUAL(true, r1.info.has_video); + + CHECK_EQUAL(2, r1.GetFrame(1)->GetAudioChannelsCount()); + CHECK_EQUAL(LAYOUT_STEREO, r1.info.channel_layout); + + CHECK_EQUAL(1, r1.info.pixel_ratio.num); + CHECK_EQUAL(1, r1.info.pixel_ratio.den); + CHECK_EQUAL(false, r1.info.interlaced_frame); + CHECK_EQUAL(true, r1.info.top_field_first); +} + +} // SUITE() diff --git a/tests/cppunittest/Fraction_Tests.cpp b/tests/cppunittest/Fraction_Tests.cpp new file mode 100644 index 000000000..760a83807 --- /dev/null +++ b/tests/cppunittest/Fraction_Tests.cpp @@ -0,0 +1,156 @@ +/** + * @file + * @brief Unit tests for openshot::Fraction + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Fraction.h" + +using namespace std; +using namespace openshot; + +SUITE(Fraction) +{ + +TEST(Constructors) +{ + // Create a default fraction (should be 1/1) + Fraction f1; + + // Check default fraction + CHECK_EQUAL(1, f1.num); + CHECK_EQUAL(1, f1.den); + CHECK_CLOSE(1.0f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.0f, f1.ToDouble(), 0.00001); + + // reduce fraction + f1.Reduce(); + + // Check the reduced fraction + CHECK_EQUAL(1, f1.num); + CHECK_EQUAL(1, f1.den); + CHECK_CLOSE(1.0f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.0f, f1.ToDouble(), 0.00001); +} + +TEST(Alt_Constructors) +{ + // Use the delegating constructor for std::pair + std::pair args{24, 1}; + Fraction f1(args); + CHECK_EQUAL(24, f1.num); + CHECK_EQUAL(1, f1.den); + CHECK_CLOSE(24.0f, f1.ToFloat(), 0.00001); + + // Use the delegating constructor for std::vector + std::vector v{30000, 1001}; + Fraction f2(v); + CHECK_CLOSE(30000.0/1001.0, f2.ToFloat(), 0.00001); + + // Use the delegating constructor for std::map + std::map dict; + dict.insert({"num", 24000}); + dict.insert({"den", 1001}); + Fraction f3(dict); + CHECK_EQUAL(1001, f3.den); + CHECK_EQUAL(24000, f3.num); + CHECK_CLOSE(1001.0/24000.0, f3.Reciprocal().ToFloat(), 0.00001); +} + +TEST(WxH_640_480) +{ + // Create fraction + Fraction f1(640, 480); + + // Check fraction + CHECK_EQUAL(640, f1.num); + CHECK_EQUAL(480, f1.den); + CHECK_CLOSE(1.33333f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.33333f, f1.ToDouble(), 0.00001); + + // reduce fraction + f1.Reduce(); + + // Check the reduced fraction + CHECK_EQUAL(4, f1.num); + CHECK_EQUAL(3, f1.den); + CHECK_CLOSE(1.33333f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.33333f, f1.ToDouble(), 0.00001); +} + +TEST(WxH_1280_720) +{ + // Create fraction + Fraction f1(1280, 720); + + // Check fraction + CHECK_EQUAL(1280, f1.num); + CHECK_EQUAL(720, f1.den); + CHECK_CLOSE(1.77777f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.77777f, f1.ToDouble(), 0.00001); + + // reduce fraction + f1.Reduce(); + + // Check the reduced fraction + CHECK_EQUAL(16, f1.num); + CHECK_EQUAL(9, f1.den); + CHECK_CLOSE(1.77777f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.77777f, f1.ToDouble(), 0.00001); +} + +TEST(Reciprocal) +{ + // Create fraction + Fraction f1(1280, 720); + + // Check fraction + CHECK_EQUAL(1280, f1.num); + CHECK_EQUAL(720, f1.den); + CHECK_CLOSE(1.77777f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.77777f, f1.ToDouble(), 0.00001); + + // Get the reciprocal of the fraction (i.e. flip the fraction) + Fraction f2 = f1.Reciprocal(); + + // Check the reduced fraction + CHECK_EQUAL(720, f2.num); + CHECK_EQUAL(1280, f2.den); + CHECK_CLOSE(0.5625f, f2.ToFloat(), 0.00001); + CHECK_CLOSE(0.5625f, f2.ToDouble(), 0.00001); + + // Re-Check the original fraction (to be sure it hasn't changed) + CHECK_EQUAL(1280, f1.num); + CHECK_EQUAL(720, f1.den); + CHECK_CLOSE(1.77777f, f1.ToFloat(), 0.00001); + CHECK_CLOSE(1.77777f, f1.ToDouble(), 0.00001); +} + +} // SUITE diff --git a/tests/cppunittest/FrameMapper_Tests.cpp b/tests/cppunittest/FrameMapper_Tests.cpp new file mode 100644 index 000000000..d586c30c2 --- /dev/null +++ b/tests/cppunittest/FrameMapper_Tests.cpp @@ -0,0 +1,501 @@ +/** + * @file + * @brief Unit tests for openshot::FrameMapper + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#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; + +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 24 fps without pulldown + FrameMapper mapping(&r, Fraction(24, 1), PULLDOWN_NONE, 22000, 2, LAYOUT_STEREO); + CHECK_EQUAL("FrameMapper", mapping.Name()); + + // 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(Invalid_Frame_Too_Small) +{ + // Create a reader + DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); + + // Create mapping 24 fps and 29.97 fps + FrameMapper mapping(&r, Fraction(30000, 1001), PULLDOWN_CLASSIC, 22000, 2, LAYOUT_STEREO); + + // Check invalid frame number + CHECK_THROW(mapping.GetMappedFrame(0), OutOfBoundsFrame); + +} + +TEST(24_fps_to_30_fps_Pulldown_Classic) +{ + // Create a reader + DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); + + // Create mapping 24 fps and 30 fps + FrameMapper mapping(&r, Fraction(30, 1), PULLDOWN_CLASSIC, 22000, 2, LAYOUT_STEREO); + MappedFrame frame2 = mapping.GetMappedFrame(2); + MappedFrame frame3 = mapping.GetMappedFrame(3); + + // Check for 3 fields of frame 2 + CHECK_EQUAL(2, frame2.Odd.Frame); + CHECK_EQUAL(2, frame2.Even.Frame); + CHECK_EQUAL(2, frame3.Odd.Frame); + CHECK_EQUAL(3, frame3.Even.Frame); +} + +TEST(24_fps_to_30_fps_Pulldown_Advanced) +{ + // Create a reader + DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); + + // Create mapping 24 fps and 30 fps + FrameMapper mapping(&r, Fraction(30, 1), PULLDOWN_ADVANCED, 22000, 2, LAYOUT_STEREO); + MappedFrame frame2 = mapping.GetMappedFrame(2); + MappedFrame frame3 = mapping.GetMappedFrame(3); + MappedFrame frame4 = mapping.GetMappedFrame(4); + + // Check for advanced pulldown (only 1 fake frame) + CHECK_EQUAL(2, frame2.Odd.Frame); + CHECK_EQUAL(2, frame2.Even.Frame); + CHECK_EQUAL(2, frame3.Odd.Frame); + CHECK_EQUAL(3, frame3.Even.Frame); + CHECK_EQUAL(3, frame4.Odd.Frame); + CHECK_EQUAL(3, frame4.Even.Frame); +} + +TEST(24_fps_to_30_fps_Pulldown_None) +{ + // Create a reader + DummyReader r(Fraction(24,1), 720, 480, 22000, 2, 5.0); + + // Create mapping 24 fps and 30 fps + FrameMapper mapping(&r, Fraction(30, 1), PULLDOWN_NONE, 22000, 2, LAYOUT_STEREO); + MappedFrame frame4 = mapping.GetMappedFrame(4); + MappedFrame frame5 = mapping.GetMappedFrame(5); + + // Check for advanced pulldown (only 1 fake frame) + CHECK_EQUAL(4, frame4.Odd.Frame); + CHECK_EQUAL(4, frame4.Even.Frame); + CHECK_EQUAL(4, frame5.Odd.Frame); + CHECK_EQUAL(4, frame5.Even.Frame); +} + +TEST(30_fps_to_24_fps_Pulldown_Classic) +{ + // Create a reader + DummyReader r(Fraction(30, 1), 720, 480, 22000, 2, 5.0); + + // Create mapping between 30 fps and 24 fps + FrameMapper mapping(&r, Fraction(24, 1), PULLDOWN_CLASSIC, 22000, 2, LAYOUT_STEREO); + MappedFrame frame3 = mapping.GetMappedFrame(3); + MappedFrame frame4 = mapping.GetMappedFrame(4); + MappedFrame frame5 = mapping.GetMappedFrame(5); + + // Check for advanced pulldown (only 1 fake frame) + CHECK_EQUAL(4, frame3.Odd.Frame); + CHECK_EQUAL(3, frame3.Even.Frame); + CHECK_EQUAL(5, frame4.Odd.Frame); + CHECK_EQUAL(4, frame4.Even.Frame); + CHECK_EQUAL(6, frame5.Odd.Frame); + CHECK_EQUAL(6, frame5.Even.Frame); +} + +TEST(30_fps_to_24_fps_Pulldown_Advanced) +{ + // Create a reader + DummyReader r(Fraction(30, 1), 720, 480, 22000, 2, 5.0); + + // Create mapping between 30 fps and 24 fps + FrameMapper mapping(&r, Fraction(24, 1), PULLDOWN_ADVANCED, 22000, 2, LAYOUT_STEREO); + MappedFrame frame2 = mapping.GetMappedFrame(2); + MappedFrame frame3 = mapping.GetMappedFrame(3); + MappedFrame frame4 = mapping.GetMappedFrame(4); + + // Check for advanced pulldown (only 1 fake frame) + CHECK_EQUAL(2, frame2.Odd.Frame); + CHECK_EQUAL(2, frame2.Even.Frame); + CHECK_EQUAL(4, frame3.Odd.Frame); + CHECK_EQUAL(4, frame3.Even.Frame); + CHECK_EQUAL(5, frame4.Odd.Frame); + CHECK_EQUAL(5, frame4.Even.Frame); +} + +TEST(30_fps_to_24_fps_Pulldown_None) +{ + // Create a reader + DummyReader r(Fraction(30, 1), 720, 480, 22000, 2, 5.0); + + // Create mapping between 30 fps and 24 fps + FrameMapper mapping(&r, Fraction(24, 1), PULLDOWN_NONE, 22000, 2, LAYOUT_STEREO); + MappedFrame frame4 = mapping.GetMappedFrame(4); + MappedFrame frame5 = mapping.GetMappedFrame(5); + + // Check for advanced pulldown (only 1 fake frame) + CHECK_EQUAL(4, frame4.Odd.Frame); + CHECK_EQUAL(4, frame4.Even.Frame); + CHECK_EQUAL(6, frame5.Odd.Frame); + CHECK_EQUAL(6, frame5.Even.Frame); +} + +TEST(resample_audio_48000_to_41000) +{ + // Create a reader: 24 fps, 2 channels, 48000 sample rate + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + + // Map to 30 fps, 3 channels surround, 44100 sample rate + FrameMapper map(&r, Fraction(30,1), PULLDOWN_NONE, 44100, 3, LAYOUT_SURROUND); + map.Open(); + + // Check details + CHECK_EQUAL(3, map.GetFrame(1)->GetAudioChannelsCount()); + CHECK_EQUAL(1470, map.GetFrame(1)->GetAudioSamplesCount()); + CHECK_EQUAL(1470, map.GetFrame(2)->GetAudioSamplesCount()); + CHECK_EQUAL(1470, map.GetFrame(50)->GetAudioSamplesCount()); + + // Change mapping data + map.ChangeMapping(Fraction(25,1), PULLDOWN_NONE, 22050, 1, LAYOUT_MONO); + + // Check details + CHECK_EQUAL(1, map.GetFrame(1)->GetAudioChannelsCount()); + CHECK_CLOSE(882, map.GetFrame(1)->GetAudioSamplesCount(), 10.0); + CHECK_CLOSE(882, map.GetFrame(2)->GetAudioSamplesCount(), 10.0); + CHECK_CLOSE(882, map.GetFrame(50)->GetAudioSamplesCount(), 10.0); + + // Close mapper + map.Close(); +} + +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, + // and then resample, and compare the result back to the original SIN wave calculation. + + // Create cache object to hold test frames + CacheMemory cache; + + int OFFSET = 0; + float AMPLITUDE = 0.75; + double ANGLE = 0.0; + int NUM_SAMPLES = 100; + + // Let's create some test frames + for (int64_t frame_number = 1; frame_number <= 90; frame_number++) { + // Create blank frame (with specific frame #, samples, and channels) + // Sample count should be 44100 / 30 fps = 1470 samples per frame + int sample_count = 1470; + std::shared_ptr f(new openshot::Frame(frame_number, sample_count, 2)); + + // Create test samples with sin wave (predictable values) + float *audio_buffer = new float[sample_count * 2]; + for (int sample_number = 0; sample_number < sample_count; sample_number++) + { + // Calculate sin wave + float sample_value = float(AMPLITUDE * sin(ANGLE) + OFFSET); + audio_buffer[sample_number] = abs(sample_value); + ANGLE += (2 * M_PI) / NUM_SAMPLES; + } + + // Add custom audio samples to Frame (bool replaceSamples, int destChannel, int destStartSample, const float* source, + f->AddAudio(true, 0, 0, audio_buffer, sample_count, 1.0); // add channel 1 + f->AddAudio(true, 1, 0, audio_buffer, sample_count, 1.0); // add channel 2 + + // Add test frame to dummy reader + cache.Add(f); + } + + // Create a default fraction (should be 1/1) + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0, &cache); + r.Open(); // Open the reader + + // Sample rates + vector arr = { 44100, 16000 }; + for (auto& rate : arr) { + // Reset SIN wave + ANGLE = 0.0; + + // Map to 24 fps, which should create a variable # of samples per frame + FrameMapper map(&r, Fraction(24,1), PULLDOWN_NONE, rate, 2, LAYOUT_STEREO); + map.info.has_audio = true; + map.Open(); + + // Calculating how much the initial sample rate has changed + float resample_multiplier = ((float) rate / r.info.sample_rate); + + // Loop through samples, and verify FrameMapper didn't mess up individual sample values + int num_samples = 0; + for (int frame_index = 1; frame_index <= map.info.fps.ToInt(); frame_index++) { + int sample_count = map.GetFrame(frame_index)->GetAudioSamplesCount(); + for (int sample_index = 0; sample_index < sample_count; sample_index++) { + + // Calculate sin wave + float sample_value = abs(float(AMPLITUDE * sin(ANGLE) + OFFSET)); + ANGLE += (2 * M_PI) / (NUM_SAMPLES * resample_multiplier); + + // Verify each mapped sample value is correct (after being redistributed by the FrameMapper) + float resampled_value = map.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0); + + // TODO: 0.1 is much to broad to accurately test this, but without this, all the resampled values are too far away from expected + CHECK_CLOSE(sample_value, resampled_value, 0.1); + } + // Increment sample value + num_samples += map.GetFrame(frame_index)->GetAudioSamplesCount(); + } + + // Verify samples per second is correct (i.e. 44100) + CHECK_EQUAL(num_samples, map.info.sample_rate); + + // Create Timeline (same specs as reader) + Timeline t1(map.info.width, map.info.height, map.info.fps, rate, map.info.channels, map.info.channel_layout); + + Clip c1; + c1.Reader(&map); + c1.Layer(1); + c1.Position(0.0); + c1.Start(0.0); + c1.End(10.0); + + // Create 2nd map to 24 fps, which should create a variable # of samples per frame (for some sample rates) + FrameMapper map2(&r, Fraction(24, 1), PULLDOWN_NONE, rate, 2, LAYOUT_STEREO); + map2.info.has_audio = true; + map2.Open(); + + Clip c2; + c2.Reader(&map2); + c2.Layer(1); + c2.Position(0.0); + c2.Start(0.0); + c2.End(10.0); + + // Add clips + t1.AddClip(&c1); + t1.AddClip(&c2); + t1.Open(); + + // Reset SIN wave + ANGLE = 0.0; + + for (int frame_index = 1; frame_index < 24; frame_index++) { + t1.GetFrame(frame_index); + for (int sample_index = 0; sample_index < t1.GetFrame(frame_index)->GetAudioSamplesCount(); sample_index++) { + + // Calculate sin wave + float sample_value = abs(float(AMPLITUDE * sin(ANGLE) + OFFSET)); + ANGLE += (2 * M_PI) / (NUM_SAMPLES * resample_multiplier); + + // Verify each mapped sample value is correct (after being redistributed by the FrameMapper) + float resampled_value = t1.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0); + + // TODO: 0.1 is much to broad to accurately test this, but without this, all the resampled values are too far away from expected + // Testing wave value X 2, since we have 2 overlapping clips + CHECK_CLOSE(sample_value * 2.0, resampled_value, 0.1); + + } + } + + // Close mapper + map.Close(); + map2.Close(); + t1.Close(); + } + + // Clean up + cache.Clear(); + r.Close(); +} + +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 + // (and offset 1 clip by 1 frame), and we verify that the correct # of samples is returned by each + // Clip Frame instance. In the past, FrameMappers would sometimes generate the wrong # of samples + // in a frame, and the Timeline recieve mismatching # of audio samples from 2 or more clips... + // causing audio data to be truncated and lost (i.e. creating a pop). + + // Create cache object to hold test frames + CacheMemory cache; + + // Let's create some test frames + int sample_value = 0; + for (int64_t frame_number = 1; frame_number <= 90; frame_number++) { + // Create blank frame (with specific frame #, samples, and channels) + // Sample count should be 44100 / 30 fps = 1470 samples per frame + int sample_count = 1470; + std::shared_ptr f(new openshot::Frame(frame_number, sample_count, 2)); + + // Create test samples with incrementing value + float *audio_buffer = new float[sample_count]; + for (int64_t sample_number = 0; sample_number < sample_count; sample_number++) { + audio_buffer[sample_number] = sample_value + sample_number; + } + + // Increment counter + sample_value += sample_count; + + // Add custom audio samples to Frame (bool replaceSamples, int destChannel, int destStartSample, const float* source, + f->AddAudio(true, 0, 0, audio_buffer, sample_count, 1.0); // add channel 1 + f->AddAudio(true, 1, 0, audio_buffer, sample_count, 1.0); // add channel 2 + + // Add test frame to dummy reader + cache.Add(f); + } + + // Create a default fraction (should be 1/1) + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0, &cache); + r.Open(); // Open the reader + + // Sample rates + vector arr = { 24, 30, 60 }; + for (auto& fps : arr) { + // Map to 24 fps, which should create a variable # of samples per frame + FrameMapper map(&r, Fraction(fps,1), PULLDOWN_NONE, 44100, 2, LAYOUT_STEREO); + map.info.has_audio = true; + map.Open(); + + // Loop through samples, and verify FrameMapper didn't mess up individual sample values + sample_value = 0; + for (int frame_index = 1; frame_index <= map.info.fps.ToInt(); frame_index++) { + for (int sample_index = 0; sample_index < map.GetFrame(frame_index)->GetAudioSamplesCount(); sample_index++) { + // Verify each mapped sample value is correct (after being redistributed by the FrameMapper) + CHECK_EQUAL(sample_value + sample_index, map.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0)); + } + // Increment sample value + sample_value += map.GetFrame(frame_index)->GetAudioSamplesCount(); + } + + // Verify samples per second is correct (i.e. 44100) + CHECK_EQUAL(sample_value, map.info.sample_rate); + + // Create Timeline (same specs as reader) + Timeline t1(map.info.width, map.info.height, map.info.fps, 44100, map.info.channels, map.info.channel_layout); + + Clip c1; + c1.Reader(&map); + c1.Layer(1); + c1.Position(0.0); + c1.Start(0.0); + c1.End(10.0); + + // Create 2nd map to 24 fps, which should create a variable # of samples per frame + FrameMapper map2(&r, Fraction(fps, 1), PULLDOWN_NONE, 44100, 2, LAYOUT_STEREO); + map2.info.has_audio = true; + map2.Open(); + + Clip c2; + c2.Reader(&map2); + c2.Layer(1); + // Position 1 frame into the video, this should mis-align the audio and create situations + // which overlapping Frame instances have different # of samples for the Timeline. + c2.Position(map2.info.video_timebase.ToFloat()); + c2.Start(0.0); + c2.End(10.0); + + // Add clips + t1.AddClip(&c1); + t1.AddClip(&c2); + t1.Open(); + + // Loop through samples, and verify Timeline didn't mess up individual sample values + int previous_sample_value = 0; + for (int frame_index = 2; frame_index < 24; frame_index++) { + t1.GetFrame(frame_index); + for (int sample_index = 0; sample_index < t1.GetFrame(frame_index)->GetAudioSamplesCount(); sample_index++) { + int sample_diff = t1.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0) - previous_sample_value; + if (previous_sample_value == 0) { + sample_diff = 2; + } + + // Check if sample_value - previous_value == 2 + // This should be true, because the DummyReader is added twice to the Timeline, and is overlapping + // This should be an ever increasing linear curve, increasing by 2 each sample on the Timeline + CHECK_EQUAL(2, sample_diff); + + // Set previous sample value + previous_sample_value = t1.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0); + } + } + + // Close mapper + map.Close(); + map2.Close(); + t1.Close(); + } + + // Clean up + cache.Clear(); + r.Close(); +} + +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/cppunittest/Frame_Tests.cpp b/tests/cppunittest/Frame_Tests.cpp new file mode 100644 index 000000000..9038f8b85 --- /dev/null +++ b/tests/cppunittest/Frame_Tests.cpp @@ -0,0 +1,183 @@ +/** + * @file + * @brief Unit tests for openshot::Frame + * @author Jonathan Thomas + * @author FeRD (Frank Dana) + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Frame.h" +#include "Clip.h" +#include "Fraction.h" + +#include + +#ifdef USE_OPENCV +#include +#endif + +using namespace openshot; + +SUITE(Frame_Tests) +{ + +TEST(Default_Constructor) +{ + // Create a "blank" default Frame + std::shared_ptr f1(new Frame()); + + CHECK(f1 != nullptr); // Test aborts here if we didn't get a Frame + + // Check basic default parameters + CHECK_EQUAL(1, f1->GetHeight()); + CHECK_EQUAL(1, f1->GetWidth()); + CHECK_EQUAL(44100, f1->SampleRate()); + CHECK_EQUAL(2, f1->GetAudioChannelsCount()); + + // Should be false until we load or create contents + CHECK_EQUAL(false, f1->has_image_data); + CHECK_EQUAL(false, f1->has_audio_data); + + // Calling GetImage() paints a blank frame, by default + std::shared_ptr i1 = f1->GetImage(); + + CHECK(i1 != nullptr); + + CHECK_EQUAL(true,f1->has_image_data); + CHECK_EQUAL(false,f1->has_audio_data); +} + + +TEST(Data_Access) +{ + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + Clip c1(path.str()); + c1.Open(); + + // Get first frame + std::shared_ptr f1 = c1.GetFrame(1); + + CHECK(f1 != nullptr); + + CHECK_EQUAL(1, f1->number); + CHECK_EQUAL(1280, f1->GetWidth()); + CHECK_EQUAL(720, f1->GetHeight()); +} + + +TEST(AddImage_QImage) +{ + // Create a "blank" default Frame + std::shared_ptr f1(new Frame()); + + // Load an image + std::stringstream path; + path << TEST_MEDIA_PATH << "front.png"; + std::shared_ptr i1(new QImage(QString::fromStdString(path.str()))) ; + + CHECK(f1 != nullptr); // Test aborts here if we didn't get a Frame + CHECK_EQUAL(false, i1->isNull()); + + f1->AddImage(i1); + + // Check loaded image parameters + CHECK_EQUAL(i1->height(), f1->GetHeight()); + CHECK_EQUAL(i1->width(), f1->GetWidth()); + CHECK_EQUAL(true, f1->has_image_data); +} + + +TEST(Copy_Constructor) +{ + // Create a dummy Frame + openshot::Frame f1(1, 800, 600, "#000000"); + + // Load an image + std::stringstream path; + path << TEST_MEDIA_PATH << "front.png"; + std::shared_ptr i1( new QImage(QString::fromStdString(path.str())) ); + + CHECK_EQUAL(false, i1->isNull()); + + // Add image to f1, then copy f1 to f2 + f1.AddImage(i1); + + Frame f2 = f1; + + CHECK_EQUAL(f1.GetHeight(), f2.GetHeight()); + CHECK_EQUAL(f1.GetWidth(), f2.GetWidth()); + + CHECK_EQUAL(f1.has_image_data, f2.has_image_data); + CHECK_EQUAL(f1.has_audio_data, f2.has_audio_data); + + Fraction par1 = f1.GetPixelRatio(); + Fraction par2 = f2.GetPixelRatio(); + + CHECK_EQUAL(par1.num, par2.num); + CHECK_EQUAL(par1.den, par2.den); + + + CHECK_EQUAL(f1.SampleRate(), f2.SampleRate()); + CHECK_EQUAL(f1.GetAudioChannelsCount(), f2.GetAudioChannelsCount()); + CHECK_EQUAL(f1.ChannelsLayout(), f2.ChannelsLayout()); + + CHECK_EQUAL(f1.GetBytes(), f2.GetBytes()); + CHECK_EQUAL(f1.GetAudioSamplesCount(), f2.GetAudioSamplesCount()); +} + +#ifdef USE_OPENCV +TEST(Convert_Image) +{ + // Create a video clip + std::stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + Clip c1(path.str()); + c1.Open(); + + // Get first frame + auto f1 = c1.GetFrame(1); + + // Get first Mat image + cv::Mat cvimage = f1->GetImageCV(); + + CHECK(!cvimage.empty()); + + CHECK_EQUAL(1, f1->number); + CHECK_EQUAL(f1->GetWidth(), cvimage.cols); + CHECK_EQUAL(f1->GetHeight(), cvimage.rows); + CHECK_EQUAL(3, cvimage.channels()); +} +#endif + +} // SUITE(Frame_Tests) diff --git a/tests/cppunittest/ImageWriter_Tests.cpp b/tests/cppunittest/ImageWriter_Tests.cpp new file mode 100644 index 000000000..c4afaee00 --- /dev/null +++ b/tests/cppunittest/ImageWriter_Tests.cpp @@ -0,0 +1,122 @@ +/** + * @file + * @brief Unit tests for openshot::ImageWriter + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 + +#ifdef USE_IMAGEMAGICK +#include "ImageWriter.h" +#include "Exceptions.h" +#include "ImageReader.h" +#include "FFmpegReader.h" +#include "Frame.h" + +using namespace std; +using namespace openshot; + +SUITE(ImageWriter) +{ + +TEST(Gif) +{ + // Reader --------------- + + // Bad path + FFmpegReader bad_r("/tmp/bleeblorp.xls", false); + CHECK_THROW(bad_r.Open(), InvalidFile); + + // Good path + stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + FFmpegReader r(path.str()); + + // Read-before-open error + CHECK_THROW(r.GetFrame(1), ReaderClosed); + + r.Open(); + + /* WRITER ---------------- */ + ImageWriter w("output1.gif"); + + CHECK_EQUAL(false, w.IsOpen()); + + // Check for exception on write-before-open + CHECK_THROW(w.WriteFrame(&r, 500, 504), WriterClosed); + + // Set the image output settings (format, fps, width, height, quality, loops, combine) + w.SetVideoOptions("GIF", r.info.fps, r.info.width, r.info.height, 70, 1, true); + + // Open writer + w.Open(); + + // Write some frames (start on frame 500 and go to frame 510) + w.WriteFrame(&r, 500, 504); + + // Close writer & reader + w.Close(); + r.Close(); + + // Open up the 5th frame from the newly created GIF + ImageReader r1("output1.gif[4]"); + + // Basic Reader state queries + CHECK_EQUAL("ImageReader", r1.Name()); + + CacheBase* c = r1.GetCache(); + CHECK_EQUAL(true, c == nullptr); + + CHECK_EQUAL(false, r1.IsOpen()); + r1.Open(); + CHECK_EQUAL(true, r1.IsOpen()); + + // Verify various settings + CHECK_EQUAL(r.info.width, r1.info.width); + CHECK_EQUAL(r.info.height, r1.info.height); + + // Get a specific frame + std::shared_ptr f = r1.GetFrame(8); + + // Get the image data for row 500 + const unsigned char* pixels = f->GetPixels(500); + int pixel_index = 230 * 4; // pixel 230 (4 bytes per pixel) + + // Check image properties + CHECK_CLOSE(20, (int)pixels[pixel_index], 5); + CHECK_CLOSE(18, (int)pixels[pixel_index + 1], 5); + CHECK_CLOSE(11, (int)pixels[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)pixels[pixel_index + 3], 5); +} + +} // SUITE +#endif diff --git a/tests/cppunittest/KeyFrame_Tests.cpp b/tests/cppunittest/KeyFrame_Tests.cpp new file mode 100644 index 000000000..f4718dccb --- /dev/null +++ b/tests/cppunittest/KeyFrame_Tests.cpp @@ -0,0 +1,513 @@ +/** + * @file + * @brief Unit tests for openshot::Keyframe + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "KeyFrame.h" +#include "Exceptions.h" +#include "Coordinate.h" +#include "Fraction.h" +#include "Point.h" + +using namespace std; +using namespace openshot; + +SUITE(Keyframe) { + +TEST(GetPoint_With_No_Points) +{ + // Create an empty keyframe + Keyframe k1; + + CHECK_THROW(k1.GetPoint(0), OutOfBoundsPoint); +} + +TEST(GetPoint_With_1_Points) +{ + // Create an empty keyframe + Keyframe k1; + k1.AddPoint(openshot::Point(2,3)); + + CHECK_THROW(k1.GetPoint(-1), OutOfBoundsPoint); + CHECK_EQUAL(1, k1.GetCount()); + CHECK_CLOSE(2.0f, k1.GetPoint(0).co.X, 0.00001); + CHECK_CLOSE(3.0f, k1.GetPoint(0).co.Y, 0.00001); + CHECK_THROW(k1.GetPoint(1), OutOfBoundsPoint); +} + + +TEST(AddPoint_With_1_Point) +{ + // Create an empty keyframe + Keyframe k1; + k1.AddPoint(openshot::Point(2,9)); + + CHECK_CLOSE(2.0f, k1.GetPoint(0).co.X, 0.00001); + CHECK_THROW(k1.GetPoint(-1), OutOfBoundsPoint); + CHECK_THROW(k1.GetPoint(1), OutOfBoundsPoint); +} + +TEST(AddPoint_With_2_Points) +{ + // Create an empty keyframe + Keyframe k1; + k1.AddPoint(openshot::Point(2,9)); + k1.AddPoint(openshot::Point(5,20)); + + CHECK_CLOSE(2.0f, k1.GetPoint(0).co.X, 0.00001); + CHECK_CLOSE(5.0f, k1.GetPoint(1).co.X, 0.00001); + CHECK_THROW(k1.GetPoint(-1), OutOfBoundsPoint); + CHECK_THROW(k1.GetPoint(2), OutOfBoundsPoint); +} + +TEST(GetValue_For_Bezier_Curve_2_Points) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(50, 4), BEZIER)); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(-1), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(1.12414f, kf.GetValue(9), 0.0001); + CHECK_CLOSE(1.86370f, kf.GetValue(20), 0.0001); + CHECK_CLOSE(3.79733f, kf.GetValue(40), 0.0001); + CHECK_CLOSE(4.0f, kf.GetValue(50), 0.0001); + // Check the expected number of values + CHECK_EQUAL(51, kf.GetLength()); +} + +TEST(GetValue_For_Bezier_Curve_5_Points_40_Percent_Handle) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(50, 4), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(100, 10), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(150, 0), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(200, 3), BEZIER)); + + // Spot check values from the curve + CHECK_CLOSE(kf.GetValue(-1), 1.0f, 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(2.68197f, kf.GetValue(27), 0.0001); + CHECK_CLOSE(7.47719f, kf.GetValue(77), 0.0001); + CHECK_CLOSE(4.20468f, kf.GetValue(127), 0.0001); + CHECK_CLOSE(1.73860f, kf.GetValue(177), 0.0001); + CHECK_CLOSE(3.0f, kf.GetValue(200), 0.0001); + // Check the expected number of values + CHECK_EQUAL(201, kf.GetLength()); +} + +TEST(GetValue_For_Bezier_Curve_5_Points_25_Percent_Handle) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(50, 4), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(100, 10), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(150, 0), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(200, 3), BEZIER)); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(-1), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(2.68197f, kf.GetValue(27), 0.0001); + CHECK_CLOSE(7.47719f, kf.GetValue(77), 0.0001); + CHECK_CLOSE(4.20468f, kf.GetValue(127), 0.0001); + CHECK_CLOSE(1.73860f, kf.GetValue(177), 0.0001); + CHECK_CLOSE(3.0f, kf.GetValue(200), 0.0001); + // Check the expected number of values + CHECK_EQUAL(201, kf.GetLength()); +} + +TEST(GetValue_For_Linear_Curve_3_Points) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), LINEAR)); + kf.AddPoint(openshot::Point(Coordinate(25, 8), LINEAR)); + kf.AddPoint(openshot::Point(Coordinate(50, 2), LINEAR)); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(-1), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(3.33333f, kf.GetValue(9), 0.0001); + CHECK_CLOSE(6.54167f, kf.GetValue(20), 0.0001); + CHECK_CLOSE(4.4f, kf.GetValue(40), 0.0001); + CHECK_CLOSE(2.0f, kf.GetValue(50), 0.0001); + // Check the expected number of values + CHECK_EQUAL(51, kf.GetLength()); +} + +TEST(GetValue_For_Constant_Curve_3_Points) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), CONSTANT)); + kf.AddPoint(openshot::Point(Coordinate(25, 8), CONSTANT)); + kf.AddPoint(openshot::Point(Coordinate(50, 2), CONSTANT)); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(-1), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(0), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.0001); + CHECK_CLOSE(1.0f, kf.GetValue(24), 0.0001); + CHECK_CLOSE(8.0f, kf.GetValue(25), 0.0001); + CHECK_CLOSE(8.0f, kf.GetValue(40), 0.0001); + CHECK_CLOSE(8.0f, kf.GetValue(49), 0.0001); + CHECK_CLOSE(2.0f, kf.GetValue(50), 0.0001); + // Check the expected number of values + CHECK_EQUAL(51, kf.GetLength()); +} + +TEST(Check_Direction_and_Repeat_Fractions) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(1, 500); + kf.AddPoint(400, 100); + kf.AddPoint(500, 500); + + // Spot check values from the curve + CHECK_EQUAL(500, kf.GetInt(1)); + CHECK_EQUAL(false, kf.IsIncreasing(1)); + CHECK_EQUAL(1, kf.GetRepeatFraction(1).num); + CHECK_EQUAL(13, kf.GetRepeatFraction(1).den); + CHECK_EQUAL(500, kf.GetDelta(1)); + + CHECK_EQUAL(498, kf.GetInt(24)); + CHECK_EQUAL(false, kf.IsIncreasing(24)); + CHECK_EQUAL(3, kf.GetRepeatFraction(24).num); + CHECK_EQUAL(6, kf.GetRepeatFraction(24).den); + CHECK_EQUAL(0, kf.GetDelta(24)); + + CHECK_EQUAL(100, kf.GetLong(390)); + CHECK_EQUAL(true, kf.IsIncreasing(390)); + CHECK_EQUAL(3, kf.GetRepeatFraction(390).num); + CHECK_EQUAL(16, kf.GetRepeatFraction(390).den); + CHECK_EQUAL(0, kf.GetDelta(390)); + + CHECK_EQUAL(100, kf.GetLong(391)); + CHECK_EQUAL(true, kf.IsIncreasing(391)); + CHECK_EQUAL(4, kf.GetRepeatFraction(391).num); + CHECK_EQUAL(16, kf.GetRepeatFraction(391).den); + CHECK_EQUAL(-1, kf.GetDelta(388)); +} + + +TEST(Get_Closest_Point) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(1, 0.0); + kf.AddPoint(1000, 1.0); + kf.AddPoint(2500, 0.0); + + // Spot check values from the curve (to the right) + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(900, 900)).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(1, 1)).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(5, 5)).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(1000, 1000)).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(1001, 1001)).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(2500, 2500)).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(3000, 3000)).co.X); + + // Spot check values from the curve (to the left) + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(900, 900), true).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(1, 1), true).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(5, 5), true).co.X); + CHECK_EQUAL(1, kf.GetClosestPoint(openshot::Point(1000, 1000), true).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(1001, 1001), true).co.X); + CHECK_EQUAL(1000, kf.GetClosestPoint(openshot::Point(2500, 2500), true).co.X); + CHECK_EQUAL(2500, kf.GetClosestPoint(openshot::Point(3000, 3000), true).co.X); +} + + +TEST(Get_Previous_Point) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(1, 0.0); + kf.AddPoint(1000, 1.0); + kf.AddPoint(2500, 0.0); + + // Spot check values from the curve + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(900, 900))).co.X); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1, 1))).co.X); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(5, 5))).co.X); + CHECK_EQUAL(1, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1000, 1000))).co.X); + CHECK_EQUAL(1000, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(1001, 1001))).co.X); + CHECK_EQUAL(1000, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(2500, 2500))).co.X); + CHECK_EQUAL(1000, kf.GetPreviousPoint(kf.GetClosestPoint(openshot::Point(3000, 3000))).co.X); + +} + +TEST(Get_Max_Point) +{ + // Create a keyframe curve + Keyframe kf; + kf.AddPoint(1, 1.0); + + // Spot check values from the curve + CHECK_EQUAL(1.0, kf.GetMaxPoint().co.Y); + + kf.AddPoint(2, 0.0); + + // Spot check values from the curve + CHECK_EQUAL(1.0, kf.GetMaxPoint().co.Y); + + kf.AddPoint(3, 2.0); + + // Spot check values from the curve + CHECK_EQUAL(2.0, kf.GetMaxPoint().co.Y); + + kf.AddPoint(4, 1.0); + + // Spot check values from the curve + CHECK_EQUAL(2.0, kf.GetMaxPoint().co.Y); +} + +TEST(Scale_Keyframe) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(25, 8), BEZIER)); + kf.AddPoint(openshot::Point(Coordinate(50, 2), BEZIER)); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); + CHECK_CLOSE(7.99f, kf.GetValue(24), 0.01); + CHECK_CLOSE(8.0f, kf.GetValue(25), 0.01); + CHECK_CLOSE(3.85f, kf.GetValue(40), 0.01); + CHECK_CLOSE(2.01f, kf.GetValue(49), 0.01); + CHECK_CLOSE(2.0f, kf.GetValue(50), 0.01); + + // Resize / Scale the keyframe + kf.ScalePoints(2.0); // 100% larger + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); + CHECK_CLOSE(4.08f, kf.GetValue(24), 0.01); + CHECK_CLOSE(4.36f, kf.GetValue(25), 0.01); + CHECK_CLOSE(7.53f, kf.GetValue(40), 0.01); + CHECK_CLOSE(7.99f, kf.GetValue(49), 0.01); + CHECK_CLOSE(8.0f, kf.GetValue(50), 0.01); + CHECK_CLOSE(2.39f, kf.GetValue(90), 0.01); + CHECK_CLOSE(2.0f, kf.GetValue(100), 0.01); + + // Resize / Scale the keyframe + kf.ScalePoints(0.5); // 50% smaller, which should match the original size + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); + CHECK_CLOSE(7.99f, kf.GetValue(24), 0.01); + CHECK_CLOSE(8.0f, kf.GetValue(25), 0.01); + CHECK_CLOSE(3.85f, kf.GetValue(40), 0.01); + CHECK_CLOSE(2.01f, kf.GetValue(49), 0.01); + CHECK_CLOSE(2.0f, kf.GetValue(50), 0.01); + +} + +TEST(Flip_Keyframe) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), LINEAR)); + kf.AddPoint(openshot::Point(Coordinate(25, 8), LINEAR)); + kf.AddPoint(openshot::Point(Coordinate(50, 2), LINEAR)); + kf.AddPoint(openshot::Point(Coordinate(100, 10), LINEAR)); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); + CHECK_CLOSE(8.0f, kf.GetValue(25), 0.01); + CHECK_CLOSE(2.0f, kf.GetValue(50), 0.01); + CHECK_CLOSE(10.0f, kf.GetValue(100), 0.01); + + // Flip the points + kf.FlipPoints(); + + // Spot check values from the curve + CHECK_CLOSE(10.0f, kf.GetValue(1), 0.01); + CHECK_CLOSE(2.0f, kf.GetValue(25), 0.01); + CHECK_CLOSE(8.0f, kf.GetValue(50), 0.01); + CHECK_CLOSE(1.0f, kf.GetValue(100), 0.01); + + // Flip the points again (back to the original) + kf.FlipPoints(); + + // Spot check values from the curve + CHECK_CLOSE(1.0f, kf.GetValue(1), 0.01); + CHECK_CLOSE(8.0f, kf.GetValue(25), 0.01); + CHECK_CLOSE(2.0f, kf.GetValue(50), 0.01); + CHECK_CLOSE(10.0f, kf.GetValue(100), 0.01); +} + +TEST(Remove_Duplicate_Point) +{ + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(1, 0.0); + kf.AddPoint(1, 1.0); + kf.AddPoint(1, 2.0); + + // Spot check values from the curve + CHECK_EQUAL(1, kf.GetLength()); + CHECK_CLOSE(2.0, kf.GetPoint(0).co.Y, 0.01); +} + +TEST(Large_Number_Values) +{ + // Large value + int64_t const large_value = 30 * 60 * 90; + + // Create a keyframe curve with 2 points + Keyframe kf; + kf.AddPoint(1, 1.0); + kf.AddPoint(large_value, 100.0); // 90 minutes long + + // Spot check values from the curve + CHECK_EQUAL(large_value + 1, kf.GetLength()); + CHECK_CLOSE(1.0, kf.GetPoint(0).co.Y, 0.01); + CHECK_CLOSE(100.0, kf.GetPoint(1).co.Y, 0.01); +} + +TEST(Remove_Point) +{ + Keyframe kf; + kf.AddPoint(openshot::Point(Coordinate(1, 1), CONSTANT)); + kf.AddPoint(openshot::Point(Coordinate(3, 100), CONSTANT)); + CHECK_EQUAL(1, kf.GetInt(2)); + kf.AddPoint(openshot::Point(Coordinate(2, 50), CONSTANT)); + CHECK_EQUAL(50, kf.GetInt(2)); + kf.RemovePoint(1); // This is the index of point with X == 2 + CHECK_EQUAL(1, kf.GetInt(2)); + CHECK_THROW(kf.RemovePoint(100), OutOfBoundsPoint); +} + +TEST(Constant_Interpolation_First_Segment) +{ + Keyframe kf; + kf.AddPoint(Point(Coordinate(1, 1), CONSTANT)); + kf.AddPoint(Point(Coordinate(2, 50), CONSTANT)); + kf.AddPoint(Point(Coordinate(3, 100), CONSTANT)); + CHECK_EQUAL(1, kf.GetInt(0)); + CHECK_EQUAL(1, kf.GetInt(1)); + CHECK_EQUAL(50, kf.GetInt(2)); + CHECK_EQUAL(100, kf.GetInt(3)); + CHECK_EQUAL(100, kf.GetInt(4)); +} + +TEST(isIncreasing) +{ + // Which cases need to be tested to keep same behaviour as + // previously? + // + // - "invalid point" => true + // - point where all next values are equal => false + // - point where first non-eq next value is smaller => false + // - point where first non-eq next value is larger => true + Keyframe kf; + kf.AddPoint(1, 1, LINEAR); // testing with linear + kf.AddPoint(3, 5, BEZIER); // testing with bezier + kf.AddPoint(6, 10, CONSTANT); // first non-eq is smaller + kf.AddPoint(8, 8, CONSTANT); // first non-eq is larger + kf.AddPoint(10, 10, CONSTANT); // all next values are equal + kf.AddPoint(15, 10, CONSTANT); + + // "invalid points" + CHECK_EQUAL(true, kf.IsIncreasing(0)); + CHECK_EQUAL(true, kf.IsIncreasing(15)); + // all next equal + CHECK_EQUAL(false, kf.IsIncreasing(12)); + // first non-eq is larger + CHECK_EQUAL(true, kf.IsIncreasing(8)); + // first non-eq is smaller + CHECK_EQUAL(false, kf.IsIncreasing(6)); + // bezier and linear + CHECK_EQUAL(true, kf.IsIncreasing(4)); + CHECK_EQUAL(true, kf.IsIncreasing(2)); +} + +TEST(GetLength) +{ + Keyframe f; + CHECK_EQUAL(0, f.GetLength()); + f.AddPoint(1, 1); + CHECK_EQUAL(1, f.GetLength()); + f.AddPoint(2, 1); + CHECK_EQUAL(3, f.GetLength()); + f.AddPoint(200, 1); + CHECK_EQUAL(201, f.GetLength()); + + Keyframe g; + g.AddPoint(200, 1); + CHECK_EQUAL(1, g.GetLength()); + g.AddPoint(1,1); + CHECK_EQUAL(201, g.GetLength()); +} + +TEST(Use_Interpolation_of_Segment_End_Point) +{ + Keyframe f; + f.AddPoint(1,0, CONSTANT); + f.AddPoint(100,155, BEZIER); + CHECK_CLOSE(75.9, f.GetValue(50), 0.1); +} + +TEST(Handle_Large_Segment) +{ + Keyframe kf; + kf.AddPoint(1, 0, CONSTANT); + kf.AddPoint(1000000, 1, LINEAR); + UNITTEST_TIME_CONSTRAINT(10); // 10 milliseconds would still be relatively slow, but need to think about slower build machines! + CHECK_CLOSE(0.5, kf.GetValue(500000), 0.01); + CHECK_EQUAL(true, kf.IsIncreasing(10)); + Fraction fr = kf.GetRepeatFraction(250000); + CHECK_CLOSE(0.5, (double)fr.num / fr.den, 0.01); +} + +TEST(Point_Vector_Constructor) +{ + std::vector points{Point(1, 10), Point(5, 20), Point(10, 30)}; + Keyframe k1(points); + + CHECK_EQUAL(11, k1.GetLength()); + CHECK_CLOSE(30.0f, k1.GetValue(10), 0.0001); +} + +}; // SUITE diff --git a/tests/cppunittest/Point_Tests.cpp b/tests/cppunittest/Point_Tests.cpp new file mode 100644 index 000000000..39012845f --- /dev/null +++ b/tests/cppunittest/Point_Tests.cpp @@ -0,0 +1,186 @@ +/** + * @file + * @brief Unit tests for openshot::Point + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Point.h" +#include "Enums.h" +#include "Coordinate.h" +#include "Json.h" + +SUITE(POINT) { + +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(openshot::InterpolationType::BEZIER, p1.interpolation); +} + +TEST(Pair_Constructor) +{ + // Create a point from a std::pair + std::pair coordinates(22, 5); + openshot::Point p1(coordinates); + + CHECK_CLOSE(22.0f, p1.co.X, 0.00001); + CHECK_CLOSE(5.0f, p1.co.Y, 0.00001); +} + +TEST(Constructor_With_Coordinate) +{ + // Create a point with a coordinate + openshot::Coordinate c1(3,7); + openshot::Point p1(c1); + + CHECK_CLOSE(3.0f, p1.co.X, 0.00001); + CHECK_CLOSE(7.0f, p1.co.Y, 0.00001); + CHECK_EQUAL(openshot::InterpolationType::BEZIER, p1.interpolation); +} + +TEST(Constructor_With_Coordinate_And_LINEAR_Interpolation) +{ + // Create a point with a coordinate and interpolation + 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(openshot::InterpolationType::LINEAR, p1.interpolation); +} + +TEST(Constructor_With_Coordinate_And_BEZIER_Interpolation) +{ + // Create a point with a coordinate and interpolation + 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(openshot::InterpolationType::BEZIER, p1.interpolation); +} + +TEST(Constructor_With_Coordinate_And_CONSTANT_Interpolation) +{ + // Create a point with a coordinate and interpolation + 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(openshot::InterpolationType::CONSTANT, p1.interpolation); +} + +TEST(Constructor_With_Coordinate_And_BEZIER_And_AUTO_Handle) +{ + // Create a point with a coordinate and interpolation + 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(openshot::InterpolationType::BEZIER, p1.interpolation); + CHECK_EQUAL(openshot::HandleType::AUTO, p1.handle_type); +} + +TEST(Constructor_With_Coordinate_And_BEZIER_And_MANUAL_Handle) +{ + // Create a point with a coordinate and interpolation + 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(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/cppunittest/QtImageReader_Tests.cpp b/tests/cppunittest/QtImageReader_Tests.cpp new file mode 100644 index 000000000..2fd78d5e4 --- /dev/null +++ b/tests/cppunittest/QtImageReader_Tests.cpp @@ -0,0 +1,107 @@ +/** + * @file + * @brief Unit tests for openshot::QtImageReader + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "QGuiApplication" +#include "OpenShot.h" + +using namespace std; +using namespace openshot; + +SUITE(QtImageReader) +{ + +TEST(Default_Constructor) +{ + // Check invalid path + CHECK_THROW(QtImageReader(""), InvalidFile); +} + +TEST(GetFrame_Before_Opening) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "front.png"; + QtImageReader r(path.str()); + + // Check invalid path + CHECK_THROW(r.GetFrame(1), ReaderClosed); +} + +TEST(Check_SVG_Loading) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "1F0CF.svg"; + QtImageReader r(path.str()); + r.Open(); + + // Get frame, with no Timeline or Clip + // Default SVG scaling sizes things to 1920x1080 + std::shared_ptr f = r.GetFrame(1); + CHECK_EQUAL(1080, f->GetImage()->width()); + CHECK_EQUAL(1080, f->GetImage()->height()); + + Fraction fps(30000,1000); + Timeline t1(640, 480, fps, 44100, 2, LAYOUT_STEREO); + + Clip clip1(path.str()); + clip1.Layer(1); + clip1.Position(0.0); // Delay the overlay by 0.05 seconds + clip1.End(10.0); // Make the duration of the overlay 1/2 second + + // Add clips + t1.AddClip(&clip1); + t1.Open(); + + // Get frame, with 640x480 Timeline + // Should scale to 480 + clip1.Reader()->Open(); + f = clip1.Reader()->GetFrame(2); + CHECK_EQUAL(480, f->GetImage()->width()); + CHECK_EQUAL(480, f->GetImage()->height()); + + // Add scale_x and scale_y. Should scale the square SVG + // by the largest scale keyframe (i.e. 4) + clip1.scale_x.AddPoint(1.0, 2.0, openshot::LINEAR); + clip1.scale_y.AddPoint(1.0, 2.0, openshot::LINEAR); + f = clip1.Reader()->GetFrame(3); + CHECK_EQUAL(480 * 2, f->GetImage()->width()); + CHECK_EQUAL(480 * 2, f->GetImage()->height()); + + // Close reader + t1.Close(); + r.Close(); +} + +} // SUITE(QtImageReader) + diff --git a/tests/cppunittest/ReaderBase_Tests.cpp b/tests/cppunittest/ReaderBase_Tests.cpp new file mode 100644 index 000000000..dec61efd9 --- /dev/null +++ b/tests/cppunittest/ReaderBase_Tests.cpp @@ -0,0 +1,93 @@ +/** + * @file + * @brief Unit tests for openshot::ReaderBase + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "ReaderBase.h" +#include "CacheBase.h" +#include "Frame.h" +#include "Json.h" + +using namespace std; +using namespace openshot; + +// Since it is not possible to instantiate an abstract class, this test creates +// a new derived class, in order to test the base class file info struct. +TEST(ReaderBase_Derived_Class) +{ + // Create a new derived class from type ReaderBase + class TestReader : public ReaderBase + { + public: + TestReader() { }; + CacheBase* GetCache() { return NULL; }; + std::shared_ptr GetFrame(int64_t number) { std::shared_ptr f(new Frame()); return f; } + void Close() { }; + void Open() { }; + string Json() const { return ""; }; + void SetJson(string value) { }; + Json::Value JsonValue() const { return Json::Value("{}"); }; + void SetJsonValue(Json::Value root) { }; + bool IsOpen() { return true; }; + string Name() { return "TestReader"; }; + }; + + // Create an instance of the derived class + TestReader t1; + + // Validate the new class + CHECK_EQUAL("TestReader", t1.Name()); + + t1.Close(); + t1.Open(); + CHECK_EQUAL(true, t1.IsOpen()); + + CHECK_EQUAL(true, t1.GetCache() == NULL); + + t1.SetJson("{ }"); + t1.SetJsonValue(Json::Value("{}")); + CHECK_EQUAL("", t1.Json()); + auto json = t1.JsonValue(); + CHECK_EQUAL(json, Json::Value("{}")); + + auto f = t1.GetFrame(1); + + // Check some of the default values of the FileInfo struct on the base class + CHECK_EQUAL(false, t1.info.has_audio); + CHECK_EQUAL(false, t1.info.has_audio); + CHECK_CLOSE(0.0f, t1.info.duration, 0.00001); + CHECK_EQUAL(0, t1.info.height); + CHECK_EQUAL(0, t1.info.width); + CHECK_EQUAL(1, t1.info.fps.num); + CHECK_EQUAL(1, t1.info.fps.den); +} diff --git a/tests/cppunittest/Settings_Tests.cpp b/tests/cppunittest/Settings_Tests.cpp new file mode 100644 index 000000000..ce5dc2824 --- /dev/null +++ b/tests/cppunittest/Settings_Tests.cpp @@ -0,0 +1,60 @@ +/** + * @file + * @brief Unit tests for openshot::Color + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Settings.h" + +using namespace std; +using namespace openshot; + +TEST(Settings_Default_Constructor) +{ + // Create an empty color + Settings *s = Settings::Instance(); + + CHECK_EQUAL(12, s->OMP_THREADS); + CHECK_EQUAL(false, s->HIGH_QUALITY_SCALING); +} + +TEST(Settings_Change_Settings) +{ + // Create an empty color + Settings *s = Settings::Instance(); + s->OMP_THREADS = 8; + s->HIGH_QUALITY_SCALING = true; + + CHECK_EQUAL(8, s->OMP_THREADS); + CHECK_EQUAL(true, s->HIGH_QUALITY_SCALING); + + CHECK_EQUAL(8, Settings::Instance()->OMP_THREADS); + CHECK_EQUAL(true, Settings::Instance()->HIGH_QUALITY_SCALING); +} diff --git a/tests/cppunittest/Timeline_Tests.cpp b/tests/cppunittest/Timeline_Tests.cpp new file mode 100644 index 000000000..fecba1788 --- /dev/null +++ b/tests/cppunittest/Timeline_Tests.cpp @@ -0,0 +1,620 @@ +/** + * @file + * @brief Unit tests for openshot::Timeline + * @author Jonathan Thomas + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2019 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include +#include +#include + +#include "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 +#include "Timeline.h" +#include "Clip.h" +#include "Frame.h" +#include "Fraction.h" +#include "effects/Blur.h" +#include "effects/Negate.h" + +using namespace std; +using namespace openshot; + +SUITE(Timeline) +{ + +TEST(Constructor) +{ + // Create a default fraction (should be 1/1) + Fraction fps(30000,1000); + Timeline t1(640, 480, fps, 44100, 2, LAYOUT_STEREO); + + // Check values + CHECK_EQUAL(640, t1.info.width); + CHECK_EQUAL(480, t1.info.height); + CHECK_EQUAL("Timeline", t1.Name()); + + // Create a default fraction (should be 1/1) + Timeline t2(300, 240, fps, 44100, 2, LAYOUT_STEREO); + + // Check values + CHECK_EQUAL(300, t2.info.width); + CHECK_EQUAL(240, t2.info.height); +} + +TEST(Width_and_Height_Functions) +{ + // Create a default fraction (should be 1/1) + Fraction fps(30000,1000); + Timeline t1(640, 480, fps, 44100, 2, LAYOUT_STEREO); + + // Check values + CHECK_EQUAL(640, t1.info.width); + CHECK_EQUAL(480, t1.info.height); + + // Set width + t1.info.width = 600; + + // Check values + CHECK_EQUAL(600, t1.info.width); + CHECK_EQUAL(480, t1.info.height); + + // Set height + t1.info.height = 400; + + // Check values + CHECK_EQUAL(600, t1.info.width); + CHECK_EQUAL(400, t1.info.height); +} + +TEST(Framerate) +{ + // Create a default fraction (should be 1/1) + Fraction fps(24,1); + Timeline t1(640, 480, fps, 44100, 2, LAYOUT_STEREO); + + // Check values + CHECK_CLOSE(24.0f, t1.info.fps.ToFloat(), 0.00001); +} + +TEST(Check_Two_Track_Video) +{ + // Create a reader + stringstream path; + path << TEST_MEDIA_PATH << "test.mp4"; + Clip clip_video(path.str()); + clip_video.Layer(0); + clip_video.Position(0.0); + + stringstream path_overlay; + path_overlay << TEST_MEDIA_PATH << "front3.png"; + Clip clip_overlay(path_overlay.str()); + clip_overlay.Layer(1); + clip_overlay.Position(0.05); // Delay the overlay by 0.05 seconds + clip_overlay.End(0.5); // Make the duration of the overlay 1/2 second + + // Create a timeline + Timeline t(1280, 720, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + // Add clips + t.AddClip(&clip_video); + t.AddClip(&clip_overlay); + + // Open Timeline + t.Open(); + + // Get frame + std::shared_ptr f = t.GetFrame(1); + + // Get the image data + int pixel_row = 200; + int pixel_index = 230 * 4; // pixel 230 (4 bytes per pixel) + + // Check image properties + CHECK_CLOSE(21, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(191, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Get frame + f = t.GetFrame(2); + + // Check image properties + CHECK_CLOSE(176, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(186, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Get frame + f = t.GetFrame(3); + + // Check image properties + CHECK_CLOSE(23, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(190, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Get frame + f = t.GetFrame(24); + + // Check image properties + CHECK_CLOSE(186, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(106, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Get frame + f = t.GetFrame(5); + + // Check image properties + CHECK_CLOSE(23, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(190, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Get frame + f = t.GetFrame(25); + + // Check image properties + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(94, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(186, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Get frame + f = t.GetFrame(4); + + // Check image properties + CHECK_CLOSE(176, (int)f->GetPixels(pixel_row)[pixel_index], 5); + CHECK_CLOSE(0, (int)f->GetPixels(pixel_row)[pixel_index + 1], 5); + CHECK_CLOSE(186, (int)f->GetPixels(pixel_row)[pixel_index + 2], 5); + CHECK_CLOSE(255, (int)f->GetPixels(pixel_row)[pixel_index + 3], 5); + + // Close reader + t.Close(); +} + +TEST(Clip_Order) +{ + // Create a timeline + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + // Add some clips out of order + stringstream path_top; + path_top << TEST_MEDIA_PATH << "front3.png"; + Clip clip_top(path_top.str()); + clip_top.Layer(2); + t.AddClip(&clip_top); + + stringstream path_middle; + path_middle << TEST_MEDIA_PATH << "front.png"; + Clip clip_middle(path_middle.str()); + clip_middle.Layer(0); + t.AddClip(&clip_middle); + + stringstream path_bottom; + path_bottom << TEST_MEDIA_PATH << "back.png"; + Clip clip_bottom(path_bottom.str()); + clip_bottom.Layer(1); + t.AddClip(&clip_bottom); + + // Open Timeline + t.Open(); + + // Loop through Clips and check order (they should have been sorted into the correct order) + // Bottom layer to top layer, then by position. + list::iterator clip_itr; + list clips = t.Clips(); + int counter = 0; + for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) + { + // Get clip object from the iterator + Clip *clip = (*clip_itr); + + switch (counter) { + case 0: + CHECK_EQUAL(0, clip->Layer()); + break; + case 1: + CHECK_EQUAL(1, clip->Layer()); + break; + case 2: + CHECK_EQUAL(2, clip->Layer()); + break; + } + + // increment counter + counter++; + } + + // Add another clip + stringstream path_middle1; + path_middle1 << TEST_MEDIA_PATH << "interlaced.png"; + Clip clip_middle1(path_middle1.str()); + clip_middle1.Layer(1); + clip_middle1.Position(0.5); + t.AddClip(&clip_middle1); + + // Loop through clips again, and re-check order + counter = 0; + clips = t.Clips(); + for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) + { + // Get clip object from the iterator + Clip *clip = (*clip_itr); + + switch (counter) { + case 0: + CHECK_EQUAL(0, clip->Layer()); + break; + case 1: + CHECK_EQUAL(1, clip->Layer()); + CHECK_CLOSE(0.0, clip->Position(), 0.0001); + break; + case 2: + CHECK_EQUAL(1, clip->Layer()); + CHECK_CLOSE(0.5, clip->Position(), 0.0001); + break; + case 3: + CHECK_EQUAL(2, clip->Layer()); + break; + } + + // increment counter + counter++; + } + + // Close reader + t.Close(); +} + + +TEST(Effect_Order) +{ + // Create a timeline + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + // Add some effects out of order + Negate effect_top; + effect_top.Id("C"); + effect_top.Layer(2); + t.AddEffect(&effect_top); + + Negate effect_middle; + effect_middle.Id("A"); + effect_middle.Layer(0); + t.AddEffect(&effect_middle); + + Negate effect_bottom; + effect_bottom.Id("B"); + effect_bottom.Layer(1); + t.AddEffect(&effect_bottom); + + // Open Timeline + t.Open(); + + // Loop through effects and check order (they should have been sorted into the correct order) + // Bottom layer to top layer, then by position, and then by order. + list::iterator effect_itr; + list effects = t.Effects(); + int counter = 0; + for (effect_itr=effects.begin(); effect_itr != effects.end(); ++effect_itr) + { + // Get clip object from the iterator + EffectBase *effect = (*effect_itr); + + switch (counter) { + case 0: + CHECK_EQUAL(0, effect->Layer()); + CHECK_EQUAL("A", effect->Id()); + CHECK_EQUAL(0, effect->Order()); + break; + case 1: + CHECK_EQUAL(1, effect->Layer()); + CHECK_EQUAL("B", effect->Id()); + CHECK_EQUAL(0, effect->Order()); + break; + case 2: + CHECK_EQUAL(2, effect->Layer()); + CHECK_EQUAL("C", effect->Id()); + CHECK_EQUAL(0, effect->Order()); + break; + } + + // increment counter + counter++; + } + + // Add some more effects out of order + Negate effect_top1; + effect_top1.Id("B-2"); + effect_top1.Layer(1); + effect_top1.Position(0.5); + effect_top1.Order(2); + t.AddEffect(&effect_top1); + + Negate effect_middle1; + effect_middle1.Id("B-3"); + effect_middle1.Layer(1); + effect_middle1.Position(0.5); + effect_middle1.Order(1); + t.AddEffect(&effect_middle1); + + Negate effect_bottom1; + effect_bottom1.Id("B-1"); + effect_bottom1.Layer(1); + effect_bottom1.Position(0); + effect_bottom1.Order(3); + t.AddEffect(&effect_bottom1); + + + // Loop through effects again, and re-check order + effects = t.Effects(); + counter = 0; + for (effect_itr=effects.begin(); effect_itr != effects.end(); ++effect_itr) + { + // Get clip object from the iterator + EffectBase *effect = (*effect_itr); + + switch (counter) { + case 0: + CHECK_EQUAL(0, effect->Layer()); + CHECK_EQUAL("A", effect->Id()); + CHECK_EQUAL(0, effect->Order()); + break; + case 1: + CHECK_EQUAL(1, effect->Layer()); + CHECK_EQUAL("B-1", effect->Id()); + CHECK_CLOSE(0.0, effect->Position(), 0.0001); + CHECK_EQUAL(3, effect->Order()); + break; + case 2: + CHECK_EQUAL(1, effect->Layer()); + CHECK_EQUAL("B", effect->Id()); + CHECK_CLOSE(0.0, effect->Position(), 0.0001); + CHECK_EQUAL(0, effect->Order()); + break; + case 3: + CHECK_EQUAL(1, effect->Layer()); + CHECK_EQUAL("B-2", effect->Id()); + CHECK_CLOSE(0.5, effect->Position(), 0.0001); + CHECK_EQUAL(2, effect->Order()); + break; + case 4: + CHECK_EQUAL(1, effect->Layer()); + CHECK_EQUAL("B-3", effect->Id()); + CHECK_CLOSE(0.5, effect->Position(), 0.0001); + CHECK_EQUAL(1, effect->Order()); + break; + case 5: + CHECK_EQUAL(2, effect->Layer()); + CHECK_EQUAL("C", effect->Id()); + CHECK_EQUAL(0, effect->Order()); + break; + } + + // increment counter + counter++; + } + + // Close reader + t.Close(); +} + +TEST(GetClip_by_id) +{ + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + stringstream path1; + path1 << TEST_MEDIA_PATH << "interlaced.png"; + auto media_path1 = path1.str(); + + stringstream path2; + path2 << TEST_MEDIA_PATH << "front.png"; + auto media_path2 = path2.str(); + + Clip clip1(media_path1); + std::string clip1_id("CLIP00001"); + clip1.Id(clip1_id); + clip1.Layer(1); + + Clip clip2(media_path2); + std::string clip2_id("CLIP00002"); + clip2.Id(clip2_id); + clip2.Layer(2); + clip2.Waveform(true); + + t.AddClip(&clip1); + t.AddClip(&clip2); + + // 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()); + + Clip* matched2 = t.GetClip(clip2_id); + CHECK_EQUAL(clip2_id, matched2->Id()); + CHECK_EQUAL(false, matched2->Layer() < 2); + + 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) +{ + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + stringstream path1; + path1 << TEST_MEDIA_PATH << "interlaced.png"; + auto media_path1 = path1.str(); + + // Create a clip, nothing special + Clip clip1(media_path1); + std::string clip1_id("CLIP00001"); + clip1.Id(clip1_id); + clip1.Layer(1); + + // Add a blur effect + Keyframe horizontal_radius(5.0); + Keyframe vertical_radius(5.0); + Keyframe sigma(3.0); + Keyframe iterations(3.0); + Blur blur1(horizontal_radius, vertical_radius, sigma, iterations); + std::string blur1_id("EFFECT00011"); + blur1.Id(blur1_id); + clip1.AddEffect(&blur1); + + // A second clip, different layer + Clip clip2(media_path1); + std::string clip2_id("CLIP00002"); + clip2.Id(clip2_id); + clip2.Layer(2); + + // Some effects for clip2 + Negate neg2; + std::string neg2_id("EFFECT00021"); + neg2.Id(neg2_id); + neg2.Layer(2); + clip2.AddEffect(&neg2); + Blur blur2(horizontal_radius, vertical_radius, sigma, iterations); + std::string blur2_id("EFFECT00022"); + blur2.Id(blur2_id); + blur2.Layer(2); + clip2.AddEffect(&blur2); + + t.AddClip(&clip1); + + // Check that we can look up clip1's effect + auto match1 = t.GetClipEffect("EFFECT00011"); + CHECK_EQUAL(blur1_id, match1->Id()); + + // clip2 hasn't been added yet, shouldn't be found + match1 = t.GetClipEffect(blur2_id); + CHECK_EQUAL(true, match1 == nullptr); + + t.AddClip(&clip2); + + // Check that blur2 can now be found via clip2 + match1 = t.GetClipEffect(blur2_id); + CHECK_EQUAL(blur2_id, match1->Id()); + CHECK_EQUAL(2, match1->Layer()); +} + +TEST(GetEffect_by_id) +{ + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + // Create a timeline effect + Keyframe horizontal_radius(5.0); + Keyframe vertical_radius(5.0); + Keyframe sigma(3.0); + Keyframe iterations(3.0); + Blur blur1(horizontal_radius, vertical_radius, sigma, iterations); + std::string blur1_id("EFFECT00011"); + blur1.Id(blur1_id); + blur1.Layer(1); + t.AddEffect(&blur1); + + auto match1 = t.GetEffect(blur1_id); + CHECK_EQUAL(blur1_id, match1->Id()); + CHECK_EQUAL(1, match1->Layer()); + + match1 = t.GetEffect("NOSUCHNAME"); + CHECK_EQUAL(true, match1 == nullptr); +} + +TEST(Effect_Blur) +{ + // Create a timeline + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + stringstream path_top; + path_top << TEST_MEDIA_PATH << "interlaced.png"; + Clip clip_top(path_top.str()); + clip_top.Layer(2); + t.AddClip(&clip_top); + + // Add some effects out of order + Keyframe horizontal_radius(5.0); + Keyframe vertical_radius(5.0); + Keyframe sigma(3.0); + Keyframe iterations(3.0); + Blur blur(horizontal_radius, vertical_radius, sigma, iterations); + blur.Id("B"); + blur.Layer(2); + t.AddEffect(&blur); + + // Open Timeline + t.Open(); + + // Get frame + std::shared_ptr f = t.GetFrame(1); + + // Close reader + t.Close(); +} + +TEST(GetMaxFrame_GetMaxTime) +{ + // Create a timeline + Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + + stringstream path1; + path1 << TEST_MEDIA_PATH << "interlaced.png"; + Clip clip1(path1.str()); + clip1.Layer(1); + clip1.Position(50); + clip1.End(45); + t.AddClip(&clip1); + + CHECK_CLOSE(95.0, t.GetMaxTime(), 0.001); + CHECK_EQUAL(95 * 30 + 1, t.GetMaxFrame()); + + Clip clip2(path1.str()); + clip2.Layer(2); + clip2.Position(0); + clip2.End(55); + t.AddClip(&clip2); + + CHECK_EQUAL(95 * 30 + 1, t.GetMaxFrame()); + CHECK_CLOSE(95.0, t.GetMaxTime(), 0.001); + + clip2.Position(100); + clip1.Position(80); + CHECK_EQUAL(155 * 30 + 1, t.GetMaxFrame()); + CHECK_CLOSE(155.0, t.GetMaxTime(), 0.001); + t.RemoveClip(&clip2); + CHECK_EQUAL(125 * 30 + 1, t.GetMaxFrame()); + CHECK_CLOSE(125.0, t.GetMaxTime(), 0.001); +} + +} // SUITE diff --git a/tests/tests.cpp b/tests/cppunittest/tests.cpp similarity index 100% rename from tests/tests.cpp rename to tests/cppunittest/tests.cpp