From 7831cfe9128502ae894d85c14e033991cd9a3d1b Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 6 Jun 2020 01:55:52 -0500 Subject: [PATCH 1/3] Adding some new functionality and documentation to DummyReader. Adding the ability to add test frames, with fake image and audio data. This will can be used in unittests, and will soon be used to verify some new audio improvements (coming soon). --- include/DummyReader.h | 50 +++++++++++++++++- src/DummyReader.cpp | 31 +++++++++-- src/Frame.cpp | 5 ++ tests/CMakeLists.txt | 1 + tests/DummyReader_Tests.cpp | 102 ++++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 tests/DummyReader_Tests.cpp diff --git a/include/DummyReader.h b/include/DummyReader.h index 4c935103c..576a0df85 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -46,14 +46,58 @@ namespace openshot { /** - * @brief This class is used as a simple, dummy reader, which always returns a blank frame. + * @brief This class is used as a simple, dummy reader, which can be very useful when writing + * unit tests. It can return a single blank frame or it can return custom frame objects + * which were added using the WriteFrame() method. * * A dummy reader can be created with any framerate or samplerate. This is useful in unit * tests that need to test different framerates or samplerates. + * + * @code + * // Create a reader (Fraction fps, int width, int height, int sample_rate, int channels, float duration) + * openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + * r.Open(); // Open the reader + * + * // Get a frame (which will be blank, since we haven't added any frames yet) + * std::shared_ptr f = r.GetFrame(1); + * + * // Now 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; + * 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++) + * { + * // 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, + * // int numSamples, float gainToApplyToSource = 1.0f) + * 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 + * + * // Write test frame to dummy reader + * r.WriteFrame(f); + * } + * + * // Now let's verify our DummyReader works + * std::shared_ptr f = r.GetFrame(1); + * // r.GetFrame(1)->GetAudioSamples(0)[1] should equal 1.00068033 based on our above calculations + * + * // Close the reader + * r.Close(); + * @endcode */ class DummyReader : public ReaderBase { private: + CacheMemory dummy_cache; std::shared_ptr image_frame; bool is_open; @@ -94,6 +138,10 @@ namespace openshot /// Open File - which is called by the constructor automatically void Open() override; + + /// @brief Add a frame to the dummy reader. This is useful when constructing unit tests that require custom frames. + /// @param frame The openshot::Frame object to write to this image + void WriteFrame(std::shared_ptr frame); }; } diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index 8fd98bcbc..1b663a07e 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -99,24 +99,49 @@ void DummyReader::Close() { // Mark as "closed" is_open = false; + + // Clear cache + dummy_cache.Clear(); + } +} + +// Add Frame objects to DummyReader +void DummyReader::WriteFrame(std::shared_ptr frame) +{ + if (frame) { + dummy_cache.Add(frame); } } -// Get an openshot::Frame object for a specific frame number of this reader. +// Get an openshot::Frame object for a specific frame number of this reader. It is either a blank frame +// or a custom frame added with the WriteFrame() method. std::shared_ptr DummyReader::GetFrame(int64_t requested_frame) { // Check for open reader (or throw exception) if (!is_open) throw ReaderClosed("The ImageReader is closed. Call Open() before calling this method.", "dummy"); - if (image_frame) - { + if (dummy_cache.Count() == 0 && image_frame) { // Create a scoped lock, allowing only a single thread to run the following code at one time const GenericScopedLock lock(getFrameCriticalSection); // Always return same frame (regardless of which frame number was requested) image_frame->number = requested_frame; return image_frame; + + } else if (dummy_cache.Count() > 0) { + // Create a scoped lock, allowing only a single thread to run the following code at one time + const GenericScopedLock lock(getFrameCriticalSection); + + // Get a frame from the dummy cache + std::shared_ptr f = dummy_cache.GetFrame(requested_frame); + if (f) { + // return frame from cache (if found) + return f; + } else { + // No cached frame found + throw InvalidFile("Requested frame not found. You can only access Frame numbers added with WriteFrame().", "dummy"); + } } else // no frame loaded diff --git a/src/Frame.cpp b/src/Frame.cpp index ae9f1a4be..764b9651a 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -480,6 +480,11 @@ const unsigned char* Frame::GetPixels() // Get pixel data (for only a single scan-line) const unsigned char* Frame::GetPixels(int row) { + // Check for blank image + if (!image) + // Fill with black + AddColor(width, height, color); + // Return array of pixel packets return image->constScanLine(row); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 480dfb3d3..7ccddba8d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -109,6 +109,7 @@ set(OPENSHOT_TEST_FILES Clip_Tests.cpp Color_Tests.cpp Coordinate_Tests.cpp + DummyReader_Tests.cpp ReaderBase_Tests.cpp ImageWriter_Tests.cpp FFmpegReader_Tests.cpp diff --git a/tests/DummyReader_Tests.cpp b/tests/DummyReader_Tests.cpp new file mode 100644 index 000000000..ea410ad0e --- /dev/null +++ b/tests/DummyReader_Tests.cpp @@ -0,0 +1,102 @@ +/** + * @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 "UnitTest++.h" +// Prevent name clashes with juce::UnitTest +#define DONT_SET_USING_JUCE_NAMESPACE 1 + +#include "../include/OpenShot.h" + +using namespace std; +using namespace openshot; + +TEST (DummyReader_Constructor) { + // Create a default fraction (should be 1/1) + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + r.Open(); // Open the reader + + // 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(44100, r.info.sample_rate); + CHECK_EQUAL(2, r.info.channels); + CHECK_EQUAL(30.0, r.info.duration); +} + +TEST (DummyReader_Blank_Frame) { + // Create a default fraction (should be 1/1) + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + r.Open(); // Open the reader + + // Get a blank frame (because we have not added any frames using WriteFrame() yet) + // 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 (DummyReader_Fake_Frame) { + // Create a default fraction (should be 1/1) + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + r.Open(); // Open the reader + + // 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; + 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++) { + // 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 + + // Write test frame to dummy reader + r.WriteFrame(f); + } + + // 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); +} \ No newline at end of file From d29027ae30466f3aeb6ec0b78cc28754d7630174 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 6 Jun 2020 02:03:27 -0500 Subject: [PATCH 2/3] Added an additional unittest for DummyReader (for invalid frame) --- tests/DummyReader_Tests.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/DummyReader_Tests.cpp b/tests/DummyReader_Tests.cpp index ea410ad0e..6ec11e08f 100644 --- a/tests/DummyReader_Tests.cpp +++ b/tests/DummyReader_Tests.cpp @@ -99,4 +99,23 @@ TEST (DummyReader_Fake_Frame) { 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); +} + +TEST (DummyReader_Invalid_Fake_Frame) { + // Create a default fraction (should be 1/1) + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + r.Open(); + + // Create fake frames (with specific frame #, samples, and channels) + std::shared_ptr f1(new openshot::Frame(1, 1470, 2)); + std::shared_ptr f2(new openshot::Frame(2, 1470, 2)); + + // Write test frames to dummy reader + r.WriteFrame(f1); + r.WriteFrame(f2); + + // Verify exception + CHECK_EQUAL(1, r.GetFrame(1)->number); + CHECK_EQUAL(2, r.GetFrame(2)->number); + CHECK_THROW(r.GetFrame(3)->number, InvalidFile); } \ No newline at end of file From 8b12c1fa2173db24246858e744118da544d7e320 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 6 Jun 2020 17:25:45 -0500 Subject: [PATCH 3/3] Replacing WriteFrame() method with custom constructor which can accept a CacheBase* pointer, for instances where a DummyReader needs some specific test Frame objects --- include/DummyReader.h | 33 ++++++++++--------- src/DummyReader.cpp | 64 ++++++++++++++++++++----------------- tests/DummyReader_Tests.cpp | 58 ++++++++++++++++++++++++--------- 3 files changed, 96 insertions(+), 59 deletions(-) diff --git a/include/DummyReader.h b/include/DummyReader.h index 576a0df85..e9c90968a 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -48,18 +48,14 @@ namespace openshot /** * @brief This class is used as a simple, dummy reader, which can be very useful when writing * unit tests. It can return a single blank frame or it can return custom frame objects - * which were added using the WriteFrame() method. + * which were passed into the constructor with a Cache object. * * A dummy reader can be created with any framerate or samplerate. This is useful in unit * tests that need to test different framerates or samplerates. * * @code - * // Create a reader (Fraction fps, int width, int height, int sample_rate, int channels, float duration) - * openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); - * r.Open(); // Open the reader - * - * // Get a frame (which will be blank, since we haven't added any frames yet) - * std::shared_ptr f = r.GetFrame(1); + * // Create cache object to store fake Frame objects + * CacheMemory cache; * * // Now let's create some test frames * for (int64_t frame_number = 1; frame_number <= 30; frame_number++) @@ -82,25 +78,33 @@ namespace openshot * 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 * - * // Write test frame to dummy reader - * r.WriteFrame(f); + * // Add test frame to cache + * cache.Add(f); * } * + * // Create a reader (Fraction fps, int width, int height, int sample_rate, int channels, float duration, CacheBase* cache) + * openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0, &cache); + * r.Open(); // Open the reader + * * // Now let's verify our DummyReader works * std::shared_ptr f = r.GetFrame(1); * // r.GetFrame(1)->GetAudioSamples(0)[1] should equal 1.00068033 based on our above calculations * - * // Close the reader + * // Clean up * r.Close(); + * cache.Clear() * @endcode */ class DummyReader : public ReaderBase { private: - CacheMemory dummy_cache; + CacheBase* dummy_cache; std::shared_ptr image_frame; bool is_open; + /// Initialize variables used by constructor + void init(Fraction fps, int width, int height, int sample_rate, int channels, float duration); + public: /// Blank constructor for DummyReader, with default settings. @@ -109,6 +113,9 @@ namespace openshot /// Constructor for DummyReader. DummyReader(openshot::Fraction fps, int width, int height, int sample_rate, int channels, float duration); + /// Constructor for DummyReader which takes a frame cache object. + DummyReader(openshot::Fraction fps, int width, int height, int sample_rate, int channels, float duration, CacheBase* cache); + virtual ~DummyReader(); /// Close File @@ -138,10 +145,6 @@ namespace openshot /// Open File - which is called by the constructor automatically void Open() override; - - /// @brief Add a frame to the dummy reader. This is useful when constructing unit tests that require custom frames. - /// @param frame The openshot::Frame object to write to this image - void WriteFrame(std::shared_ptr frame); }; } diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index 1b663a07e..8b6f752f7 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -32,16 +32,8 @@ using namespace openshot; -// Blank constructor for DummyReader, with default settings. -DummyReader::DummyReader() { - - // Call actual constructor with default values - DummyReader(Fraction(24,1), 1280, 768, 44100, 2, 30.0); -} - -// Constructor for DummyReader. Pass a framerate and samplerate. -DummyReader::DummyReader(Fraction fps, int width, int height, int sample_rate, int channels, float duration) { - +// Initialize variables used by constructor +void DummyReader::init(Fraction fps, int width, int height, int sample_rate, int channels, float duration) { // Set key info settings info.has_audio = false; info.has_video = true; @@ -68,10 +60,30 @@ DummyReader::DummyReader(Fraction fps, int width, int height, int sample_rate, i // Set the ratio based on the reduced fraction info.display_ratio.num = size.num; info.display_ratio.den = size.den; +} + +// Blank constructor for DummyReader, with default settings. +DummyReader::DummyReader() : dummy_cache(NULL), is_open(false) { + + // Initialize important variables + init(Fraction(24,1), 1280, 768, 44100, 2, 30.0); +} + +// Constructor for DummyReader. Pass a framerate and samplerate. +DummyReader::DummyReader(Fraction fps, int width, int height, int sample_rate, int channels, float duration) : dummy_cache(NULL), is_open(false) { + + // Initialize important variables + init(fps, width, height, sample_rate, channels, duration); +} - // Open and Close the reader, to populate its attributes (such as height, width, etc...) - Open(); - Close(); +// Constructor which also takes a cache object +DummyReader::DummyReader(Fraction fps, int width, int height, int sample_rate, int channels, float duration, CacheBase* cache) : is_open(false) { + + // Initialize important variables + init(fps, width, height, sample_rate, channels, duration); + + // Set cache object + dummy_cache = (CacheBase*) cache; } DummyReader::~DummyReader() { @@ -99,29 +111,23 @@ void DummyReader::Close() { // Mark as "closed" is_open = false; - - // Clear cache - dummy_cache.Clear(); - } -} - -// Add Frame objects to DummyReader -void DummyReader::WriteFrame(std::shared_ptr frame) -{ - if (frame) { - dummy_cache.Add(frame); } } // Get an openshot::Frame object for a specific frame number of this reader. It is either a blank frame -// or a custom frame added with the WriteFrame() method. +// or a custom frame added with passing a Cache object to the constructor. std::shared_ptr DummyReader::GetFrame(int64_t requested_frame) { // Check for open reader (or throw exception) if (!is_open) throw ReaderClosed("The ImageReader is closed. Call Open() before calling this method.", "dummy"); - if (dummy_cache.Count() == 0 && image_frame) { + int dummy_cache_count = 0; + if (dummy_cache) { + dummy_cache_count = dummy_cache->Count(); + } + + if (dummy_cache_count == 0 && image_frame) { // Create a scoped lock, allowing only a single thread to run the following code at one time const GenericScopedLock lock(getFrameCriticalSection); @@ -129,18 +135,18 @@ std::shared_ptr DummyReader::GetFrame(int64_t requested_frame) image_frame->number = requested_frame; return image_frame; - } else if (dummy_cache.Count() > 0) { + } else if (dummy_cache_count > 0) { // Create a scoped lock, allowing only a single thread to run the following code at one time const GenericScopedLock lock(getFrameCriticalSection); // Get a frame from the dummy cache - std::shared_ptr f = dummy_cache.GetFrame(requested_frame); + std::shared_ptr f = dummy_cache->GetFrame(requested_frame); if (f) { // return frame from cache (if found) return f; } else { // No cached frame found - throw InvalidFile("Requested frame not found. You can only access Frame numbers added with WriteFrame().", "dummy"); + throw InvalidFile("Requested frame not found. You can only access Frame numbers that exist in the Cache object.", "dummy"); } } else diff --git a/tests/DummyReader_Tests.cpp b/tests/DummyReader_Tests.cpp index 6ec11e08f..c72be2d9f 100644 --- a/tests/DummyReader_Tests.cpp +++ b/tests/DummyReader_Tests.cpp @@ -37,9 +37,24 @@ using namespace std; using namespace openshot; +TEST (DummyReader_Basic_Constructor) { + // Create a default fraction (should be 1/1) + openshot::DummyReader r; + r.Open(); // Open the reader + + // Check 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); +} + TEST (DummyReader_Constructor) { // Create a default fraction (should be 1/1) - openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); + openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 60.0); r.Open(); // Open the reader // Check values @@ -49,7 +64,7 @@ TEST (DummyReader_Constructor) { 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(60.0, r.info.duration); } TEST (DummyReader_Blank_Frame) { @@ -57,7 +72,7 @@ TEST (DummyReader_Blank_Frame) { openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); r.Open(); // Open the reader - // Get a blank frame (because we have not added any frames using WriteFrame() yet) + // 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 @@ -65,9 +80,9 @@ TEST (DummyReader_Blank_Frame) { } TEST (DummyReader_Fake_Frame) { - // Create a default fraction (should be 1/1) - openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); - r.Open(); // Open the reader + + // Create cache object to hold test frames + CacheMemory cache; // Let's create some test frames for (int64_t frame_number = 1; frame_number <= 30; frame_number++) { @@ -87,10 +102,14 @@ TEST (DummyReader_Fake_Frame) { 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 - // Write test frame to dummy reader - r.WriteFrame(f); + // 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 + // Verify our artificial audio sample data is correct CHECK_EQUAL(1, r.GetFrame(1)->number); CHECK_EQUAL(1, r.GetFrame(1)->GetAudioSamples(0)[0]); @@ -99,23 +118,32 @@ TEST (DummyReader_Fake_Frame) { 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 (DummyReader_Invalid_Fake_Frame) { - // Create a default fraction (should be 1/1) - openshot::DummyReader r(openshot::Fraction(30, 1), 1920, 1080, 44100, 2, 30.0); - r.Open(); - // Create fake frames (with specific frame #, samples, and channels) std::shared_ptr f1(new openshot::Frame(1, 1470, 2)); std::shared_ptr f2(new openshot::Frame(2, 1470, 2)); - // Write test frames to dummy reader - r.WriteFrame(f1); - r.WriteFrame(f2); + // Add test frames to cache object + CacheMemory cache; + cache.Add(f1); + cache.Add(f2); + + // Create a default fraction (should be 1/1) + 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, InvalidFile); + + // Clean up + cache.Clear(); + r.Close(); } \ No newline at end of file