From 3c99e53dad17d9ec872d257dedd2cb4490a0cea8 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 8 Jan 2014 01:43:58 -0600 Subject: [PATCH] Added JSON change method, which accepts a diff / sync JSON array, and applies the changes to a timeline and timeline associated properties and items. Also fixed many bugs on JSON type checking. --- include/ClipBase.h | 3 + include/Exceptions.h | 10 ++ include/Timeline.h | 11 ++ src/ChunkReader.cpp | 6 +- src/Clip.cpp | 58 ++++----- src/ClipBase.cpp | 11 +- src/Color.cpp | 6 +- src/Coordinate.cpp | 10 +- src/EffectBase.cpp | 2 +- src/FFmpegReader.cpp | 2 +- src/ImageReader.cpp | 2 +- src/KeyFrame.cpp | 4 +- src/Main.cpp | 13 ++ src/Point.cpp | 10 +- src/ReaderBase.cpp | 46 +++---- src/TextReader.cpp | 20 +-- src/Timeline.cpp | 243 +++++++++++++++++++++++++++++++++++- src/WriterBase.cpp | 46 +++---- src/effects/ChromaKey.cpp | 4 +- src/effects/Deinterlace.cpp | 2 +- src/effects/Mask.cpp | 4 +- 21 files changed, 395 insertions(+), 118 deletions(-) diff --git a/include/ClipBase.h b/include/ClipBase.h index c8d517dd..661c26c7 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -49,6 +49,7 @@ namespace openshot { */ class ClipBase { private: + string id; ///< ID Property for all derived Clip and Effect classes. float position; ///< The position on the timeline where this clip should start playing int layer; ///< The layer this clip is on. Lower clips are covered up by higher clips. float start; ///< The position in seconds to start playing (used to trim the beginning of a clip) @@ -63,6 +64,7 @@ namespace openshot { bool operator>= ( ClipBase& a) { return (Position() >= a.Position()); } /// Get basic properties + string Id() { return id; } ///< Get the Id of this clip object float Position() { return position; } ///< Get position on timeline (in seconds) int Layer() { return layer; } ///< Get layer of clip on timeline (lower number is covered by higher numbers) float Start() { return start; } ///< Get start position (in seconds) of clip (trim start of video) @@ -70,6 +72,7 @@ namespace openshot { float Duration() { return End() - Start(); } ///< Get the length of this clip (in seconds) /// Set basic properties + void Id(string value) { id = value; } ///> Set the Id of this clip object void Position(float value) { position = value; } ///< Set position on timeline (in seconds) void Layer(int value) { layer = value; } ///< Set layer of clip on timeline (lower number is covered by higher numbers) void Start(float value) { start = value; } ///< Set start position (in seconds) of clip (trim start of video) diff --git a/include/Exceptions.h b/include/Exceptions.h index bae491e8..fc227ca2 100644 --- a/include/Exceptions.h +++ b/include/Exceptions.h @@ -178,6 +178,16 @@ namespace openshot { virtual ~InvalidSampleRate() throw () {} }; + /// Exception for missing JSON Change key + class InvalidJSONKey : public BaseException + { + public: + string json; + InvalidJSONKey(string message, string json) + : BaseException(message), json(json) { } + virtual ~InvalidJSONKey() throw () {} + }; + /// Exception when no streams are found in the file class NoStreamsFound : public BaseException { diff --git a/include/Timeline.h b/include/Timeline.h index a7e4eced..1456833b 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -156,6 +156,11 @@ namespace openshot { /// Process a new layer of video or audio void add_layer(tr1::shared_ptr new_frame, Clip* source_clip, int clip_frame_number, int timeline_frame_number); + /// Apply JSON Diffs to various objects contained in this timeline + void apply_json_to_clips(Json::Value change) throw(InvalidJSONKey); ///::iterator clip_itr; + for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) + { + // Get clip object from the iterator + Clip *c = (*clip_itr); + if (c->Id() == clip_id) { + existing_clip = c; + break; // clip found, exit loop + } + } + break; // id found, exit loop + } + } + } + + // Determine type of change operation + if (change_type == "insert") { + + // Create new clip + Clip *clip = new Clip(); + clip->SetJsonValue(change["value"]); // Set properties of new clip from JSON + AddClip(clip); // Add clip to timeline + + } else if (change_type == "update") { + + // Update existing clip + if (existing_clip) + existing_clip->SetJsonValue(change["value"]); // Update clip properties from JSON + + } else if (change_type == "delete") { + + // Remove existing clip + if (existing_clip) + RemoveClip(existing_clip); // Remove clip from timeline + + } + +} + +// Apply JSON diff to effects +void Timeline::apply_json_to_effects(Json::Value change) throw(InvalidJSONKey) { + + // Get key and type of change + string change_type = change["type"].asString(); + string effect_id = ""; + EffectBase *existing_effect = NULL; + + // Find id of an effect (if any) + for (int x = 0; x < change["key"].size(); x++) { + // Get each change + Json::Value key_part = change["key"][x]; + + if (key_part.isObject()) { + // Check for id + if (!key_part["id"].isNull()) + { + // Set the id + effect_id = key_part["id"].asString(); + + // Find matching effect in timeline (if any) + list::iterator effect_itr; + for (effect_itr=effects.begin(); effect_itr != effects.end(); ++effect_itr) + { + // Get effect object from the iterator + EffectBase *e = (*effect_itr); + if (e->Id() == effect_id) { + existing_effect = e; + break; // effect found, exit loop + } + } + break; // id found, exit loop + } + } + } + + // Determine type of change operation + if (change_type == "insert") { + + // Determine type of effect + string effect_type = change["value"]["type"].asString(); + + // Create Effect + EffectBase *e = NULL; + + // Init the matching effect object + if (effect_type == "ChromaKey") + e = new ChromaKey(); + + else if (effect_type == "Deinterlace") + e = new Deinterlace(); + + else if (effect_type == "Mask") + e = new Mask(); + + else if (effect_type == "Negate") + e = new Negate(); + + // Load Json into Effect + e->SetJsonValue(change["value"]); + + // Add Effect to Timeline + AddEffect(e); + + } else if (change_type == "update") { + + // Update existing effect + if (existing_effect) + existing_effect->SetJsonValue(change["value"]); // Update effect properties from JSON + + } else if (change_type == "delete") { + + // Remove existing effect + if (existing_effect) + RemoveEffect(existing_effect); // Remove effect from timeline + + } + +} + +// Apply JSON diff to timeline properties +void Timeline::apply_json_to_timeline(Json::Value change) throw(InvalidJSONKey) { + + // Get key and type of change + string change_type = change["type"].asString(); + string root_key = change["key"][(uint)0].asString(); + + // Determine type of change operation + if (change_type == "insert" || change_type == "update") { + + // INSERT / UPDATE + // Check for valid property + if (root_key == "color") + // Set color + color.SetJsonValue(change["value"]); + else if (root_key == "viewport_scale") + // Set viewport scale + viewport_scale.SetJsonValue(change["value"]); + else if (root_key == "viewport_x") + // Set viewport x offset + viewport_x.SetJsonValue(change["value"]); + else if (root_key == "viewport_y") + // Set viewport y offset + viewport_y.SetJsonValue(change["value"]); + else + // Error parsing JSON (or missing keys) + throw InvalidJSONKey("JSON change key is invalid", change.toStyledString()); + + + } else if (change["type"].asString() == "delete") { + + // DELETE / RESET + // Reset the following properties (since we can't delete them) + if (root_key == "color") { + color = Color(); + color.red = Keyframe(0.0); + color.green = Keyframe(0.0); + color.blue = Keyframe(0.0); + } + else if (root_key == "viewport_scale") + viewport_scale = Keyframe(1.0); + else if (root_key == "viewport_x") + viewport_x = Keyframe(0.0); + else if (root_key == "viewport_y") + viewport_y = Keyframe(0.0); + else + // Error parsing JSON (or missing keys) + throw InvalidJSONKey("JSON change key is invalid", change.toStyledString()); + + } + +} + + + + diff --git a/src/WriterBase.cpp b/src/WriterBase.cpp index 08a28342..534dcde5 100644 --- a/src/WriterBase.cpp +++ b/src/WriterBase.cpp @@ -209,60 +209,60 @@ void WriterBase::SetJson(string value) throw(InvalidJSON) { void WriterBase::SetJsonValue(Json::Value root) { // Set data from Json (if key is found) - if (root["has_video"] != Json::nullValue) + if (!root["has_video"].isNull()) info.has_video = root["has_video"].asBool(); - if (root["has_audio"] != Json::nullValue) + if (!root["has_audio"].isNull()) info.has_audio = root["has_audio"].asBool(); - if (root["duration"] != Json::nullValue) + if (!root["duration"].isNull()) info.duration = root["duration"].asDouble(); - if (root["file_size"] != Json::nullValue) + if (!root["file_size"].isNull()) info.file_size = (long long) root["file_size"].asUInt(); - if (root["height"] != Json::nullValue) + if (!root["height"].isNull()) info.height = root["height"].asInt(); - if (root["width"] != Json::nullValue) + if (!root["width"].isNull()) info.width = root["width"].asInt(); - if (root["pixel_format"] != Json::nullValue) + if (!root["pixel_format"].isNull()) info.pixel_format = root["pixel_format"].asInt(); - if (root["fps"] != Json::nullValue) { + if (!root["fps"].isNull()) { info.fps.num = root["fps"]["num"].asInt(); info.fps.den = root["fps"]["den"].asInt(); } - if (root["video_bit_rate"] != Json::nullValue) + if (!root["video_bit_rate"].isNull()) info.video_bit_rate = root["video_bit_rate"].asInt(); - if (root["pixel_ratio"] != Json::nullValue) { + if (!root["pixel_ratio"].isNull()) { info.pixel_ratio.num = root["pixel_ratio"]["num"].asInt(); info.pixel_ratio.den = root["pixel_ratio"]["den"].asInt(); } - if (root["display_ratio"] != Json::nullValue) { + if (!root["display_ratio"].isNull()) { info.display_ratio.num = root["display_ratio"]["num"].asInt(); info.display_ratio.den = root["display_ratio"]["den"].asInt(); } - if (root["vcodec"] != Json::nullValue) + if (!root["vcodec"].isNull()) info.vcodec = root["vcodec"].asString(); - if (root["video_length"] != Json::nullValue) + if (!root["video_length"].isNull()) info.video_length = (long int) root["video_length"].asUInt(); - if (root["video_stream_index"] != Json::nullValue) + if (!root["video_stream_index"].isNull()) info.video_stream_index = root["video_stream_index"].asInt(); - if (root["video_timebase"] != Json::nullValue) { + if (!root["video_timebase"].isNull()) { info.video_timebase.num = root["video_timebase"]["num"].asInt(); info.video_timebase.den = root["video_timebase"]["den"].asInt(); } - if (root["interlaced_frame"] != Json::nullValue) + if (!root["interlaced_frame"].isNull()) info.interlaced_frame = root["interlaced_frame"].asBool(); - if (root["top_field_first"] != Json::nullValue) + if (!root["top_field_first"].isNull()) info.top_field_first = root["top_field_first"].asBool(); - if (root["acodec"] != Json::nullValue) + if (!root["acodec"].isNull()) info.acodec = root["acodec"].asString(); - if (root["audio_bit_rate"] != Json::nullValue) + if (!root["audio_bit_rate"].isNull()) info.audio_bit_rate = root["audio_bit_rate"].asInt(); - if (root["sample_rate"] != Json::nullValue) + if (!root["sample_rate"].isNull()) info.sample_rate = root["sample_rate"].asInt(); - if (root["channels"] != Json::nullValue) + if (!root["channels"].isNull()) info.channels = root["channels"].asInt(); - if (root["audio_stream_index"] != Json::nullValue) + if (!root["audio_stream_index"].isNull()) info.audio_stream_index = root["audio_stream_index"].asInt(); - if (root["audio_timebase"] != Json::nullValue) { + if (!root["audio_timebase"].isNull()) { info.audio_timebase.num = root["audio_timebase"]["num"].asInt(); info.audio_timebase.den = root["audio_timebase"]["den"].asInt(); } diff --git a/src/effects/ChromaKey.cpp b/src/effects/ChromaKey.cpp index 833e1017..5c1c0dd7 100644 --- a/src/effects/ChromaKey.cpp +++ b/src/effects/ChromaKey.cpp @@ -112,8 +112,8 @@ void ChromaKey::SetJsonValue(Json::Value root) { EffectBase::SetJsonValue(root); // Set data from Json (if key is found) - if (root["color"] != Json::nullValue) + if (!root["color"].isNull()) color.SetJsonValue(root["color"]); - if (root["fuzz"] != Json::nullValue) + if (!root["fuzz"].isNull()) fuzz.SetJsonValue(root["fuzz"]); } diff --git a/src/effects/Deinterlace.cpp b/src/effects/Deinterlace.cpp index 557be0b2..7725572a 100644 --- a/src/effects/Deinterlace.cpp +++ b/src/effects/Deinterlace.cpp @@ -120,6 +120,6 @@ void Deinterlace::SetJsonValue(Json::Value root) { EffectBase::SetJsonValue(root); // Set data from Json (if key is found) - if (root["isOdd"] != Json::nullValue) + if (!root["isOdd"].isNull()) isOdd = root["isOdd"].asBool(); } diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index 1cc51190..eaa46fed 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -178,9 +178,9 @@ void Mask::SetJsonValue(Json::Value root) { EffectBase::SetJsonValue(root); // Set data from Json (if key is found) - if (root["brightness"] != Json::nullValue) + if (!root["brightness"].isNull()) brightness.SetJsonValue(root["brightness"]); - if (root["contrast"] != Json::nullValue) + if (!root["contrast"].isNull()) contrast.SetJsonValue(root["contrast"]); }