diff --git a/include/Clip.h b/include/Clip.h index d35bce77..f4a5d783 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -48,6 +48,7 @@ #include "ChunkReader.h" #include "KeyFrame.h" #include "ReaderBase.h" +#include "DummyReader.h" using namespace std; using namespace openshot; diff --git a/include/DummyReader.h b/include/DummyReader.h index dd403d91..2a3de7ca 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -65,6 +65,9 @@ namespace openshot public: + /// Blank constructor for DummyReader, with default settings. + DummyReader(); + /// Constructor for DummyReader. DummyReader(Framerate fps, int width, int height, int sample_rate, int channels, float duration); diff --git a/include/Timeline.h b/include/Timeline.h index ed4091d1..d63931cc 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -50,6 +50,11 @@ #include "FrameRate.h" #include "KeyFrame.h" +#include "effects/ChromaKey.h" +#include "effects/Deinterlace.h" +#include "effects/Mask.h" +#include "effects/Negate.h" + using namespace std; using namespace openshot; @@ -142,11 +147,6 @@ namespace openshot { class Timeline : public ReaderBase { private: bool is_open; /// clips; /// closing_clips; /// open_clips; /// new_frame, Clip* source_clip, int clip_frame_number, int timeline_frame_number); /// Calculate time of a frame number, based on a framerate - float calculate_time(int number, Framerate rate); + float calculate_time(int number, Fraction rate); /// Apply effects to the source frame (if any) tr1::shared_ptr apply_effects(tr1::shared_ptr frame, int timeline_frame_number, int layer); @@ -182,7 +182,7 @@ namespace openshot { /// @param fps The frames rate of the timeline /// @param sample_rate The sample rate of the timeline's audio /// @param channels The number of audio channels of the timeline - Timeline(int width, int height, Framerate fps, int sample_rate, int channels); + Timeline(int width, int height, Fraction fps, int sample_rate, int channels); /// @brief Add an openshot::Clip to the timeline /// @param clip Add an openshot::Clip to the timeline. A clip can contain any type of Reader. @@ -201,26 +201,12 @@ namespace openshot { /// Return the list of effects on the timeline list Effects() { return effects; }; - /// Get the framerate of this timeline - Framerate FrameRate() { return fps; }; - - /// @brief Set the framerate for this timeline - /// @param new_fps The new frame rate to set for this timeline - void FrameRate(Framerate new_fps) { fps = new_fps; }; - /// Get an openshot::Frame object for a specific frame number of this timeline. /// /// @returns The requested frame (containing the image) /// @param requested_frame The frame number that is requested. tr1::shared_ptr GetFrame(int requested_frame) throw(ReaderClosed); - /// Get the height of canvas and viewport - int Height() { return height; } - - /// @brief Set the height of canvas and viewport - /// @param new_height The new height to set for this timeline - void Height(int new_height) { height = new_height; } - // Curves for the viewport Keyframe viewport_scale; ///SetJsonValue(root["reader"]); + + } else if (type == "DummyReader") { + + // Create new reader + reader = new DummyReader(); + reader->SetJsonValue(root["reader"]); } // Re-Open reader (if needed) diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index c110c395..3681052d 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -29,6 +29,12 @@ using namespace openshot; +// Blank constructor for DummyReader, with default settings. +DummyReader::DummyReader() : fps(Framerate(24,1)), width(1280), height(720), + sample_rate(44100), channels(2), duration(30.0) { + +} + // Constructor for DummyReader. Pass a framerate and samplerate. DummyReader::DummyReader(Framerate fps, int width, int height, int sample_rate, int channels, float duration) : fps(fps), width(width), height(height), sample_rate(sample_rate), channels(channels), duration(duration) diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index 9949b0bf..0cce9aab 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -296,7 +296,7 @@ void Keyframe::SetJsonValue(Json::Value root) { Points.clear(); if (root["Points"] != Json::nullValue) - // loop through points, and find a matching coordinate + // loop through points for (int x = 0; x < root["Points"].size(); x++) { // Get each point Json::Value existing_point = root["Points"][x]; diff --git a/src/Main_Blackmagic.cpp b/src/Main_Blackmagic.cpp index 0b6a1a3a..ee599428 100644 --- a/src/Main_Blackmagic.cpp +++ b/src/Main_Blackmagic.cpp @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) struct tm * timeinfo; /* TIMELINE ---------------- */ - Timeline t(1920, 1080, Framerate(30,1), 48000, 2); + Timeline t(1920, 1080, Fraction(30,1), 48000, 2); // Create background video ImageReader b1("/home/jonathan/Pictures/moon.jpg"); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index ded8d638..f3116438 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -30,8 +30,7 @@ using namespace openshot; // Default Constructor for the timeline (which sets the canvas width and height) -Timeline::Timeline(int width, int height, Framerate fps, int sample_rate, int channels) : - width(width), height(height), fps(fps), sample_rate(sample_rate), channels(channels), is_open(false) +Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels) : is_open(false) { // Init viewport size (curve based, because it can be animated) viewport_scale = Keyframe(100.0); @@ -51,10 +50,10 @@ Timeline::Timeline(int width, int height, Framerate fps, int sample_rate, int ch InitFileInfo(); info.width = width; info.height = height; - info.fps = fps.GetFraction(); + info.fps = fps; info.sample_rate = sample_rate; info.channels = channels; - info.video_timebase = fps.GetFraction().Reciprocal(); + info.video_timebase = fps.Reciprocal(); } // Add an openshot::Clip to the timeline @@ -62,7 +61,7 @@ void Timeline::AddClip(Clip* clip) throw(ReaderClosed) { // All clips must be converted to the frame rate of this timeline, // so assign the same frame rate to each clip. - clip->Reader()->info.fps = fps.GetFraction(); + clip->Reader()->info.fps = info.fps; // Add clip to list clips.push_back(clip); @@ -94,10 +93,10 @@ void Timeline::RemoveClip(Clip* clip) } // Calculate time of a frame number, based on a framerate -float Timeline::calculate_time(int number, Framerate rate) +float Timeline::calculate_time(int number, Fraction rate) { // Get float version of fps fraction - float raw_fps = rate.GetFPS(); + float raw_fps = rate.ToFloat(); // Return the time (in seconds) of this frame return float(number - 1) / raw_fps; @@ -107,7 +106,7 @@ float Timeline::calculate_time(int number, Framerate rate) tr1::shared_ptr Timeline::apply_effects(tr1::shared_ptr frame, int timeline_frame_number, int layer) { // Calculate time of frame - float requested_time = calculate_time(timeline_frame_number, fps); + float requested_time = calculate_time(timeline_frame_number, info.fps); // Find Effects at this position and layer list::iterator effect_itr; @@ -129,7 +128,7 @@ tr1::shared_ptr Timeline::apply_effects(tr1::shared_ptr frame, int { // Determine the frame needed for this clip (based on the position on the timeline) float time_diff = (requested_time - effect->Position()) + effect->Start(); - int effect_frame_number = round(time_diff * fps.GetFPS()) + 1; + int effect_frame_number = round(time_diff * info.fps.ToFloat()) + 1; // Apply the effect to this frame frame = effect->GetFrame(frame, effect_frame_number); @@ -192,7 +191,7 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, in int blue = source_clip->wave_color.blue.GetInt(timeline_frame_number); // Generate Waveform Dynamically (the size of the timeline) - source_image = source_frame->GetWaveform(width, height, red, green, blue); + source_image = source_frame->GetWaveform(info.width, info.height, red, green, blue); } // Get some basic image properties @@ -207,7 +206,7 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, in } /* RESIZE SOURCE IMAGE - based on scale type */ - Magick::Geometry new_size(width, height); + Magick::Geometry new_size(info.width, info.height); switch (source_clip->scale) { case (SCALE_FIT): @@ -223,10 +222,10 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, in source_height = source_image->size().height(); break; case (SCALE_CROP): - Magick::Geometry width_size(width, round(width / (float(source_width) / float(source_height)))); - Magick::Geometry height_size(round(height / (float(source_height) / float(source_width))), height); + Magick::Geometry width_size(info.width, round(info.width / (float(source_width) / float(source_height)))); + Magick::Geometry height_size(round(info.height / (float(source_height) / float(source_width))), info.height); new_size.aspect(false); // respect aspect ratio - if (width_size.width() >= width && width_size.height() >= height) + if (width_size.width() >= info.width && width_size.height() >= info.height) source_image->resize(width_size); // width is larger, so resize to it else source_image->resize(height_size); // height is larger, so resize to it @@ -241,39 +240,39 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, in switch (source_clip->gravity) { case (GRAVITY_TOP): - x = (width - source_width) / 2.0; // center + x = (info.width - source_width) / 2.0; // center break; case (GRAVITY_TOP_RIGHT): - x = width - source_width; // right + x = info.width - source_width; // right break; case (GRAVITY_LEFT): - y = (height - source_height) / 2.0; // center + y = (info.height - source_height) / 2.0; // center break; case (GRAVITY_CENTER): - x = (width - source_width) / 2.0; // center - y = (height - source_height) / 2.0; // center + x = (info.width - source_width) / 2.0; // center + y = (info.height - source_height) / 2.0; // center break; case (GRAVITY_RIGHT): - x = width - source_width; // right - y = (height - source_height) / 2.0; // center + x = info.width - source_width; // right + y = (info.height - source_height) / 2.0; // center break; case (GRAVITY_BOTTOM_LEFT): - y = (height - source_height); // bottom + y = (info.height - source_height); // bottom break; case (GRAVITY_BOTTOM): - x = (width - source_width) / 2.0; // center - y = (height - source_height); // bottom + x = (info.width - source_width) / 2.0; // center + y = (info.height - source_height); // bottom break; case (GRAVITY_BOTTOM_RIGHT): - x = width - source_width; // right - y = (height - source_height); // bottom + x = info.width - source_width; // right + y = (info.height - source_height); // bottom break; } /* LOCATION, ROTATION, AND SCALE */ float r = source_clip->rotation.GetValue(clip_frame_number); // rotate in degrees - x += width * source_clip->location_x.GetValue(clip_frame_number); // move in percentage of final width - y += height * source_clip->location_y.GetValue(clip_frame_number); // move in percentage of final height + x += info.width * source_clip->location_x.GetValue(clip_frame_number); // move in percentage of final width + y += info.height * source_clip->location_y.GetValue(clip_frame_number); // move in percentage of final height float sx = source_clip->scale_x.GetValue(clip_frame_number); // percentage X scale float sy = source_clip->scale_y.GetValue(clip_frame_number); // percentage Y scale bool is_x_animated = source_clip->location_x.Points.size() > 2; @@ -295,11 +294,11 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, in //cout << "COMPLEX" << endl; /* RESIZE SOURCE CANVAS - to the same size as timeline canvas */ - if (source_width != width || source_height != height) + if (source_width != info.width || source_height != info.height) { source_image->borderColor(Magick::Color("none")); source_image->border(Magick::Geometry(1, 1, 0, 0, false, false)); // prevent stretching of edge pixels (during the canvas resize) - source_image->size(Magick::Geometry(width, height, 0, 0, false, false)); // resize the canvas (to prevent clipping) + source_image->size(Magick::Geometry(info.width, info.height, 0, 0, false, false)); // resize the canvas (to prevent clipping) } // Use the distort operator, which is very CPU intensive @@ -321,7 +320,7 @@ void Timeline::add_layer(tr1::shared_ptr new_frame, Clip* source_clip, in int red = color.red.GetInt(timeline_frame_number); int green = color.green.GetInt(timeline_frame_number); int blue = color.blue.GetInt(timeline_frame_number); - new_frame->AddColor(width, height, Magick::Color(red, green, blue, 0)); + new_frame->AddColor(info.width, info.height, Magick::Color(red, green, blue, 0)); /* COMPOSITE SOURCE IMAGE (LAYER) ONTO FINAL IMAGE */ tr1::shared_ptr new_image = new_frame->GetImage(); @@ -409,6 +408,9 @@ void Timeline::Close() // Actually close the clips update_closed_clips(); is_open = false; + + // Clear cache + final_cache.Clear(); } // Open the reader (and start consuming resources) @@ -421,9 +423,9 @@ void Timeline::Open() int Timeline::GetSamplesPerFrame(int frame_number) { // Get the total # of samples for the previous frame, and the current frame (rounded) - double fps_value = fps.GetFraction().Reciprocal().ToDouble(); - double previous_samples = round((sample_rate * fps_value) * (frame_number - 1)); - double total_samples = round((sample_rate * fps_value) * frame_number); + double fps_value = info.fps.Reciprocal().ToDouble(); + double previous_samples = round((info.sample_rate * fps_value) * (frame_number - 1)); + double total_samples = round((info.sample_rate * fps_value) * frame_number); // Subtract the previous frame's total samples with this frame's total samples. Not all sample rates can // be evenly divided into frames, so each frame can have have different # of samples. @@ -465,11 +467,11 @@ tr1::shared_ptr Timeline::GetFrame(int requested_frame) throw(ReaderClose #pragma xx omp task firstprivate(frame_number) { // Create blank frame (which will become the requested frame) - tr1::shared_ptr new_frame(tr1::shared_ptr(new Frame(frame_number, width, height, "#000000", GetSamplesPerFrame(frame_number), channels))); + tr1::shared_ptr new_frame(tr1::shared_ptr(new Frame(frame_number, info.width, info.height, "#000000", GetSamplesPerFrame(frame_number), info.channels))); new_frame->SetSampleRate(info.sample_rate); // Calculate time of frame - float requested_time = calculate_time(frame_number, fps); + float requested_time = calculate_time(frame_number, info.fps); // Find Clips at this time list::iterator clip_itr; @@ -495,7 +497,7 @@ tr1::shared_ptr Timeline::GetFrame(int requested_frame) throw(ReaderClose { // Determine the frame needed for this clip (based on the position on the timeline) float time_diff = (requested_time - clip->Position()) + clip->Start(); - int clip_frame_number = round(time_diff * fps.GetFPS()) + 1; + int clip_frame_number = round(time_diff * info.fps.ToFloat()) + 1; // Add clip's frame as layer add_layer(new_frame, clip, clip_frame_number, frame_number); @@ -510,7 +512,7 @@ tr1::shared_ptr Timeline::GetFrame(int requested_frame) throw(ReaderClose int red = color.red.GetInt(frame_number); int green = color.green.GetInt(frame_number); int blue = color.blue.GetInt(frame_number); - new_frame->AddColor(width, height, Magick::Color(red, green, blue)); + new_frame->AddColor(info.width, info.height, Magick::Color(red, green, blue)); } // Add final frame to cache @@ -548,6 +550,34 @@ Json::Value Timeline::JsonValue() { // Create root json object Json::Value root = ReaderBase::JsonValue(); // get parent properties root["type"] = "Timeline"; + root["viewport_scale"] = viewport_scale.JsonValue(); + root["viewport_x"] = viewport_x.JsonValue(); + root["viewport_y"] = viewport_y.JsonValue(); + root["color"] = color.JsonValue(); + + // Add array of clips + root["Clips"] = Json::Value(Json::arrayValue); + + // Find Clips at this time + list::iterator clip_itr; + for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) + { + // Get clip object from the iterator + Clip *existing_clip = (*clip_itr); + root["Clips"].append(existing_clip->JsonValue()); + } + + // Add array of effects + root["Effects"] = Json::Value(Json::arrayValue); + + // loop through effects + list::iterator effect_itr; + for (effect_itr=effects.begin(); effect_itr != effects.end(); ++effect_itr) + { + // Get clip object from the iterator + EffectBase *existing_effect = (*effect_itr); + root["Effects"].append(existing_effect->JsonValue()); + } // return JsonValue return root; @@ -577,9 +607,63 @@ void Timeline::SetJson(string value) throw(InvalidJSON) { } // Load Json::JsonValue into this object -void Timeline::SetJsonValue(Json::Value root) throw(InvalidFile) { +void Timeline::SetJsonValue(Json::Value root) throw(InvalidFile, ReaderClosed) { + + // Close timeline before we do anything (this also removes all open and closing clips) + Close(); // Set parent data ReaderBase::SetJsonValue(root); + // Clear existing clips + clips.clear(); + + if (root["Clips"] != Json::nullValue) + // loop through clips + for (int x = 0; x < root["Clips"].size(); x++) { + // Get each clip + Json::Value existing_clip = root["Clips"][x]; + + // Create Clip + Clip *c = new Clip(); + + // Load Json into Clip + c->SetJsonValue(existing_clip); + + // Add Clip to Timeline + AddClip(c); + } + + // Clear existing effects + effects.clear(); + + if (root["Effects"] != Json::nullValue) + // loop through effects + for (int x = 0; x < root["Effects"].size(); x++) { + // Get each effect + Json::Value existing_effect = root["Effects"][x]; + + // Create Effect + EffectBase *e = NULL; + + if (existing_effect["type"] != Json::nullValue) + // Init the matching effect object + if (existing_effect["type"].asString() == "ChromaKey") + e = new ChromaKey(); + + else if (existing_effect["type"].asString() == "Deinterlace") + e = new Deinterlace(); + + else if (existing_effect["type"].asString() == "Mask") + e = new Mask(); + + else if (existing_effect["type"].asString() == "Negate") + e = new Negate(); + + // Load Json into Effect + e->SetJsonValue(existing_effect); + + // Add Effect to Timeline + AddEffect(e); + } } diff --git a/src/effects/ChromaKey.cpp b/src/effects/ChromaKey.cpp index 86c7b5b7..833e1017 100644 --- a/src/effects/ChromaKey.cpp +++ b/src/effects/ChromaKey.cpp @@ -29,6 +29,12 @@ using namespace openshot; +/// Blank constructor, useful when using Json to load the effect properties +ChromaKey::ChromaKey() : fuzz(0.0) { + // Init default color + color = Color(); +} + // Default constructor, which takes an openshot::Color object and a 'fuzz' factor, which // is used to determine how similar colored pixels are matched. The higher the fuzz, the // more colors are matched. @@ -68,6 +74,7 @@ Json::Value ChromaKey::JsonValue() { // Create root json object Json::Value root = EffectBase::JsonValue(); // get parent properties + root["type"] = "ChromaKey"; root["color"] = color.JsonValue(); root["fuzz"] = fuzz.JsonValue(); diff --git a/src/effects/Deinterlace.cpp b/src/effects/Deinterlace.cpp index 1fb8ca5d..557be0b2 100644 --- a/src/effects/Deinterlace.cpp +++ b/src/effects/Deinterlace.cpp @@ -29,6 +29,11 @@ using namespace openshot; +/// Blank constructor, useful when using Json to load the effect properties +Deinterlace::Deinterlace() : isOdd(true) { + +} + // Default constructor Deinterlace::Deinterlace(bool UseOddLines) : isOdd(UseOddLines) { @@ -78,6 +83,7 @@ Json::Value Deinterlace::JsonValue() { // Create root json object Json::Value root = EffectBase::JsonValue(); // get parent properties + root["type"] = "Deinterlace"; root["isOdd"] = isOdd; // return JsonValue diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index 24f13e4c..1cc51190 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -29,6 +29,11 @@ using namespace openshot; +/// Blank constructor, useful when using Json to load the effect properties +Mask::Mask() : reader(NULL) { + +} + // Default constructor Mask::Mask(ReaderBase *mask_reader, Keyframe mask_brightness, Keyframe mask_contrast) throw(InvalidFile, ReaderClosed) : reader(mask_reader), brightness(mask_brightness), contrast(mask_contrast) @@ -134,6 +139,7 @@ Json::Value Mask::JsonValue() { // Create root json object Json::Value root = EffectBase::JsonValue(); // get parent properties + root["type"] = "Mask"; root["brightness"] = brightness.JsonValue(); root["contrast"] = contrast.JsonValue(); //root["reader"] = reader.JsonValue(); diff --git a/src/effects/Negate.cpp b/src/effects/Negate.cpp index 7756a6c7..47ba6431 100644 --- a/src/effects/Negate.cpp +++ b/src/effects/Negate.cpp @@ -65,6 +65,7 @@ Json::Value Negate::JsonValue() { // Create root json object Json::Value root = EffectBase::JsonValue(); // get parent properties + root["type"] = "Negate"; // return JsonValue return root; diff --git a/tests/Timeline_Tests.cpp b/tests/Timeline_Tests.cpp index ff818bab..ac62aaf7 100644 --- a/tests/Timeline_Tests.cpp +++ b/tests/Timeline_Tests.cpp @@ -34,59 +34,52 @@ using namespace openshot; TEST(Timeline_Constructor) { // Create a default fraction (should be 1/1) - Framerate fps(30000,1000); + Fraction fps(30000,1000); Timeline t1(640, 480, fps, 44100, 2); // Check values - CHECK_EQUAL(640, t1.Width()); - CHECK_EQUAL(480, t1.Height()); + CHECK_EQUAL(640, t1.info.width); + CHECK_EQUAL(480, t1.info.height); // Create a default fraction (should be 1/1) Timeline t2(300, 240, fps, 44100, 2); // Check values - CHECK_EQUAL(300, t2.Width()); - CHECK_EQUAL(240, t2.Height()); + CHECK_EQUAL(300, t2.info.width); + CHECK_EQUAL(240, t2.info.height); } TEST(Timeline_Width_and_Height_Functions) { // Create a default fraction (should be 1/1) - Framerate fps(30000,1000); + Fraction fps(30000,1000); Timeline t1(640, 480, fps, 44100, 2); // Check values - CHECK_EQUAL(640, t1.Width()); - CHECK_EQUAL(480, t1.Height()); + CHECK_EQUAL(640, t1.info.width); + CHECK_EQUAL(480, t1.info.height); // Set width - t1.Width(600); + t1.info.width = 600; // Check values - CHECK_EQUAL(600, t1.Width()); - CHECK_EQUAL(480, t1.Height()); + CHECK_EQUAL(600, t1.info.width); + CHECK_EQUAL(480, t1.info.height); // Set height - t1.Height(400); + t1.info.height = 400; // Check values - CHECK_EQUAL(600, t1.Width()); - CHECK_EQUAL(400, t1.Height()); + CHECK_EQUAL(600, t1.info.width); + CHECK_EQUAL(400, t1.info.height); } TEST(Timeline_Framerate) { // Create a default fraction (should be 1/1) - Framerate fps(24,1); + Fraction fps(24,1); Timeline t1(640, 480, fps, 44100, 2); // Check values - CHECK_CLOSE(24.0f, t1.FrameRate().GetFPS(), 0.00001); - - // Set width - t1.FrameRate(Framerate(30000,1001)); - - // Check values - CHECK_CLOSE(29.97002f, t1.FrameRate().GetFPS(), 0.00001); - + CHECK_CLOSE(24.0f, t1.info.fps.ToFloat(), 0.00001); }